From 760f78bc9b9a9f22492ffbcb794abbd80d2a0d4f Mon Sep 17 00:00:00 2001 From: bolpin Date: Sat, 5 Apr 2014 23:29:32 -0700 Subject: [PATCH 01/14] added tags to .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 0702df0..d9dda18 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ spec/reports test/tmp test/version_tmp tmp +tags .idea/.name .idea/.rakeTasks .idea/dictionaries/bsundarraj.xml @@ -24,4 +25,4 @@ tmp .idea/plain_old_model.iml .idea/scopes/scope_settings.xml .idea/vcs.xml -.idea/workspace.xml \ No newline at end of file +.idea/workspace.xml From fce3552e7c5de79ca522e31b3db404b817d5b5b8 Mon Sep 17 00:00:00 2001 From: bolpin Date: Sat, 5 Apr 2014 23:30:40 -0700 Subject: [PATCH 02/14] started to clean up README file --- README.md | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index f406b34..6406e0b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,12 @@ # PlainOldModel -TODO: Write a gem description +Implements attribute assignment and basic associations for ActiveModel, and also pulls in: + +ActiveModel::Naming +ActiveModel::Translation +ActiveModel::Validations +ActiveModel::Conversion + ## Installation @@ -20,18 +26,13 @@ Or install it yourself as: Example ======= - class Person < PlainOldModel::Base - attr_accessor :name, :age, :book - - attr_reader :account_number - attr_writer :address - - validates_presence_of :book - end +class Person < PlainOldModel::Base + attr_accessor :name, :age, :book + validates_presence_of :book +end - params = {"name" =>"testmeparams", "age" => "25", "book" =>["wewrwrwr", "werwrwrr"]} +params = {"name" =>"Leo", "age" => "25", "book" =>["War and Peace", "Tolstoy"]} - params1 = {:name =>"testmeparams", :age => "25", :book => {:author =>"my name", :category => "fiction"}} p = Person.new(params) @@ -43,7 +44,7 @@ p.valid? #true p = Person.new() -p.assign_attributes(params11) +p.assign_attributes {:name =>"Leo", :age => "25", :book => {:author =>"Tolstoy", :category => "fiction"}} ===================================================================== p1 = Person.new(params1) @@ -55,9 +56,7 @@ p.assign_attributes(params11) TODO: -* Association(s) -* mass assignments -* +- mass assignments ## Contributing From 48c971882d1b8e2fcc74f6b1afac017a3f69e280 Mon Sep 17 00:00:00 2001 From: bolpin Date: Sat, 5 Apr 2014 23:31:01 -0700 Subject: [PATCH 03/14] whitespace --- examples/book.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/book.rb b/examples/book.rb index 4b9dd81..68ef8cd 100644 --- a/examples/book.rb +++ b/examples/book.rb @@ -1,6 +1,7 @@ require_relative '../lib/plain_old_model/base' + class Book < PlainOldModel::Base attr_accessor :author, :category -end \ No newline at end of file +end From 5bc67ed892e189013bb65443735629f6d8e51938 Mon Sep 17 00:00:00 2001 From: bolpin Date: Sat, 5 Apr 2014 23:36:25 -0700 Subject: [PATCH 04/14] broke Association stuff out of the AttributeAssignment module; refactored long methods; cleaned up tests --- lib/plain_old_model/associations.rb | 106 ++++++++++ lib/plain_old_model/attribute_assignment.rb | 119 ++---------- lib/plain_old_model/base.rb | 4 +- spec/assignment_spec.rb | 50 +++++ spec/associations_spec.rb | 202 ++++++++++++++++++++ spec/lib/base_spec.rb | 178 +---------------- spec/spec_helper.rb | 40 ++++ 7 files changed, 418 insertions(+), 281 deletions(-) create mode 100644 lib/plain_old_model/associations.rb create mode 100644 spec/assignment_spec.rb create mode 100644 spec/associations_spec.rb diff --git a/lib/plain_old_model/associations.rb b/lib/plain_old_model/associations.rb new file mode 100644 index 0000000..19f7037 --- /dev/null +++ b/lib/plain_old_model/associations.rb @@ -0,0 +1,106 @@ +module PlainOldModel + module Associations + module ClassMethods + def has_one(attr_name, options={}) + associations << HasOneAssociation.new(attr_name, options) + attr_accessor attr_name + end + + def has_many(attr_name, options={}) + associations << HasManyAssociation.new(attr_name, options) + attr_accessor attr_name + end + + def associations + @associations ||= [] + end + + end + + def self.included(klass) + klass.extend ClassMethods + end + + def associations + self.class.associations + end + + class Association + attr_reader :attr_name + + def initialize(attr_name, options) + @attr_name = attr_name + @options = options + end + + def create_value_from_attributes(attributes) + + if @options[:factory_method] + klass.send(@options[:factory_method], attributes) + else + klass.new(attributes) + end + end + + def klass + if @options[:class_name] + @options[:class_name].to_s.camelcase.constantize + else + klass_from_attr_name + end + end + + def klass_from_attr_name + @attr_name.to_s.camelcase.constantize + end + end + + class HasOneAssociation < Association + end + + class HasManyAssociation < Association + def create_value_from_attributes(items) + items.map{|item| super(item)} + end + + def klass_from_attr_name + @attr_name.to_s.singularize.camelcase.constantize + end + end + + private + + def merge_association_instance_variables_with_attributes(association, attr_name, attributes) + association_instance = send(attr_name) + + if association.class == HasOneAssociation + merged_hash = instance_hash(association_instance).deep_merge(attributes[attr_name]) + elsif association.class == HasManyAssociation + association_instance_array = [] + if association_instance.nil? + merged_hash = attributes[attr_name] + else + association_instance.each_with_index do |instance, i| + association_instance_array << instance_hash(instance).deep_merge(attributes[attr_name][i]) + end + merged_hash = association_instance_array + end + end + end + + def instance_hash(association_instance) + hash = HashWithIndifferentAccess.new + unless association_instance.nil? + association_instance.instance_variables.each do |var| + if association_instance.instance_variable_get(var).instance_variables.length > 0 + hash[var.to_s.delete("@")] = instance_hash(association_instance.instance_variable_get(var)) + else + hash[var.to_s.delete("@")] = association_instance.instance_variable_get(var) + end + end + end + hash + end + + end +end diff --git a/lib/plain_old_model/attribute_assignment.rb b/lib/plain_old_model/attribute_assignment.rb index 6837c74..fa02961 100644 --- a/lib/plain_old_model/attribute_assignment.rb +++ b/lib/plain_old_model/attribute_assignment.rb @@ -1,104 +1,45 @@ -require 'active_support/all' +require 'active_support/core_ext/hash/deep_merge' +require 'active_support/core_ext/hash/indifferent_access' module PlainOldModel module AttributeAssignment - module ClassMethods - def has_one(attr_name, options={}) - associations << HasOneAssociation.new(attr_name, options) - attr_accessor attr_name - end - - def has_many(attr_name, options={}) - associations << HasManyAssociation.new(attr_name, options) - attr_accessor attr_name - end - - def associations - @associations ||= [] - end + def assign_attributes(new_attributes, options = {}) + return unless new_attributes + attributes = new_attributes.dup + assign_attributes_from_associations(attributes) + assign_simple_attributes(attributes) end - def self.included(klass) - klass.extend ClassMethods + def attributes=(new_attributes) + assign_attributes(new_attributes) end - def assign_attributes(new_attributes, options = {}) - return unless new_attributes - attributes = new_attributes.dup + private + def assign_attributes_from_associations(attributes) associations.each do |association| attr_name = association.attr_name if attributes.include?(attr_name) merged_hash = merge_association_instance_variables_with_attributes(association, attr_name, attributes) value = association.create_value_from_attributes(merged_hash) set_attribute(attr_name, value) - attributes = attributes.delete_if { |key, value| key.to_s == attr_name.to_s } + attributes.delete_if { |key, value| key.to_s == attr_name.to_s } end end - assign_simple_attributes(attributes, options) - end - - def associations - self.class.associations end - class Association - attr_reader :attr_name - - def initialize(attr_name, options) - @attr_name = attr_name - @options = options - end - - def create_value_from_attributes(attributes) - if @options[:factory_method] - klass.send(@options[:factory_method], attributes) - else - klass.new(attributes) - end - end - - def klass - if @options[:class_name] - @options[:class_name].to_s.camelcase.constantize - else - klass_from_attr_name - end - end - - def klass_from_attr_name - @attr_name.to_s.camelcase.constantize - end - end - - class HasOneAssociation < Association - - end - - class HasManyAssociation < Association - def create_value_from_attributes(items) - items.map{|item| super(item)} - end - - def klass_from_attr_name - @attr_name.to_s.singularize.camelcase.constantize - end - end - - private - def sanitize_attributes(attributes) sanitized_attributes = {} attributes.each do |k, v| if respond_to?("#{k}=") || respond_to?("#{k}") - sanitized_attributes[k]=v + sanitized_attributes[k] = v end end sanitized_attributes end - def assign_simple_attributes(attributes, options) + def assign_simple_attributes(attributes) attributes = sanitize_attributes(attributes).stringify_keys attributes.each do |k, v| @@ -114,37 +55,5 @@ def set_attribute(attr_name, value) end end - def merge_association_instance_variables_with_attributes(association, attr_name, attributes) - association_instance = send(attr_name) - if association.class == HasOneAssociation - instance_hash = create_association_hash(association_instance,HashWithIndifferentAccess.new) - merged_result = instance_hash.deep_merge(attributes[attr_name]) - elsif association.class == HasManyAssociation - association_instance_array = [] - if association_instance.nil? - merged_result = attributes[attr_name] - else - for i in 0..association_instance.length-1 - instance_hash = create_association_hash(association_instance[i],HashWithIndifferentAccess.new) - association_instance_array << instance_hash.deep_merge(attributes[attr_name][i]) - end - merged_result = association_instance_array - end - end - merged_result - end - - def create_association_hash(association_instance,association_instance_hash) - unless association_instance.nil? - association_instance.instance_variables.each do |var| - if association_instance.instance_variable_get(var).instance_variables.length > 0 - association_instance_hash[var.to_s.delete("@")] = create_association_hash(association_instance.instance_variable_get(var),HashWithIndifferentAccess.new) - else - association_instance_hash[var.to_s.delete("@")] = association_instance.instance_variable_get(var) - end - end - end - association_instance_hash - end end end diff --git a/lib/plain_old_model/base.rb b/lib/plain_old_model/base.rb index 7ddcf6c..a7ae2af 100644 --- a/lib/plain_old_model/base.rb +++ b/lib/plain_old_model/base.rb @@ -3,6 +3,7 @@ require 'active_model/validations' require 'active_model/conversion' require 'plain_old_model/attribute_assignment' +require 'plain_old_model/associations' module PlainOldModel class Base @@ -11,6 +12,7 @@ class Base include ActiveModel::Validations include ActiveModel::Conversion include PlainOldModel::AttributeAssignment + include PlainOldModel::Associations def initialize(attributes = {}, options = {}) assign_attributes(attributes, options) if attributes @@ -22,4 +24,4 @@ def persisted? end -end \ No newline at end of file +end diff --git a/spec/assignment_spec.rb b/spec/assignment_spec.rb new file mode 100644 index 0000000..51914bd --- /dev/null +++ b/spec/assignment_spec.rb @@ -0,0 +1,50 @@ +require 'spec_helper' + +describe PlainOldModel::Base do + + describe "creating a new model" do + + it "accepts the params and instantiate with variables initialized" do + @person= Person.new( + {:fname => "Tom", + :address => { + :city => 'Oso', + :state => 'WA' + } + }) + @person.fname.should == "Tom" + @person.address.should == {:city => 'Oso', :state => 'WA'} + end + + it "eliminates the params that are not available in the class" do + @person= Person.new({:fname => "Tom", :foo => "bar"}) + @person.valid?.should == true + @person.instance_variable_get(:@foo).should == nil + end + + it "supports activemodel validations" do + @person= Person.new({:lname => "Jobim"}) + @person.valid?.should == false + @person.errors.should_not == nil + end + + end + + describe "assigning attributes to an existing model" do + before(:each) do + @address = Address.new + @address.assign_attributes({:state => "WA"}) + @address.attributes = {:city => "Oakland"} + end + + it "assigns value to the attr_reader attributes/ read only attribute" do + @address.state.should == "WA" + end + + it "supports the assignment operator syntax" do + @address.city.should == "Oakland" + end + + end +end + diff --git a/spec/associations_spec.rb b/spec/associations_spec.rb new file mode 100644 index 0000000..9d29f74 --- /dev/null +++ b/spec/associations_spec.rb @@ -0,0 +1,202 @@ +require 'spec_helper' + +describe PlainOldModel::Base do + + describe "associations" do + + it "returns empty array for unassociated class" do + continent = Continent.new + continent.associations.should == [] + end + + it "provides all the associations when the class has associations" do + address = Address.new + address.associations.length.should == 1 + address.associations.first.attr_name.should == :country + end + + describe "a has_one association" do + before(:each) do + @address = Address.new( + { + :city => "Mumbai", + :state => "Maharashtra", + :country => { + :code => "In", :name => "India", + :continent => {:name => "Asia", :desc => {:actual_desc => "big"}}}}) + end + + it "assigns attributes to the associated class" do + @address.country.class.should == Country + end + + it "creates the nested class instance and assign_attributes to the associated nested class" do + @address.country.continent.class.should == Continent + end + + it "creates a new instance and assign_attributes to the associated class" do + @address.country.continent.name.should == "Asia" + end + + it "assigns values to the read only attributes" do + address = Address.new( + { + :city => "Mumbai", + :state => "Maharashtra", + :country => {:code => "In", :name => "India"}, + :readable => 'yes' + }) + address.readable.should == "yes" + end + + it "overrides assigned attributes" do + address = Address.new( + { + :city => "Mumbai", + :country => {:code => "In", :name => "India"}, + :readable => 'should be assigned'}) + address.assign_attributes( + { + :city => "Oso", + :country => {:code => "USA", :name => "United States"} + }) + address.city.should == "Oso" + address.country.code.should == "USA" + address.country.name.should == "United States" + address.readable.should == "should be assigned" + end + + it "should not overwrite unassigned nested attributes" do + address = Address.new( + { + :city => "Mumbai", + :state => "Maharashtra", + :country => {:code => "In", :name => "India"}}) + address.assign_attributes({:city => "Bangalore"}) + address.city.should == "Bangalore" + address.country.code.should == "In" + address.country.name.should == "India" + end + + it "should not overwrite unassigned nested attributes' values" do + address = Address.new( + { + :city => "Mumbai", + :country => {:code => "In", :name => "India", :continent => {:name => "asia" }}, + :readable => 'yes'}) + address.assign_attributes( + {"city" => "Oso", + :country => {:name => "United States", :continent => {:desc => {:text => "large landmass"}}}}) + address.city.should == "Oso" + address.country.code.should == "In" + address.country.name.should == "United States" + address.country.continent.name.should == "asia" + address.readable.should == "yes" + end + + it "creates assigned nested attributes" do + address = Address.new({:state => "AK", :readable => 'should be assigned'}) + address.assign_attributes({"city" => "Oso", :country => {:code => "In", :name => "India"} }) + address.city.should == "Oso" + address.country.code.should == "In" + address.country.name.should == "India" + address.readable.should == "should be assigned" + end + + it "should assigned nested attributes with mixed string and symbol hash keys" do + address = Address.new( + { + :city => "Seattle", + :country => {:code => "US", :name => "USA"}, + :readable => 'should be assigned' + }) + address.attributes = {:city => "Mumbai", :country => {"code" => "In", "name" => "India"} } + address.city.should == "Mumbai" + address.country.code.should == "In" + address.country.name.should == "India" + address.readable.should == "should be assigned" + end + + end + + describe "construction of a model with a has_many association" do + + it "should create a new instance and assign attributes for each value in array" do + person = Person.new({ addresses: [{ city: "Oso", state: "WA"}, + { city: "Oakland", state: "CA"}]}) + person.addresses.first.class.should == Address + person.addresses.first.city.should == "Oso" + person.addresses.first.state.should == "WA" + person.addresses[1].class.should == Address + person.addresses[1].city.should == "Oakland" + person.addresses[1].state.should == "CA" + end + end + + describe "assignment to a model with a has_many association" do + + + it "should not alter the params passed in" do + passed_params = { addresses: [ + { :city => "Oso", :state => "WA"}, + { :city => "Oakland", :state => "CA"} + ]} + person = Person.new(passed_params) + person.addresses.length.should == 2 + person.addresses.first.class.should == Address + person.addresses.first.city.should == "Oso" + passed_params.should == { addresses: [ + { :city => "Oso", :state => "WA"}, + { :city => "Oakland", :state => "CA"} + ]} + end + + it "should create each class via factory_method if one is specified" do + person = Person.new({ phones: [{ number: '5841'}, {number: '9139'}]}) + person.phones.length.should == 2 + person.phones[0].number.should == '5841' + person.phones[0].extension.should == 'set_via_factory' + person.phones[1].extension.should == 'set_via_factory' + end + end + + describe "mass-assignment to a model with a has_many association" do + before(:all) do + @person = Person.new({addresses: [ + { + :state => "Gujarat", + :country => { + :continent => {:name => "Asia"}} + }, + { + :state => "AK", + }]}) + + @person.attributes = {addresses: [ + { + :city => "Surat", + :country => { + :name => "India", + :continent => {:desc => "large"} + } + }, + { + state: "CA" + }]} + end + + it "should overwrite assigned nested attributes' values" do + @person.addresses.last.state.should == "CA" + @person.addresses.first.country.name.should == "India" + end + + it "should not affect unassigned nested attributes' values" do + @person.addresses.first.state.should == "Gujarat" + @person.addresses.first.country.continent.name.should == "Asia" + end + + end + end +end + + diff --git a/spec/lib/base_spec.rb b/spec/lib/base_spec.rb index 98ab31e..54eef9c 100644 --- a/spec/lib/base_spec.rb +++ b/spec/lib/base_spec.rb @@ -2,184 +2,12 @@ describe PlainOldModel::Base do - describe " assign_attribute and new" do + describe "assign_attribute and new" do + it "should accept the params and new up a class with variables initialized" do @person= Person.new({:fname => "first value", :lname => "second value", :address => {:fname => 'fname', :lname => 'lname'}}) - @person.fname.should == "first value" - @person.lname.should == "second value" - @person.address.should == {:fname => 'fname', :lname => 'lname'} - end - it "should assign value to the attr_reader attributes/ read only attribute" do - @address = Address.new - @address.fname = "first value" - @address.assign_attributes({:lname => "second value", :read_test => 'This should be assigned'}) - @address.fname.should == "first value" - @address.read_test.should == "This should be assigned" - end - it "should assign value to the attr_writer attributes" do - @address = Address.new - @address.assign_attributes({:fname => "first value", :lname => "second value", :read_test => 'This should not be assigned',:write_test => "this shd be available"}) - @address.instance_variable_get(:@write_test).should == "this shd be available" - end - it "should assign_attributes to the class" do - @person = Person.new - @person.assign_attributes({:fname => "first value", :lname => "second value"}) - @person.fname.should == "first value" - @person.lname.should == "second value" - end - it "should eliminate the params that are not available in the class" do - @person= Person.new({:fname => "first value", :lname => "second value"}) - @person.valid?.should == true - end - it "should allow the class to use activemodel validations and errors" do - @person= Person.new({:lname => "second value"}) - @person.valid?.should == false - @person.errors.should_not == nil - end - end - - describe "associations" do - it "should return empty array for unassociated class" do - @person = Continent.new - @person.associations.should == [] - end - it "should provide all the associations when the class has associations" do - @address = Address.new - @address.associations.length.should == 1 - @address.associations.first.attr_name.should == :country - end - describe "has_one" do - it "should create a new instance and assign_attributes to the associated class" do - @address = Address.new({:fname => "first value", :lname => "second value", :country => {:code => "In", :name => "India"}, :read_test => 'This should not be assigned',:write_test => "this shd be available"}) - @address.country.class.should == Country - end - it "should create the nested class instance and assign_attributes to the associated nested class" do - @address = Address.new({:fname => "first value", :lname => "second value", :country => {:code => "In", :name => "India", :continent => {:name => "asia"}}, :read_test => 'This should not be assigned',:write_test => "this shd be available"}) - @address.country.continent.class.should == Continent - end - it "should create a new instance and assign_attributes to the associated class" do - @address = Address.new({:fname => "first value", :lname => "second value", :country => {:code => "In", :name => "India", :continent => {:name => "asia", :desc => {:this => "is a test", :actual_desc => "is another test"}}}, :read_test => 'This should not be assigned',:write_test => "this shd be available"}) - @address.country.continent.class.should == Continent - @address.country.continent.name.should == "asia" - @continent = @address.country.continent - @continent.name.should == "asia" - end - it "should assign values to the read only attributes" do - @address = Address.new({:fname => "first value", :lname => "second value", :country => {:code => "In", :name => "India"}, :read_test => 'This should be assigned',:write_test => "this shd be available"}) - @address.read_test.should == "This should be assigned" - end - it "should override assigned attributes" do - @address = Address.new({:fname => "first value", :lname => "second value", :country => {:code => "In", :name => "India"}, :read_test => 'This should be assigned',:write_test => "this shd be available"}) - @address.assign_attributes({:fname => "replaced first value", :lname => "replaced second value", :country => {:code => "USA", :name => "United States"}}) - @address.fname.should == "replaced first value" - @address.country.code.should == "USA" - @address.country.name.should == "United States" - @address.read_test.should == "This should be assigned" - end - it "should not override unassigned nested attributes" do - @address = Address.new({:fname => "first value", :lname => "second value", :country => {:code => "In", :name => "India"}, :read_test => 'This should be assigned',:write_test => "this shd be available"}) - @address.assign_attributes({:fname => "replaced first value", :lname => "replaced second value"}) - @address.fname.should == "replaced first value" - @address.country.code.should == "In" - @address.country.name.should == "India" - @address.read_test.should == "This should be assigned" - end - it "should not override unassigned nested attributes' values" do - @address = Address.new({:fname => "first value", :lname => "second value", :country => {:code => "In", :name => "India", :continent => {:name => "asia", :desc => {:this => "is a test", :actual_desc => "is another test"}}}, :read_test => 'This should be assigned',:write_test => "this shd be available"}) - @address.assign_attributes({"fname" => "replaced first value", :lname => "replaced second value", :country => {:name => "United States", :continent => {:desc => {:this => "is a replacement", :actual_desc => "is another replacement"}}}}) - @address.fname.should == "replaced first value" - @address.country.code.should == "In" - @address.country.name.should == "United States" - @address.country.continent.name.should == "asia" - @address.read_test.should == "This should be assigned" - end - it "should create assigned nested attributes" do - @address = Address.new({:lname => "second value", :read_test => 'This should be assigned',:write_test => "this shd be available"}) - @address.assign_attributes({"fname" => "first value", :lname => "replaced second value", :country => {:code => "In", :name => "India"} }) - @address.fname.should == "first value" - @address.country.code.should == "In" - @address.country.name.should == "India" - @address.read_test.should == "This should be assigned" - end - it "should assigned nested attributes with mixed string and symbol hash keys" do - @address = Address.new({:fname => "first value", :lname => "second value", :country => {:code => "", :name => ""}, :read_test => 'This should be assigned',:write_test => "this shd be available"}) - @address.assign_attributes({:fname => "replaced first value", :lname => "replaced second value", :country => {"code" => "In", "name" => "India"} }) - @address.fname.should == "replaced first value" - @address.country.code.should == "In" - @address.country.name.should == "India" - @address.read_test.should == "This should be assigned" - end + @person.persisted?.should == false end - describe "has_many" do - it "should create a new instance and assign attributes for each value in array" do - @person = Person.new({ addresses: [{ fname: "first name 1", lname: "last name 1"}, { fname: "first name 2", lname: "last name 2"}]}) - @person.addresses.length.should == 2 - @person.addresses.first.class.should == Address - @person.addresses.first.fname.should == "first name 1" - @person.addresses.first.lname.should == "last name 1" - @person.addresses[1].class.should == Address - @person.addresses[1].fname.should == "first name 2" - @person.addresses[1].lname.should == "last name 2" - end - it "should not alter the params passed in" do - passed_params = { addresses: [{ fname: "first name 1", lname: "last name 1"}, { fname: "first name 2", lname: "last name 2"}]} - @person = Person.new(passed_params) - @person.addresses.length.should == 2 - @person.addresses.first.class.should == Address - @person.addresses.first.fname.should == "first name 1" - passed_params.should == { addresses: [{ fname: "first name 1", lname: "last name 1"}, { fname: "first name 2", lname: "last name 2"}]} - end - it "should create each class via factory_method if one is specified" do - @person = Person.new({ phones: [{ number: '423-5841'}, {number: '383-9139'}]}) - @person.phones.length.should == 2 - @person.phones[0].number.should == '423-5841' - @person.phones[0].extension.should == 'set_via_factory' - @person.phones[1].extension.should == 'set_via_factory' - end - it "should not override unassigned nested attributes' values" do - @person = Person.new({ addresses: [{ fname: "first name 1", lname: "last name 1", :country => {:code => "In", :name => "India", :continent => {:name => "asia", :desc => {:this => "is a test", :actual_desc => "is another test"}}}}, { fname: "first name 2", lname: "last name 2"}]}) - @person.assign_attributes({ addresses: [{ fname: "first name 1", :country => {:name => "United States", :continent => {:desc => {:this => "is a replacement", :actual_desc => "is another replacement"}}}}, { fname: "first name 2", lname: "last name 2"}]}) - @person.addresses.first.lname.should == "last name 1" - @person.addresses.last.lname.should == "last name 2" - @person.addresses.first.country.name.should == "United States" - @person.addresses.first.country.continent.name.should == "asia" - end - end end end - -class Person < PlainOldModel::Base - attr_accessor :fname, :lname, :address - has_many :addresses - has_many :phones, factory_method: :create - validates_presence_of :fname -end - -class Phone < PlainOldModel::Base - attr_accessor :number, :extension - - def self.create(attributes) - attributes[:extension] = 'set_via_factory' - new(attributes) - end -end - -class Address < PlainOldModel::Base - attr_accessor :fname, :lname - attr_reader :read_test - attr_writer :write_test - - has_one :country -end - -class Country < PlainOldModel::Base - attr_accessor :code, :name - - has_one :continent -end - -class Continent < PlainOldModel::Base - attr_accessor :name, :desc -end - diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index e1bbb26..ae1ce10 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -4,6 +4,10 @@ # loaded once. # # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration + +require 'simplecov' +SimpleCov.start + require 'plain_old_model' require 'plain_old_model/base' require 'plain_old_model/version' @@ -20,3 +24,39 @@ # --seed 1234 config.order = 'random' end + + +class Telephone < PlainOldModel::Base + attr_accessor :number, :extension + + def self.create(attributes) + attributes[:extension] = 'set_via_factory' + new(attributes) + end +end + +class Person < PlainOldModel::Base + attr_accessor :fname, :lname, :address + has_many :addresses + has_many :phones, factory_method: :create, class_name: :telephone + validates_presence_of :fname +end + +class Address < PlainOldModel::Base + attr_accessor :city, :state + attr_reader :readable + attr_writer :writable + + has_one :country +end + +class Country < PlainOldModel::Base + attr_accessor :code, :name + + has_one :continent +end + +class Continent < PlainOldModel::Base + attr_accessor :name, :desc +end + From 79fa3f3d548fc5283ed5cc2d1d72bb35021c3e7a Mon Sep 17 00:00:00 2001 From: bolpin Date: Sun, 6 Apr 2014 11:01:24 -0700 Subject: [PATCH 05/14] updating readme --- README.md | 66 +++++++++++++++++++------------------------------------ 1 file changed, 23 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 6406e0b..328b070 100644 --- a/README.md +++ b/README.md @@ -1,67 +1,47 @@ -# PlainOldModel +# PlainOldModel # -Implements attribute assignment and basic associations for ActiveModel, and also pulls in: +Implements nested attribute mass-assignment and has_many and has_one associations, and also pulls in: -ActiveModel::Naming -ActiveModel::Translation -ActiveModel::Validations -ActiveModel::Conversion +* ActiveModel::Naming +* ActiveModel::Translation +* ActiveModel::Validations +* ActiveModel::Conversion -## Installation +## Installation ## Add this line to your application's Gemfile: gem 'plain_old_model' -And then execute: +## Usage ## - $ bundle + class Person < PlainOldModel::Base + attr_accessor :name + validates_presence_of :book + end -Or install it yourself as: + params = {"name" =>"Leo", :book => {:author =>"Tolstoy", :category => "fiction"}} - $ gem install plain_old_model +## Initialization ## -## Usage -Example -======= + p = Person.new(params) -class Person < PlainOldModel::Base - attr_accessor :name, :age, :book - validates_presence_of :book -end + p.valid? #true -params = {"name" =>"Leo", "age" => "25", "book" =>["War and Peace", "Tolstoy"]} +## Mass Assignment ## -p = Person.new(params) - -p.book # ["wewrwrwr", "werwrwrr"] + p.assign_attributes({:name =>"Leo", :age => "25", :book => {:author =>"Tolstoy", :category => "fiction"}}) -p.valid? #true +or - OR - -p = Person.new() + p.attributes = {:name =>"Fyodor", :book => {:author =>"Tolstoy", :category => "fiction"}} -p.assign_attributes {:name =>"Leo", :age => "25", :book => {:author =>"Tolstoy", :category => "fiction"}} -===================================================================== - p1 = Person.new(params1) +## Associations ## - p1.book # {:author =>"my name", :category => "fiction"} +* has_one +* has_many - p.attributes #[:name, :age, :book, :account_number, :address] - -TODO: - -- mass assignments - -## Contributing - -1. Fork it -2. Create your feature branch (`git checkout -b my-new-feature`) -3. Commit your changes (`git commit -am 'Add some feature'`) -4. Push to the branch (`git push origin my-new-feature`) -5. Create new Pull Request From ac014b000f3fa12265409304fca8fa513d49c4b1 Mon Sep 17 00:00:00 2001 From: bolpin Date: Sun, 6 Apr 2014 11:02:27 -0700 Subject: [PATCH 06/14] aliasing assignment method --- lib/plain_old_model/attribute_assignment.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/plain_old_model/attribute_assignment.rb b/lib/plain_old_model/attribute_assignment.rb index fa02961..dfd2796 100644 --- a/lib/plain_old_model/attribute_assignment.rb +++ b/lib/plain_old_model/attribute_assignment.rb @@ -11,9 +11,7 @@ def assign_attributes(new_attributes, options = {}) assign_simple_attributes(attributes) end - def attributes=(new_attributes) - assign_attributes(new_attributes) - end + alias attributes= assign_attributes private From 3d4acf470a6c3604ecddd19be3ba3378f4974f4e Mon Sep 17 00:00:00 2001 From: bolpin Date: Wed, 9 Apr 2014 10:31:32 -0700 Subject: [PATCH 07/14] moved specs into spec/lib --- spec/{ => lib}/assignment_spec.rb | 0 spec/{ => lib}/associations_spec.rb | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename spec/{ => lib}/assignment_spec.rb (100%) rename spec/{ => lib}/associations_spec.rb (100%) diff --git a/spec/assignment_spec.rb b/spec/lib/assignment_spec.rb similarity index 100% rename from spec/assignment_spec.rb rename to spec/lib/assignment_spec.rb diff --git a/spec/associations_spec.rb b/spec/lib/associations_spec.rb similarity index 100% rename from spec/associations_spec.rb rename to spec/lib/associations_spec.rb From d926307b8f18e5df37030d4c1f8550c76d2326f3 Mon Sep 17 00:00:00 2001 From: bolpin Date: Wed, 9 Apr 2014 10:32:32 -0700 Subject: [PATCH 08/14] added some development gems (guard, pry, simplecov) and Guardfile --- Gemfile | 11 +++++++++++ Guardfile | 25 +++++++++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 Guardfile diff --git a/Gemfile b/Gemfile index ff9747e..e4cf322 100644 --- a/Gemfile +++ b/Gemfile @@ -2,3 +2,14 @@ source 'https://rubygems.org' # Specify your gem's dependencies in plain_old_model.gemspec gemspec + +group :test do + gem 'simplecov', :require => false +end + +group :development do + # gem 'pry', :require => false + gem 'guard', require: false + gem 'guard-rspec', require: false +end + diff --git a/Guardfile b/Guardfile new file mode 100644 index 0000000..30f07ff --- /dev/null +++ b/Guardfile @@ -0,0 +1,25 @@ +# A sample Guardfile +# More info at https://github.com/guard/guard#readme + +guard :rspec do + watch(%r{^spec/.+_spec\.rb$}) + watch(%r{^spec/(.+)/.+_spec\.rb$}) + watch(%r{^lib/plain_old_model/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } + watch('spec/spec_helper.rb') { "spec" } + + # # Rails example + # watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } + # watch(%r{^app/(.*)(\.erb|\.haml|\.slim)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" } + # watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] } + # watch(%r{^spec/support/(.+)\.rb$}) { "spec" } + # watch('config/routes.rb') { "spec/routing" } + # watch('app/controllers/application_controller.rb') { "spec/controllers" } + # + # # Capybara features specs + # watch(%r{^app/views/(.+)/.*\.(erb|haml|slim)$}) { |m| "spec/features/#{m[1]}_spec.rb" } + # + # # Turnip features and steps + # watch(%r{^spec/acceptance/(.+)\.feature$}) + # watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' } +end + From e665562b9128205f4496bbfc5a28283510206834 Mon Sep 17 00:00:00 2001 From: bolpin Date: Wed, 9 Apr 2014 10:33:46 -0700 Subject: [PATCH 09/14] moving associations code back into attribute_assingment --- lib/plain_old_model/associations.rb | 102 ------------ lib/plain_old_model/attribute_assignment.rb | 165 ++++++++++++++++---- lib/plain_old_model/base.rb | 3 - 3 files changed, 138 insertions(+), 132 deletions(-) diff --git a/lib/plain_old_model/associations.rb b/lib/plain_old_model/associations.rb index 19f7037..011c24d 100644 --- a/lib/plain_old_model/associations.rb +++ b/lib/plain_old_model/associations.rb @@ -1,106 +1,4 @@ module PlainOldModel module Associations - module ClassMethods - def has_one(attr_name, options={}) - associations << HasOneAssociation.new(attr_name, options) - attr_accessor attr_name - end - - def has_many(attr_name, options={}) - associations << HasManyAssociation.new(attr_name, options) - attr_accessor attr_name - end - - def associations - @associations ||= [] - end - - end - - def self.included(klass) - klass.extend ClassMethods - end - - def associations - self.class.associations - end - - class Association - attr_reader :attr_name - - def initialize(attr_name, options) - @attr_name = attr_name - @options = options - end - - def create_value_from_attributes(attributes) - - if @options[:factory_method] - klass.send(@options[:factory_method], attributes) - else - klass.new(attributes) - end - end - - def klass - if @options[:class_name] - @options[:class_name].to_s.camelcase.constantize - else - klass_from_attr_name - end - end - - def klass_from_attr_name - @attr_name.to_s.camelcase.constantize - end - end - - class HasOneAssociation < Association - end - - class HasManyAssociation < Association - def create_value_from_attributes(items) - items.map{|item| super(item)} - end - - def klass_from_attr_name - @attr_name.to_s.singularize.camelcase.constantize - end - end - - private - - def merge_association_instance_variables_with_attributes(association, attr_name, attributes) - association_instance = send(attr_name) - - if association.class == HasOneAssociation - merged_hash = instance_hash(association_instance).deep_merge(attributes[attr_name]) - elsif association.class == HasManyAssociation - association_instance_array = [] - if association_instance.nil? - merged_hash = attributes[attr_name] - else - association_instance.each_with_index do |instance, i| - association_instance_array << instance_hash(instance).deep_merge(attributes[attr_name][i]) - end - merged_hash = association_instance_array - end - end - end - - def instance_hash(association_instance) - hash = HashWithIndifferentAccess.new - unless association_instance.nil? - association_instance.instance_variables.each do |var| - if association_instance.instance_variable_get(var).instance_variables.length > 0 - hash[var.to_s.delete("@")] = instance_hash(association_instance.instance_variable_get(var)) - else - hash[var.to_s.delete("@")] = association_instance.instance_variable_get(var) - end - end - end - hash - end - end end diff --git a/lib/plain_old_model/attribute_assignment.rb b/lib/plain_old_model/attribute_assignment.rb index dfd2796..20a60dc 100644 --- a/lib/plain_old_model/attribute_assignment.rb +++ b/lib/plain_old_model/attribute_assignment.rb @@ -6,52 +6,163 @@ module AttributeAssignment def assign_attributes(new_attributes, options = {}) return unless new_attributes - attributes = new_attributes.dup - assign_attributes_from_associations(attributes) - assign_simple_attributes(attributes) + @new_attributes = new_attributes.dup + assign_attributes_from_associations + assign_simple_attributes end alias attributes= assign_attributes + def self.included(klass) + klass.extend ClassMethods + end + + def associations + self.class.associations + end + + + module ClassMethods + + def has_one(attr_name, options={}) + associations << HasOneAssociation.new(attr_name, options) + attr_accessor attr_name + end + + def has_many(attr_name, options={}) + associations << HasManyAssociation.new(attr_name, options) + attr_accessor attr_name + end + + def associations + @associations ||= [] + end + + end + + + class Association + attr_reader :attr_name + + def initialize(attr_name, options) + @attr_name = attr_name + @options = options + end + + def create_value_from_attributes(attributes) + if @options[:factory_method] + klass.send(@options[:factory_method], attributes) + else + klass.new(attributes) + end + end + + def klass + if @options[:class_name] + @options[:class_name].to_s.camelcase.constantize + else + klass_from_attr_name + end + end + + def klass_from_attr_name + @attr_name.to_s.camelcase.constantize + end + end + + + class HasOneAssociation < Association + end + + + class HasManyAssociation < Association + def create_value_from_attributes(items) + items.map{|item| super(item)} + end + + def klass_from_attr_name + @attr_name.to_s.singularize.camelcase.constantize + end + end + + private - def assign_attributes_from_associations(attributes) - associations.each do |association| + def assign_attributes_from_associations + associations.each do |association| + assign_attributes_from_association(association) + end + end + + def assign_attributes_from_association(association) attr_name = association.attr_name - if attributes.include?(attr_name) - merged_hash = merge_association_instance_variables_with_attributes(association, attr_name, attributes) + if @new_attributes.include?(attr_name) + merged_hash = merged_association_hash(association, attr_name) value = association.create_value_from_attributes(merged_hash) set_attribute(attr_name, value) - attributes.delete_if { |key, value| key.to_s == attr_name.to_s } + @new_attributes.delete_if { |key, value| key.to_s == attr_name.to_s } end end - end - def sanitize_attributes(attributes) - sanitized_attributes = {} - attributes.each do |k, v| - if respond_to?("#{k}=") || respond_to?("#{k}") - sanitized_attributes[k] = v + # deep merge of instance variables from one association + # with the rest of the 'new_attributes' + def merged_association_hash(association, attr_name) + association_instance = send(attr_name) + + if association.class == HasOneAssociation + return instance_variables_hash(association_instance).deep_merge(@new_attributes[attr_name]) + elsif association.class == HasManyAssociation + if association_instance.nil? + return @new_attributes[attr_name] + else + association_instance_array = [] + association_instance.each_with_index do |instance, i| + association_instance_array << instance_variables_hash(instance).deep_merge(@new_attributes[attr_name][i]) + end + return association_instance_array + end end end - sanitized_attributes - end - def assign_simple_attributes(attributes) - attributes = sanitize_attributes(attributes).stringify_keys + def instance_variables_hash(association_instance) + hash = HashWithIndifferentAccess.new - attributes.each do |k, v| - set_attribute(k, v) + unless association_instance.nil? + association_instance.instance_variables.each do |var| + if association_instance.instance_variable_get(var).instance_variables.length > 0 + hash[var.to_s.delete("@")] = instance_variables_hash(association_instance.instance_variable_get(var)) + else + hash[var.to_s.delete("@")] = association_instance.instance_variable_get(var) + end + end + end + hash end - end - def set_attribute(attr_name, value) - if respond_to?("#{attr_name}=") - send("#{attr_name}=", value) - else - instance_variable_set("@#{attr_name}".to_sym, value) + def assign_simple_attributes + sanitized_attributes.stringify_keys.each do |k, v| + set_attribute(k, v) + end + end + + # attributes for which we have an attr_reader and/or an attr_writer + def sanitized_attributes + sanitized = {} + @new_attributes.each do |k, v| + if respond_to?("#{k}=") || respond_to?("#{k}") + sanitized[k] = v + end + end + sanitized + end + + def set_attribute(attr_name, value) + if respond_to?("#{attr_name}=") + send("#{attr_name}=", value) + else + instance_variable_set("@#{attr_name}".to_sym, value) + end end - end end end diff --git a/lib/plain_old_model/base.rb b/lib/plain_old_model/base.rb index a7ae2af..b1cfb14 100644 --- a/lib/plain_old_model/base.rb +++ b/lib/plain_old_model/base.rb @@ -3,7 +3,6 @@ require 'active_model/validations' require 'active_model/conversion' require 'plain_old_model/attribute_assignment' -require 'plain_old_model/associations' module PlainOldModel class Base @@ -12,7 +11,6 @@ class Base include ActiveModel::Validations include ActiveModel::Conversion include PlainOldModel::AttributeAssignment - include PlainOldModel::Associations def initialize(attributes = {}, options = {}) assign_attributes(attributes, options) if attributes @@ -23,5 +21,4 @@ def persisted? end end - end From 498d909dec1667d5cc5ac01b68380ec92078ae5b Mon Sep 17 00:00:00 2001 From: bolpin Date: Wed, 9 Apr 2014 13:17:08 -0700 Subject: [PATCH 10/14] ruby syntax highlights --- README.md | 27 +++++++++++++++++++++------ lib/plain_old_model/associations.rb | 4 ---- 2 files changed, 21 insertions(+), 10 deletions(-) delete mode 100644 lib/plain_old_model/associations.rb diff --git a/README.md b/README.md index 328b070..4e603c6 100644 --- a/README.md +++ b/README.md @@ -8,14 +8,24 @@ Implements nested attribute mass-assignment and has_many and has_one association * ActiveModel::Conversion -## Installation ## +### Installation ### Add this line to your application's Gemfile: + ```ruby + gem install plain_old_model + ``` + +or run + + ```ruby gem 'plain_old_model' + ``` + -## Usage ## +### Usage ### + ```ruby class Person < PlainOldModel::Base attr_accessor :name validates_presence_of :book @@ -23,23 +33,28 @@ Add this line to your application's Gemfile: params = {"name" =>"Leo", :book => {:author =>"Tolstoy", :category => "fiction"}} -## Initialization ## p = Person.new(params) + p.book p.valid? #true + ``` -## Mass Assignment ## +### Mass Assignment ### - p.assign_attributes({:name =>"Leo", :age => "25", :book => {:author =>"Tolstoy", :category => "fiction"}}) + ```ruby + p.assign_attributes({name: "Leo", book: {author: "Tolstoy", category: "fiction"}}) + ``` or + ```ruby p.attributes = {:name =>"Fyodor", :book => {:author =>"Tolstoy", :category => "fiction"}} + ``` -## Associations ## +### Associations ### * has_one * has_many diff --git a/lib/plain_old_model/associations.rb b/lib/plain_old_model/associations.rb deleted file mode 100644 index 011c24d..0000000 --- a/lib/plain_old_model/associations.rb +++ /dev/null @@ -1,4 +0,0 @@ -module PlainOldModel - module Associations - end -end From 72b8d16f8f6d57382ca6bd65e5c703859db3b753 Mon Sep 17 00:00:00 2001 From: bolpin Date: Wed, 9 Apr 2014 13:55:15 -0700 Subject: [PATCH 11/14] code indentation in README --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 4e603c6..5882f8f 100644 --- a/README.md +++ b/README.md @@ -12,15 +12,15 @@ Implements nested attribute mass-assignment and has_many and has_one association Add this line to your application's Gemfile: - ```ruby - gem install plain_old_model - ``` +```ruby + gem install plain_old_model +``` or run - ```ruby - gem 'plain_old_model' - ``` +```ruby +gem 'plain_old_model' +``` ### Usage ### From 37e77054ea5c9b2c9e69e05b71a95d5f8b3f23bf Mon Sep 17 00:00:00 2001 From: bolpin Date: Wed, 9 Apr 2014 13:55:58 -0700 Subject: [PATCH 12/14] code indentation in README --- README.md | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 5882f8f..ffec524 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Implements nested attribute mass-assignment and has_many and has_one association Add this line to your application's Gemfile: ```ruby - gem install plain_old_model +gem install plain_old_model ``` or run @@ -25,33 +25,33 @@ gem 'plain_old_model' ### Usage ### - ```ruby - class Person < PlainOldModel::Base - attr_accessor :name - validates_presence_of :book - end +```ruby +class Person < PlainOldModel::Base + attr_accessor :name + validates_presence_of :book +end - params = {"name" =>"Leo", :book => {:author =>"Tolstoy", :category => "fiction"}} +params = {"name" =>"Leo", :book => {:author =>"Tolstoy", :category => "fiction"}} - p = Person.new(params) +p = Person.new(params) - p.book - p.valid? #true - ``` +p.book +p.valid? #true +``` ### Mass Assignment ### - ```ruby - p.assign_attributes({name: "Leo", book: {author: "Tolstoy", category: "fiction"}}) - ``` +```ruby +p.assign_attributes({name: "Leo", book: {author: "Tolstoy", category: "fiction"}}) +``` or - ```ruby - p.attributes = {:name =>"Fyodor", :book => {:author =>"Tolstoy", :category => "fiction"}} - ``` +```ruby +p.attributes = {:name =>"Fyodor", :book => {:author =>"Tolstoy", :category => "fiction"}} +``` ### Associations ### From 80eb02a13e281d9fa636e56b680bbccc5c8ec7d1 Mon Sep 17 00:00:00 2001 From: bolpin Date: Fri, 5 Sep 2014 15:17:45 -0700 Subject: [PATCH 13/14] added associations example to README --- README.md | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ffec524..aacaa77 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ class Person < PlainOldModel::Base validates_presence_of :book end -params = {"name" =>"Leo", :book => {:author =>"Tolstoy", :category => "fiction"}} +params = {"name" =>"Anna", :book => {:author =>"Tolstoy", :category => "fiction"}} p = Person.new(params) @@ -44,13 +44,13 @@ p.valid? #true ### Mass Assignment ### ```ruby -p.assign_attributes({name: "Leo", book: {author: "Tolstoy", category: "fiction"}}) +p.assign_attributes({name: "Anna", book: {author: "Tolstoy", category: "fiction"}}) ``` or ```ruby -p.attributes = {:name =>"Fyodor", :book => {:author =>"Tolstoy", :category => "fiction"}} +p.attributes = {:name =>"Anna", :book => {:author =>"Tolstoy", :category => "fiction"}} ``` @@ -59,4 +59,17 @@ p.attributes = {:name =>"Fyodor", :book => {:author =>"Tolstoy", :category => "f * has_one * has_many +```ruby +class Person < PlainOldModel::Base + has_many :phones +end +``` +or, with an optional factory_method: + +```ruby +class Person < PlainOldModel::Base + has_many :phones, factory_method: :create, class_name: :telephone +end +``` + From e964be3cc6cf83cf3951a1fa89c7a8d139e9d492 Mon Sep 17 00:00:00 2001 From: bolpin Date: Fri, 5 Sep 2014 15:24:19 -0700 Subject: [PATCH 14/14] documented options to associations --- README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index aacaa77..b199f56 100644 --- a/README.md +++ b/README.md @@ -64,12 +64,21 @@ class Person < PlainOldModel::Base has_many :phones end ``` -or, with an optional factory_method: +or, with optional factory_method and class_name: ```ruby class Person < PlainOldModel::Base has_many :phones, factory_method: :create, class_name: :telephone end + +class Telephone < PlainOldModel::Base + attr_accessor :number, :extension + + def self.create(attributes) + attributes[:extension] = '000' + new(attributes) + end +end ```