diff --git a/README.md b/README.md index e96a296..42936aa 100644 --- a/README.md +++ b/README.md @@ -192,14 +192,14 @@ documents and retrieve them using the CastedModel module. Simply include the mod a Hash (or other model that responds to the [] and []= methods) and set any properties you'd like to use. For example: - class CatToy << Hash + class CatToy < Hash include CouchRest::Model::CastedModel property :name, String property :purchased, Date end - class Cat << CouchRest::Model::Base + class Cat < CouchRest::Model::Base property :name, String property :toys, [CatToy] end @@ -218,7 +218,7 @@ Ruby will bring up a missing constant error. To avoid this, or if you have a rea you'd like to model, the latest version of CouchRest Model (> 1.0.0) supports creating anonymous classes: - class Cat << CouchRest::Model::Base + class Cat < CouchRest::Model::Base property :name, String property :toys do |toy| diff --git a/Rakefile b/Rakefile index d527aad..3d5b68f 100644 --- a/Rakefile +++ b/Rakefile @@ -27,7 +27,7 @@ begin gemspec.extra_rdoc_files = %w( README.md LICENSE THANKS.md ) gemspec.files = %w( LICENSE README.md Rakefile THANKS.md history.txt couchrest.gemspec) + Dir["{examples,lib,spec}/**/*"] - Dir["spec/tmp"] gemspec.has_rdoc = true - gemspec.add_dependency("couchrest", "~> 1.0.0") + gemspec.add_dependency("couchrest", "~> 1.0.1") gemspec.add_dependency("mime-types", "~> 1.15") gemspec.add_dependency("activemodel", "~> 3.0.0.rc") gemspec.add_dependency("tzinfo", "~> 0.3.22") diff --git a/lib/couchrest/model/attributes.rb b/lib/couchrest/model/attributes.rb deleted file mode 100644 index 391cdb5..0000000 --- a/lib/couchrest/model/attributes.rb +++ /dev/null @@ -1,68 +0,0 @@ -module CouchRest - module Model - module Attributes - - ## Support for handling attributes - # - # This would be better in the properties file, but due to scoping issues - # this is not yet possible. - # - - def prepare_all_attributes(doc = {}, options = {}) - apply_all_property_defaults - if options[:directly_set_attributes] - directly_set_read_only_attributes(doc) - else - remove_protected_attributes(doc) - end - directly_set_attributes(doc) unless doc.nil? - end - - # Takes a hash as argument, and applies the values by using writer methods - # for each key. It doesn't save the document at the end. Raises a NoMethodError if the corresponding methods are - # missing. In case of error, no attributes are changed. - def update_attributes_without_saving(hash) - # Remove any protected and update all the rest. Any attributes - # which do not have a property will simply be ignored. - attrs = remove_protected_attributes(hash) - directly_set_attributes(attrs) - end - alias :attributes= :update_attributes_without_saving - - - private - - def directly_set_attributes(hash) - hash.each do |attribute_name, attribute_value| - if self.respond_to?("#{attribute_name}=") - self.send("#{attribute_name}=", hash.delete(attribute_name)) - end - end - end - - def directly_set_read_only_attributes(hash) - property_list = self.properties.map{|p| p.name} - hash.each do |attribute_name, attribute_value| - next if self.respond_to?("#{attribute_name}=") - if property_list.include?(attribute_name) - write_attribute(attribute_name, hash.delete(attribute_name)) - end - end - end - - def set_attributes(hash) - attrs = remove_protected_attributes(hash) - directly_set_attributes(attrs) - end - - def check_properties_exist(attrs) - property_list = self.properties.map{|p| p.name} - attrs.each do |attribute_name, attribute_value| - raise NoMethodError, "Property #{attribute_name} not created" unless respond_to?("#{attribute_name}=") or property_list.include?(attribute_name) - end - end - - end - end -end - diff --git a/lib/couchrest/model/base.rb b/lib/couchrest/model/base.rb index bef793f..b52ad59 100644 --- a/lib/couchrest/model/base.rb +++ b/lib/couchrest/model/base.rb @@ -13,7 +13,7 @@ module CouchRest include CouchRest::Model::ExtendedAttachments include CouchRest::Model::ClassProxy include CouchRest::Model::Collection - include CouchRest::Model::AttributeProtection + include CouchRest::Model::PropertyProtection include CouchRest::Model::Associations include CouchRest::Model::Validations @@ -47,7 +47,7 @@ module CouchRest # * :directly_set_attributes: true when data comes directly from database # def initialize(doc = {}, options = {}) - prepare_all_attributes(doc, options) + doc = prepare_all_attributes(doc, options) super(doc) unless self['_id'] && self['_rev'] self[self.model_type_key] = self.class.to_s diff --git a/lib/couchrest/model/casted_model.rb b/lib/couchrest/model/casted_model.rb index ea74c02..4d931fc 100644 --- a/lib/couchrest/model/casted_model.rb +++ b/lib/couchrest/model/casted_model.rb @@ -7,7 +7,7 @@ module CouchRest::Model include CouchRest::Model::Configuration include CouchRest::Model::Callbacks include CouchRest::Model::Properties - include CouchRest::Model::AttributeProtection + include CouchRest::Model::PropertyProtection include CouchRest::Model::Associations include CouchRest::Model::Validations attr_accessor :casted_by diff --git a/lib/couchrest/model/configuration.rb b/lib/couchrest/model/configuration.rb index 2c4919a..765a4b1 100644 --- a/lib/couchrest/model/configuration.rb +++ b/lib/couchrest/model/configuration.rb @@ -12,7 +12,7 @@ module CouchRest add_config :mass_assign_any_attribute configure do |config| - config.model_type_key = 'model' + config.model_type_key = 'couchrest-type' # 'model'? config.mass_assign_any_attribute = false end end diff --git a/lib/couchrest/model/properties.rb b/lib/couchrest/model/properties.rb index f431c21..97b4c53 100644 --- a/lib/couchrest/model/properties.rb +++ b/lib/couchrest/model/properties.rb @@ -61,7 +61,7 @@ module CouchRest if options[:directly_set_attributes] directly_set_read_only_attributes(doc) else - remove_protected_attributes(doc) + doc = remove_protected_attributes(doc) end directly_set_attributes(doc) unless doc.nil? end @@ -72,12 +72,18 @@ module CouchRest prop end + # Set all the attributes and return a hash with the attributes + # that have not been accepted. def directly_set_attributes(hash) - hash.each do |attribute_name, attribute_value| + hash.reject do |attribute_name, attribute_value| if self.respond_to?("#{attribute_name}=") - self.send("#{attribute_name}=", hash.delete(attribute_name)) + self.send("#{attribute_name}=", attribute_value) + true elsif mass_assign_any_attribute # config option self[attribute_name] = attribute_value + true + else + false end end end diff --git a/lib/couchrest/model/attribute_protection.rb b/lib/couchrest/model/property_protection.rb similarity index 66% rename from lib/couchrest/model/attribute_protection.rb rename to lib/couchrest/model/property_protection.rb index ed852a3..73a2e74 100644 --- a/lib/couchrest/model/attribute_protection.rb +++ b/lib/couchrest/model/property_protection.rb @@ -1,9 +1,9 @@ module CouchRest module Model - module AttributeProtection + module PropertyProtection extend ActiveSupport::Concern - # Attribute protection from mass assignment to CouchRest::Model properties + # Property protection from mass assignment to CouchRest::Model properties # # Protected methods will be removed from # * new @@ -22,7 +22,7 @@ module CouchRest # # 3) Mix and match, and assume all unspecified properties are protected. # property :name, :accessible => true - # property :admin, :protected => true + # property :admin, :protected => true # ignored # property :phone # this will be automatically protected # # Note: the timestamps! method protectes the created_at and updated_at properties @@ -34,11 +34,16 @@ module CouchRest module ClassMethods def accessible_properties - properties.select { |prop| prop.options[:accessible] } + props = properties.select { |prop| prop.options[:accessible] } + if props.empty? + props = properties.select { |prop| !prop.options[:protected] } + end + props end def protected_properties - properties.select { |prop| prop.options[:protected] } + accessibles = accessible_properties + properties.reject { |prop| accessibles.include?(prop) } end end @@ -50,28 +55,17 @@ module CouchRest self.class.protected_properties end + # Return a new copy of the attributes hash with protected attributes + # removed. def remove_protected_attributes(attributes) - protected_names = properties_to_remove_from_mass_assignment.map { |prop| prop.name } - return attributes if protected_names.empty? + protected_names = protected_properties.map { |prop| prop.name } + return attributes if protected_names.empty? or attributes.nil? - attributes.reject! do |property_name, property_value| + attributes.reject do |property_name, property_value| protected_names.include?(property_name.to_s) - end if attributes - - attributes || {} - end - - private - - def properties_to_remove_from_mass_assignment - to_remove = protected_properties - - unless accessible_properties.empty? - to_remove += properties.reject { |prop| prop.options[:accessible] } end - - to_remove end + end end end diff --git a/lib/couchrest_model.rb b/lib/couchrest_model.rb index 8abb75b..181bb9c 100644 --- a/lib/couchrest_model.rb +++ b/lib/couchrest_model.rb @@ -35,6 +35,7 @@ require 'couchrest/model/errors' require "couchrest/model/persistence" require "couchrest/model/typecast" require "couchrest/model/property" +require "couchrest/model/property_protection" require "couchrest/model/casted_array" require "couchrest/model/properties" require "couchrest/model/validations" @@ -45,7 +46,6 @@ require "couchrest/model/design_doc" require "couchrest/model/extended_attachments" require "couchrest/model/class_proxy" require "couchrest/model/collection" -require "couchrest/model/attribute_protection" require "couchrest/model/associations" require "couchrest/model/configuration" diff --git a/spec/couchrest/configuration_spec.rb b/spec/couchrest/configuration_spec.rb index 8e4ff35..46681c0 100644 --- a/spec/couchrest/configuration_spec.rb +++ b/spec/couchrest/configuration_spec.rb @@ -62,20 +62,13 @@ describe CouchRest::Model::Base do @default_model_key = 'model' end - it "should set default configuration options on Model::Base" do - CouchRest::Model::Base.model_type_key.should eql(@default_model_key) - end - - it "should provide options from instance" do - cat = Cat.new - cat.model_type_key.should eql(@default_model_key) - end it "should be possible to override on class using configure method" do + default_model_key = Cat.model_type_key Cat.instance_eval do model_type_key 'cat-type' end - CouchRest::Model::Base.model_type_key.should eql(@default_model_key) + CouchRest::Model::Base.model_type_key.should eql(default_model_key) Cat.model_type_key.should eql('cat-type') cat = Cat.new cat.model_type_key.should eql('cat-type') diff --git a/spec/couchrest/attribute_protection_spec.rb b/spec/couchrest/property_protection_spec.rb similarity index 81% rename from spec/couchrest/attribute_protection_spec.rb rename to spec/couchrest/property_protection_spec.rb index 47e50a8..eb5ca55 100644 --- a/spec/couchrest/attribute_protection_spec.rb +++ b/spec/couchrest/property_protection_spec.rb @@ -34,6 +34,12 @@ describe "Model Attributes" do user.name.should == "will" user.phone.should == "555-5555" end + + it "should provide a list of all properties as accessible" do + user = NoProtection.new(:name => "will", :phone => "555-5555") + user.accessible_properties.length.should eql(2) + user.protected_properties.should be_empty + end end describe "Model Base", "accessible flag" do @@ -65,6 +71,12 @@ describe "Model Attributes" do user.name.should == "will" user.admin.should == false end + + it "should provide correct accessible and protected property lists" do + user = WithAccessible.new(:name => 'will', :admin => true) + user.accessible_properties.map{|p| p.to_s}.should eql(['name']) + user.protected_properties.map{|p| p.to_s}.should eql(['admin']) + end end describe "Model Base", "protected flag" do @@ -96,6 +108,21 @@ describe "Model Attributes" do user.name.should == "will" user.admin.should == false end + + it "should not modify the provided attribute hash" do + user = WithProtected.new + attrs = {:name => "will", :admin => true} + user.attributes = attrs + attrs[:admin].should be_true + attrs[:name].should eql('will') + end + + it "should provide correct accessible and protected property lists" do + user = WithProtected.new(:name => 'will', :admin => true) + user.accessible_properties.map{|p| p.to_s}.should eql(['name']) + user.protected_properties.map{|p| p.to_s}.should eql(['admin']) + end + end describe "Model Base", "mixing protected and accessible flags" do @@ -115,6 +142,7 @@ describe "Model Attributes" do user.admin.should == false user.phone.should == 'unset phone number' end + end describe "from database" do diff --git a/spec/couchrest/subclass_spec.rb b/spec/couchrest/subclass_spec.rb index 333c383..78a8ed8 100644 --- a/spec/couchrest/subclass_spec.rb +++ b/spec/couchrest/subclass_spec.rb @@ -93,7 +93,7 @@ describe "Subclassing a Model" do end it "should have an all view with a guard clause for model == subclass name in the map function" do - OnlineCourse.design_doc['views']['all']['map'].should =~ /if \(doc\['model'\] == 'OnlineCourse'\)/ + OnlineCourse.design_doc['views']['all']['map'].should =~ /if \(doc\['#{OnlineCourse.model_type_key}'\] == 'OnlineCourse'\)/ end end