Attrio
Attributes for plain Ruby objects. The goal is to provide an ability to define attributes for your models without reinventing the wheel all over again. Attrio doesn't have any third-party dependencies like Virtus or ActiveAttr and does not redefine any methods inside your class, unless you want it to.
Installation
Add this line to your application's Gemfile:
gem 'attrio'
And then execute:
$ bundle
Or install it yourself as:
$ gem install attrio
Usage
Include Attrio into your class and then use #define_attributes
block to declare you attributes.
class User
include Attrio
define_attributes do
attr :name, String
attr :age, Integer
attr :birthday, DateTime
end
end
Accessing attributes
By default Attrio defines #attributes
accessor which contains Hash
with attributes names as keys and instances of Attrio::Attribute
as values. Each instance of Attrio::Attribute
contains following information:
- type
- writer method name
- writer method visibility
- reader method name
- reader method visibility
- instance variable name
- additional options
user = User.new
user.attributes
# => {
# :name => #<Attrio::Attribute:0x007fc44e8ca680 @object=#<User:0x007fc44e8b2b48>, @name="name", @type=String, @options={}, @writer_method_name="name=", @writer_visibility=:public, @instance_variable_name="@name", @reader_method_name="name", @reader_visibility=:public>,
# :age => #<Attrio::Attribute:0x007fc44e8d4c98 @object=#<User:0x007fc44e8b2b48>, @name="age", @type=Attrio::Types::Integer, @options={}, @writer_method_name="age=", @writer_visibility=:public, @instance_variable_name="@age", @reader_method_name="age", @reader_visibility=:public>,
# :birthday = >#<Attrio::Attribute:0x007fc44e8e2e38 @object=#<User:0x007fc44e8b2b48>, @name="birthday", @type=Attrio::Types::DateTime, @options={}, @writer_method_name="birthday=", @writer_visibility=:public, @instance_variable_name="@birthday", @reader_method_name="birthday", @reader_visibility=:public>
# }
user.attributes.keys
# => [:name, :age, :birthday]
Attributes can be filtered.
user.attributes([:name, :age, :not_existing_attribute]).keys
# => [:name, :age]
Accessor name can be easily overridden by passing :as
option to define_attributes
block.
class User
include Attrio
define_attributes :as => 'api_attributes' do
attr :name, String
attr :age, Integer
attr :birthday, DateTime
end
define_attributes :as => 'settings' do
attr :receives_notifications, Boolean, :default => true
end
end
user = User.new
user.api_attributes # => {...}
Default values
Attrio supports all the ways to setup default values that Virtus has.
class Page
include Attrio
define_attributes do
attr :title, String
# default from a singleton value (integer in this case)
attr :views, Integer, :default => 0
# default from a singleton value (boolean in this case)
attr :published, Boolean, :default => false
# default from a callable object (proc in this case)
attr :slug, String, :default => lambda { |page, attribute| page.title.present? ? page.title.downcase.gsub(' ', '-') : nil }
# default from a method name as symbol
attr :editor_title, String, :default => :default_editor_title
end
def initialize(attributes = {})
self.attributes = attributes
end
def attributes=(attributes = {})
attributes.each do |attr,value|
self.send("#{attr}=", value) if self.respond_to?("#{attr}=")
end
end
def default_editor_title
if self.published?
title
else
title.present? ? "UNPUBLISHED: #{title}" : "UNPUBLISHED"
end
end
end
You can does your attribute still has default value or not.
p = Page.new
=> #<Page title: nil, views: 0, published: false, slug: nil, editor_title: "UNPUBLISHED">
p.attributes[:editor_title].default?
=> true
p.editor_title = 'PUBLISHED'
=> "PUBLISHED"
p.attributes[:editor_title].default?
=> false
Embed Value
You can embed values in Attrio just like you do it in Virtus.
module MassAssignment
def initialize(attributes = {})
self.attributes = attributes
end
def attributes=(attributes = {})
attributes.each do |attr,value|
self.send("#{attr}=", value) if self.respond_to?("#{attr}=")
end
end
end
class City
include Attrio
include MassAssignment
define_attributes do
attr :name, String
end
end
class Address
include Attrio
include MassAssignment
define_attributes do
attr :street, String
attr :zipcode, String
attr :city, City
end
end
class User
include Attrio
include MassAssignment
define_attributes do
attr :name, String
attr :address, Address
end
end
user = User.new( :address => { :street => 'Sklizkova 6A', :zipcode => '170000', :city => { :name => 'Tver' } } )
user.address.street
# => 'Sklizkova 6A'
user.address.zipcode
# => '170000'
user.address.city.name
# => 'Tver'
Methods visibility
Don't want your accessors to be public? Visibility can be overridden easily.
class User
include Attrio
define_attributes do
attr :name, String, :writer => :protected
attr :secret_rating, Integer, :reader => :private
end
end
Types
Any Ruby class can be passed as type to Attrio. If this class responds to typecast
and typecasted?
methods then they will be called, else new
will be called.
class Klass
include Attrio
define_attributes do
attr :custom_attribute, CustomClass
end
end
Built-in Types
Boolean
By default boolean typecasts 'yes', '1', 1, 'true' as TrueClass
and all other values as FalseClass
, but you easily modify this behaviour.
class Klass
include Attrio
define_attributes do
attr :boolean_attribute, Boolean
attr :custom_boolean_attribute, Boolean, :yes => ['ja', '1', 1]
# attr :custom_boolean_attribute, Boolean, :yes_values => ['ja', '1', 1]
# attr :custom_boolean_attribute, Boolean, :no => ['nein', '0', 0]
# attr :custom_boolean_attribute, Boolean, :no_values => ['nein', '0', 0]
end
end
Date, Time and DateTime
These three class have similar behaviour and options. By passing :format
option you can setup how strftime
method will try to parse your string.
class Klass
include Attrio
define_attributes do
attr :date_attribute, Date
attr :time_attribute, Time
attr :date_time_attribute, DateTime
attr :custom_date_time_attribute, DateTime, :format => '%m/%d/%y-%H:%M:%S-%z'
end
end
Float
Attribute will be typecasted using to_f
method.
class Klass
include Attrio
define_attributes do
attr :float_attribute, Float
end
end
Integer
Attribute will be typecasted using to_i
method.
Optional :base
parameter can be passed, during the typecast attribute will be assumed to be in specified base and will always be translated to decimal base.
class Klass
include Attrio
define_attributes do
attr :integer_attribute, Integer
attr :custom_integer_attribute, Integer, :base => 2
end
end
Symbol
Attribute will be typecasted using to_sym
method.
If Optional :underscore
parameter is passed, then attribute value will be downcased and underscored before calling to_sym
.
class Klass
include Attrio
define_attributes do
attr :symbol_attribute, Symbol
attr :custom_symbol_attribute, Symbol, :underscore => true
end
end
Array
Arrays are designed to automatically handle collections of objects (that also can be typecasted)
If value that should be typecasted responds to split
, then this method is called with default (or overriden) attributes, else Array
is wrapped on value. You can easily handle types and options of collection elements.
class Klass
include Attrio
define_attributes do
attr :array_attribute, Array
attr :custom_array_attribute, Array, :split => ', ', :element => { :type => Date, :options => { :format => '%m/%d/%y' } }
end
end
Inspect
Attrio adds its own #inspect
method when included to the class. This overridden method prints object attributes in easy to read manner. To disable this feature pass :inspect => false
to define_arguments
block.
class Klass
include Attrio
define_attributes :inspect => false do
attr :attribute, String
end
end
Note on Patches / Pull Requests
- Fork the project.
- Make your feature addition or bug fix.
- Add tests for it. This is important so I don't break it in a future version unintentionally.
- Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
- Send me a pull request. Bonus points for topic branches.
Credits
Webmaster is maintained by JetRockets.
Contributors:
License
It is free software, and may be redistributed under the terms specified in the LICENSE file.