2. The Needs
• You have a simple model attribute that
needs additional behavior but don’t want to
create an additional ActiveRecord model
• You need to store structured data in the
same table as your model but treat it as a
first-class, normalized object
3. composed_of
class SomeModel < ActiveRecord::Base
composed_of :some_attribute,
:class_name => 'SomeSpecialClass',
:mapping => [%w(model_attr_name special_class_attr)]
end
“Add some_attribute, composed of SomeSpecialClass,
and map SomeModel’s model_attr_name field to
special_class_attr.”
5. Grade class
class Grade
attr_accessor :letter_grade
def initialize(letter_grade)
@letter_grade = letter_grade
end
end
6. class Grade
include Comparable
attr_accessor :letter_grade
course_records
def initialize(letter_grade)
@letter_grade = letter_grade
id
end
course_id
end
student_id
letter_grade
class CourseRecord < ActiveRecord::Base
composed_of :grade,
:mapping => %w(letter_grade letter_grade)
end
7. Things to remember
• include Comparable and define <=> in
order to compare composed_of classes
8. class Grade
Grade class
include Comparable
SORT_ORDER = ["F", "D", "D", "B", "A"].inject({}) {|h, letter|
h.update "#{letter}-" => h.size
h.update letter => h.size
h.update "#{letter}+" => h.size
}
def <=>(other)
SORT_ORDER[letter_grade.downcase] <=>
SORT_ORDER[other.letter_grade.downcase]
end
end
9. Things to remember
• composed_of objects are value objects and
are immutable!
• Create a new object to change the AR
model’s composed_of attr
10. Structured Data
person class Person < ActiveRecord::Base
id
first_name
composed_of :address, :mapping =>
last_name [ %w(address_street street),
address_street
address_city %w(address_city city),
address_state %w(address_state state),
address_country
email %w(address_country country) ]
end
11. class Address Address class
attr_accessor :street, :city, :state, :country
def initialize(street, city, state, country)
@street = street
@city = city
@state = state
@country = country
end
def full_address
“#{street} #{city}, #{state} #{country}”
end
end
12. The Results
• Keep your Models DRY and put the responsibilities
and behaviors where they belong
• Reuse the composed_of models instead of
repeating methods in multiple classes
• Store structured data flatly but treat it as a first-
class Ruby object
• Don’t garbage-up your models with lots of
“formatting” methods like :full_address