Renaming Attribute Protection and solving problem modifying the provided hash to the #attributes= method
This commit is contained in:
parent
1d1d815435
commit
d0ed97ed8b
|
@ -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
|
a Hash (or other model that responds to the [] and []= methods) and set any properties
|
||||||
you'd like to use. For example:
|
you'd like to use. For example:
|
||||||
|
|
||||||
class CatToy << Hash
|
class CatToy < Hash
|
||||||
include CouchRest::Model::CastedModel
|
include CouchRest::Model::CastedModel
|
||||||
|
|
||||||
property :name, String
|
property :name, String
|
||||||
property :purchased, Date
|
property :purchased, Date
|
||||||
end
|
end
|
||||||
|
|
||||||
class Cat << CouchRest::Model::Base
|
class Cat < CouchRest::Model::Base
|
||||||
property :name, String
|
property :name, String
|
||||||
property :toys, [CatToy]
|
property :toys, [CatToy]
|
||||||
end
|
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
|
you'd like to model, the latest version of CouchRest Model (> 1.0.0) supports creating
|
||||||
anonymous classes:
|
anonymous classes:
|
||||||
|
|
||||||
class Cat << CouchRest::Model::Base
|
class Cat < CouchRest::Model::Base
|
||||||
property :name, String
|
property :name, String
|
||||||
|
|
||||||
property :toys do |toy|
|
property :toys do |toy|
|
||||||
|
|
2
Rakefile
2
Rakefile
|
@ -27,7 +27,7 @@ begin
|
||||||
gemspec.extra_rdoc_files = %w( README.md LICENSE THANKS.md )
|
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.files = %w( LICENSE README.md Rakefile THANKS.md history.txt couchrest.gemspec) + Dir["{examples,lib,spec}/**/*"] - Dir["spec/tmp"]
|
||||||
gemspec.has_rdoc = true
|
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("mime-types", "~> 1.15")
|
||||||
gemspec.add_dependency("activemodel", "~> 3.0.0.rc")
|
gemspec.add_dependency("activemodel", "~> 3.0.0.rc")
|
||||||
gemspec.add_dependency("tzinfo", "~> 0.3.22")
|
gemspec.add_dependency("tzinfo", "~> 0.3.22")
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ module CouchRest
|
||||||
include CouchRest::Model::ExtendedAttachments
|
include CouchRest::Model::ExtendedAttachments
|
||||||
include CouchRest::Model::ClassProxy
|
include CouchRest::Model::ClassProxy
|
||||||
include CouchRest::Model::Collection
|
include CouchRest::Model::Collection
|
||||||
include CouchRest::Model::AttributeProtection
|
include CouchRest::Model::PropertyProtection
|
||||||
include CouchRest::Model::Associations
|
include CouchRest::Model::Associations
|
||||||
include CouchRest::Model::Validations
|
include CouchRest::Model::Validations
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ module CouchRest
|
||||||
# * :directly_set_attributes: true when data comes directly from database
|
# * :directly_set_attributes: true when data comes directly from database
|
||||||
#
|
#
|
||||||
def initialize(doc = {}, options = {})
|
def initialize(doc = {}, options = {})
|
||||||
prepare_all_attributes(doc, options)
|
doc = prepare_all_attributes(doc, options)
|
||||||
super(doc)
|
super(doc)
|
||||||
unless self['_id'] && self['_rev']
|
unless self['_id'] && self['_rev']
|
||||||
self[self.model_type_key] = self.class.to_s
|
self[self.model_type_key] = self.class.to_s
|
||||||
|
|
|
@ -7,7 +7,7 @@ module CouchRest::Model
|
||||||
include CouchRest::Model::Configuration
|
include CouchRest::Model::Configuration
|
||||||
include CouchRest::Model::Callbacks
|
include CouchRest::Model::Callbacks
|
||||||
include CouchRest::Model::Properties
|
include CouchRest::Model::Properties
|
||||||
include CouchRest::Model::AttributeProtection
|
include CouchRest::Model::PropertyProtection
|
||||||
include CouchRest::Model::Associations
|
include CouchRest::Model::Associations
|
||||||
include CouchRest::Model::Validations
|
include CouchRest::Model::Validations
|
||||||
attr_accessor :casted_by
|
attr_accessor :casted_by
|
||||||
|
|
|
@ -12,7 +12,7 @@ module CouchRest
|
||||||
add_config :mass_assign_any_attribute
|
add_config :mass_assign_any_attribute
|
||||||
|
|
||||||
configure do |config|
|
configure do |config|
|
||||||
config.model_type_key = 'model'
|
config.model_type_key = 'couchrest-type' # 'model'?
|
||||||
config.mass_assign_any_attribute = false
|
config.mass_assign_any_attribute = false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -61,7 +61,7 @@ module CouchRest
|
||||||
if options[:directly_set_attributes]
|
if options[:directly_set_attributes]
|
||||||
directly_set_read_only_attributes(doc)
|
directly_set_read_only_attributes(doc)
|
||||||
else
|
else
|
||||||
remove_protected_attributes(doc)
|
doc = remove_protected_attributes(doc)
|
||||||
end
|
end
|
||||||
directly_set_attributes(doc) unless doc.nil?
|
directly_set_attributes(doc) unless doc.nil?
|
||||||
end
|
end
|
||||||
|
@ -72,12 +72,18 @@ module CouchRest
|
||||||
prop
|
prop
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Set all the attributes and return a hash with the attributes
|
||||||
|
# that have not been accepted.
|
||||||
def directly_set_attributes(hash)
|
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}=")
|
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
|
elsif mass_assign_any_attribute # config option
|
||||||
self[attribute_name] = attribute_value
|
self[attribute_name] = attribute_value
|
||||||
|
true
|
||||||
|
else
|
||||||
|
false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
module CouchRest
|
module CouchRest
|
||||||
module Model
|
module Model
|
||||||
module AttributeProtection
|
module PropertyProtection
|
||||||
extend ActiveSupport::Concern
|
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
|
# Protected methods will be removed from
|
||||||
# * new
|
# * new
|
||||||
|
@ -22,7 +22,7 @@ module CouchRest
|
||||||
#
|
#
|
||||||
# 3) Mix and match, and assume all unspecified properties are protected.
|
# 3) Mix and match, and assume all unspecified properties are protected.
|
||||||
# property :name, :accessible => true
|
# property :name, :accessible => true
|
||||||
# property :admin, :protected => true
|
# property :admin, :protected => true # ignored
|
||||||
# property :phone # this will be automatically protected
|
# property :phone # this will be automatically protected
|
||||||
#
|
#
|
||||||
# Note: the timestamps! method protectes the created_at and updated_at properties
|
# Note: the timestamps! method protectes the created_at and updated_at properties
|
||||||
|
@ -34,11 +34,16 @@ module CouchRest
|
||||||
|
|
||||||
module ClassMethods
|
module ClassMethods
|
||||||
def accessible_properties
|
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
|
end
|
||||||
|
|
||||||
def protected_properties
|
def protected_properties
|
||||||
properties.select { |prop| prop.options[:protected] }
|
accessibles = accessible_properties
|
||||||
|
properties.reject { |prop| accessibles.include?(prop) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -50,28 +55,17 @@ module CouchRest
|
||||||
self.class.protected_properties
|
self.class.protected_properties
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Return a new copy of the attributes hash with protected attributes
|
||||||
|
# removed.
|
||||||
def remove_protected_attributes(attributes)
|
def remove_protected_attributes(attributes)
|
||||||
protected_names = properties_to_remove_from_mass_assignment.map { |prop| prop.name }
|
protected_names = protected_properties.map { |prop| prop.name }
|
||||||
return attributes if protected_names.empty?
|
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)
|
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
|
end
|
||||||
|
|
||||||
to_remove
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
|
@ -35,6 +35,7 @@ require 'couchrest/model/errors'
|
||||||
require "couchrest/model/persistence"
|
require "couchrest/model/persistence"
|
||||||
require "couchrest/model/typecast"
|
require "couchrest/model/typecast"
|
||||||
require "couchrest/model/property"
|
require "couchrest/model/property"
|
||||||
|
require "couchrest/model/property_protection"
|
||||||
require "couchrest/model/casted_array"
|
require "couchrest/model/casted_array"
|
||||||
require "couchrest/model/properties"
|
require "couchrest/model/properties"
|
||||||
require "couchrest/model/validations"
|
require "couchrest/model/validations"
|
||||||
|
@ -45,7 +46,6 @@ require "couchrest/model/design_doc"
|
||||||
require "couchrest/model/extended_attachments"
|
require "couchrest/model/extended_attachments"
|
||||||
require "couchrest/model/class_proxy"
|
require "couchrest/model/class_proxy"
|
||||||
require "couchrest/model/collection"
|
require "couchrest/model/collection"
|
||||||
require "couchrest/model/attribute_protection"
|
|
||||||
require "couchrest/model/associations"
|
require "couchrest/model/associations"
|
||||||
require "couchrest/model/configuration"
|
require "couchrest/model/configuration"
|
||||||
|
|
||||||
|
|
|
@ -62,20 +62,13 @@ describe CouchRest::Model::Base do
|
||||||
@default_model_key = 'model'
|
@default_model_key = 'model'
|
||||||
end
|
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
|
it "should be possible to override on class using configure method" do
|
||||||
|
default_model_key = Cat.model_type_key
|
||||||
Cat.instance_eval do
|
Cat.instance_eval do
|
||||||
model_type_key 'cat-type'
|
model_type_key 'cat-type'
|
||||||
end
|
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.model_type_key.should eql('cat-type')
|
||||||
cat = Cat.new
|
cat = Cat.new
|
||||||
cat.model_type_key.should eql('cat-type')
|
cat.model_type_key.should eql('cat-type')
|
||||||
|
|
|
@ -34,6 +34,12 @@ describe "Model Attributes" do
|
||||||
user.name.should == "will"
|
user.name.should == "will"
|
||||||
user.phone.should == "555-5555"
|
user.phone.should == "555-5555"
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
describe "Model Base", "accessible flag" do
|
describe "Model Base", "accessible flag" do
|
||||||
|
@ -65,6 +71,12 @@ describe "Model Attributes" do
|
||||||
user.name.should == "will"
|
user.name.should == "will"
|
||||||
user.admin.should == false
|
user.admin.should == false
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
describe "Model Base", "protected flag" do
|
describe "Model Base", "protected flag" do
|
||||||
|
@ -96,6 +108,21 @@ describe "Model Attributes" do
|
||||||
user.name.should == "will"
|
user.name.should == "will"
|
||||||
user.admin.should == false
|
user.admin.should == false
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
describe "Model Base", "mixing protected and accessible flags" do
|
describe "Model Base", "mixing protected and accessible flags" do
|
||||||
|
@ -115,6 +142,7 @@ describe "Model Attributes" do
|
||||||
user.admin.should == false
|
user.admin.should == false
|
||||||
user.phone.should == 'unset phone number'
|
user.phone.should == 'unset phone number'
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "from database" do
|
describe "from database" do
|
|
@ -93,7 +93,7 @@ describe "Subclassing a Model" do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should have an all view with a guard clause for model == subclass name in the map function" do
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue