Ruby accessors on steroids.
Add this line to your application's Gemfile:
gem 'smart_properties'
And then execute:
$ bundle
Or install it yourself as:
$ gem install smart_properties
SmartProperties
are meant to extend standard Ruby classes. Simply include
the SmartProperties
module and use the property
method along with a name
and optional configuration parameters to define new properties. Calling this
method results in the generation of a getter and setter pair. In contrast to
traditional Ruby accessors -- created by calling attr_accessor
,
SmartProperties
provide much more functionality:
- input conversion,
- input validation,
- default values, and
- presence checking.
These features can be configured by calling the property
method with
additional configuration parameters. The module also provides a default
implementation for a constructor that accepts a set of attributes. This is
comparable to the constructor of ActiveRecord
objects.
Before we discuss the configuration of properties in more detail, we first
present a short synopsis of all the functionality provided by
SmartProperties
.
The example below shows how to implement a class called Message
which has
three properties: subject
, body
, and priority
. The two properties,
subject
and priority
, are required whereas body
is optional.
Furthermore, all properties use input conversion. The priority
property also
uses validation and has a default value so if it is not set during initialization
it will be set according to the default value.
require 'rubygems'
require 'smart_properties'
class Message
include SmartProperties
property :subject, :converts => :to_s,
:required => true
property :body, :converts => :to_s
property :priority, :converts => :to_sym,
:accepts => [:low, :normal, :high],
:default => :normal,
:required => true
end
Creating an instance of this class without specifying any attributes will
result in an ArgumentError
telling you to specify the required property
subject
.
Message.new # => raises ArgumentError, "Message requires the property subject to be set"
Providing the constructor with a title but with an invalid value for the
property priority
will also result in an ArgumentError
telling you to
provide a proper value for the property priority
.
m = Message.new :subject => 'Lorem ipsum'
m.priority # => :normal
m.priority = :urgent # => raises ArgumentError, Message does not accept :urgent as value for the property priority
Next, we discuss the various configuration options SmartProperties
provide.
This subsection explains the various configuration options SmartProperties
provide.
To automatically convert a given value for a property, you can use the
:converts
configuration parameter. The parameter can either be a Symbol
or
a lambda
statement. Using a Symbol
will instruct the setter to call the
method identified by this symbol on the object provided as input data and take
the result of this method call as value instead. The example below shows how
to implement a property that automatically converts all given input to a
String
by calling #to_s
on the object provided as input.
class Article
property :title, :converts => :to_s
end
If you need more fine-grained control, you can use a lambda statement to specify how the conversion should be done. The statement will be evaluated in the context of the class defining the property and takes the given value as input. The example below shows how to implement a property that automatically converts all given input to a slug representation.
class Article
property :slug, :converts => lambda { |slug| slug.downcase.gsub(/\s+/, '-').gsub(/\W/, '') }
end
To ensure that a given value for a property is always of a certain type, you
can specify the :accepts
configuration parameter. This will result in an
automatic validation whenever the setter for a certain property is called. The
example below shows how to implement a property which only accepts instances
of type String
as input.
class Article
property :title, :accepts => String
end
Instead of using a class, you can also use a list of permitted values. The
example below shows how to implement a property that only accepts true
or
false
as values.
class Article
property :published, :accepts => [true, false]
end
You can also use a lambda
statement for input validation if a more complex
validation procedure is required. The lambda
statement is evaluated in the
context of the class that defines the property and receives the given value as
input. The example below shows how to implement a property called title that
only accepts values which match the given regular expression.
class Article
property :title, :accepts => lambda { |title| /^Lorem \w+$/ =~ title }
end
There is also support for default values. Simply use the :default
configuration parameter to configure a default value for a certain property.
The example below demonstrates how to implement a property that has 42 as
default value.
class Article
property :id, :default => 42
end
Default values can also be specified using blocks which are evaluated at runtime.
To ensure that a property is always set and never nil
, you can use the
:required
configuration parameter. If present, this parameter will instruct
the setter of a property to not accept nil as input. The example below shows
how to implement a property that may not be nil
.
class Article
property :title, :required => true
end
The decision whether or not a property is required can also be delayed and
evaluated at runtime by providing a block instead of a boolean value. The
example below shows how to implement a class that has two properties, name
and anonoymous
. The name
is only required if anonymous
is set to false
.
class Person
property :name, required: lambda { not anonymous }
property :anonymous, required: true, default: true, accepts: [true, false]
end
- Fork it
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Added some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create new Pull Request