active-model
Spider-Gazelle ActiveModel
Active Model provides a known set of interfaces for usage in model classes. Active Model also helps with building custom ORMs.
Usage
Please also checkout the detailed guide.
Active Model
ActiveModel::Model should be used as the base class for your ORM
require "active-model"
class Person < ActiveModel::Model
attribute name : String = "default value"
attribute age : Int32
end
p = Person.from_json("\"name\": \"Bob Jane\"")
p.name # => "Bob Jane"
p.to_json # => "\"name\":\"Bob Jane\""
p.attributes # => {:name => "Bob Jane", :age => nil}
p.age = 32
p.attributes # => {:name => "Bob Jane", :age => 32}
The attribute
macro takes two parameters. The field name with type and an optional default value.
You can also define enum attributes!
The default serialisation for enums is to a downcased string. Use Enum::ValueConverter(T)
if you want to serialise to the value backing members of the enum.
require "active-model"
class Order < ActiveModel::Model
enum Product
Fries
Burger
end
enum Size
Medium
ExtraMedium
end
attribute product : Product = Product::Fries
attribute size : Size = Size::ExtraMedium, converter: Enum::ValueConverter(Size)
end
Validations
ActiveModel::Validators is a mix-in that you include in your class. Similar to those supported by Rails: http://guides.rubyonrails.org/active_record_validations.html
require "active-model"
class Person < ActiveModel::Model
include ActiveModel::Validation
attribute name : String
attribute age : Int32
validates :name, presence: true, length: { minimum: 3 }
validates :age, presence: true, numericality: {greater_than: 5}
end
The validate
macro takes three parameters. The symbol of the field and the message that will display when the validation fails. The third is a Proc
that is provided an instance of self
and returns either true or false.
To check to see if your instance is valid, call valid?
. Each Proc will be called and if any of them fails, an errors
Array with the messages is returned.
If no Symbol is provided as a first parameter, the errors will be added to the :base
field.
person = Person.new(name: "JD")
person.valid?.should eq false
person.errors[0].to_s.should eq "Name is too short"
Dirty Checking
Changes to attributes are tracked throughout the lifetime of the model. Similar to Rails: http://api.rubyonrails.org/classes/ActiveModel/Dirty.html
person = Person.new(name: "JD")
person.changed? # => true
person.changed_attributes # => {:name => "JD"}
person.name_changed? # => true
person.name_change # => {nil, "JD"}
person.name_was # => nil
person.clear_changes_information
person.changed? # => false
Callbacks
Register before/after callbacks for create
, update
, delete
, save
methods. You must define the method you wish to register callbacks for.
Registered callbacks are invoked through wrapping crud logic with the run_create_callbacks
, run_update_callbacks
, etc. functions
require "active-model"
class Person < ActiveModel::Model
include ActiveModel::Callbacks
attribute name : String
attribute age : Int32
before_save :capitalize
def capitalize
@name = @name.capitalize
end
def save
run_save_callbacks do
# save to database
@foo.save(attributes)
end
end
end
Serialization
The serialization_group
argument to attribute
accepts an Array(Symbol)
or Symbol
.
This will include the attribute in a generated serializer, #to_<group>_json
.
The define_to_json
macro allows for defining subset serializations via only
and except
arguments. The methods
argument allows for inclusion of instance methods in the serializer.
require "active-model"
class SerializationGroups < ActiveModel::Model
attribute everywhere : String = "hi", serialization_group: [:admin, :user, :public]
attribute joined : Int64 = 0, serialization_group: [:admin, :user]
attribute mates : Int64 = 0, serialization_group: :user
attribute another : String = "ok"
define_to_json :some, only: [:joined, :another]
define_to_json :most, except: :everywhere
define_to_json :method, only: :joined, methods: :foo
getter foo = "foo"
end
m = SerializationGroups.new
m.to_public_json # {"everywhere":"hi"}
m.to_admin_json # {"everywhere":"hi","joined":0}
m.to_user_json # {"everywhere":"hi","joined":0,"mates":1}
m.to_some_json # {"joined":0,"another":"ok"}
m.to_most_json # {"joined":0,"mates":0,"another":"ok"}
m.to_method_json # {"joined":0,"foo":"foo"}