ripple 1.0.0.beta → 1.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. data/.gitignore +10 -6
  2. data/Gemfile +6 -15
  3. data/Gemfile.rails30 +3 -0
  4. data/Gemfile.rails31 +3 -0
  5. data/Gemfile.rails32 +3 -0
  6. data/Guardfile +3 -1
  7. data/LICENSE +16 -0
  8. data/README.markdown +173 -0
  9. data/RELEASE_NOTES.textile +286 -0
  10. data/Rakefile +19 -0
  11. data/lib/rails/generators/ripple/configuration/templates/ripple.yml +1 -0
  12. data/lib/rails/generators/ripple/model/model_generator.rb +1 -1
  13. data/lib/rails/generators/ripple/model/templates/{model.rb → model.rb.erb} +0 -0
  14. data/lib/rails/generators/ripple/observer/observer_generator.rb +1 -1
  15. data/lib/rails/generators/ripple/observer/templates/{observer.rb → observer.rb.erb} +0 -2
  16. data/lib/rails/generators/ripple/test/templates/cucumber.rb.erb +7 -0
  17. data/lib/rails/generators/ripple/test/test_generator.rb +17 -13
  18. data/lib/rails/generators/ripple_generator.rb +1 -0
  19. data/lib/ripple/associations.rb +65 -55
  20. data/lib/ripple/associations/embedded.rb +1 -1
  21. data/lib/ripple/associations/linked.rb +1 -1
  22. data/lib/ripple/associations/many.rb +1 -1
  23. data/lib/ripple/associations/many_embedded_proxy.rb +3 -2
  24. data/lib/ripple/associations/many_linked_proxy.rb +1 -1
  25. data/lib/ripple/associations/many_reference_proxy.rb +7 -5
  26. data/lib/ripple/associations/proxy.rb +2 -2
  27. data/lib/ripple/attribute_methods.rb +69 -61
  28. data/lib/ripple/attribute_methods/dirty.rb +2 -2
  29. data/lib/ripple/attribute_methods/read.rb +4 -2
  30. data/lib/ripple/callbacks.rb +23 -26
  31. data/lib/ripple/conflict/basic_resolver.rb +6 -2
  32. data/lib/ripple/conflict/document_hooks.rb +26 -0
  33. data/lib/ripple/conflict/resolver.rb +10 -2
  34. data/lib/ripple/conflict/test_helper.rb +3 -2
  35. data/lib/ripple/conversion.rb +1 -0
  36. data/lib/ripple/core_ext.rb +1 -0
  37. data/lib/ripple/core_ext/casting.rb +2 -0
  38. data/lib/ripple/core_ext/indexes.rb +89 -0
  39. data/lib/ripple/document.rb +23 -22
  40. data/lib/ripple/document/key.rb +12 -14
  41. data/lib/ripple/document/persistence.rb +99 -84
  42. data/lib/ripple/embedded_document.rb +9 -10
  43. data/lib/ripple/embedded_document/persistence.rb +42 -44
  44. data/lib/ripple/i18n.rb +4 -1
  45. data/lib/ripple/indexes.rb +151 -0
  46. data/lib/ripple/locale/en.yml +4 -0
  47. data/lib/ripple/locale/fr.yml +24 -0
  48. data/lib/ripple/nested_attributes.rb +92 -90
  49. data/lib/ripple/properties.rb +2 -1
  50. data/lib/ripple/railtie.rb +9 -0
  51. data/lib/ripple/railties/ripple.rake +32 -15
  52. data/lib/ripple/serialization.rb +50 -52
  53. data/lib/ripple/test_server.rb +1 -2
  54. data/lib/ripple/timestamps.rb +6 -8
  55. data/lib/ripple/validations.rb +19 -21
  56. data/lib/ripple/version.rb +1 -1
  57. data/ripple.gemspec +6 -5
  58. data/spec/generators/ripple/configuration_generator_spec.rb +9 -0
  59. data/spec/generators/ripple/js_generator_spec.rb +14 -0
  60. data/spec/generators/ripple/model_generator_spec.rb +64 -0
  61. data/spec/generators/ripple/observer_generator_spec.rb +20 -0
  62. data/spec/generators/ripple/test_generator_spec.rb +116 -0
  63. data/spec/generators/ripple_generator_spec.rb +11 -0
  64. data/spec/integration/ripple/conflict_resolution_spec.rb +35 -4
  65. data/spec/integration/ripple/indexes_spec.rb +47 -0
  66. data/spec/ripple/associations/many_embedded_proxy_spec.rb +50 -60
  67. data/spec/ripple/associations/many_linked_proxy_spec.rb +2 -2
  68. data/spec/ripple/associations/many_reference_proxy_spec.rb +1 -1
  69. data/spec/ripple/associations_spec.rb +16 -7
  70. data/spec/ripple/attribute_methods_spec.rb +43 -2
  71. data/spec/ripple/callbacks_spec.rb +120 -101
  72. data/spec/ripple/conversion_spec.rb +5 -13
  73. data/spec/ripple/core_ext_spec.rb +93 -15
  74. data/spec/ripple/finders_spec.rb +0 -2
  75. data/spec/ripple/indexes_spec.rb +111 -0
  76. data/spec/ripple/observable_spec.rb +1 -2
  77. data/spec/ripple/persistence_spec.rb +55 -32
  78. data/spec/ripple/properties_spec.rb +1 -1
  79. data/spec/ripple/ripple_spec.rb +5 -5
  80. data/spec/ripple/timestamps_spec.rb +9 -2
  81. data/spec/ripple/validations_spec.rb +50 -52
  82. data/spec/spec_helper.rb +9 -2
  83. data/spec/support/generator_setup.rb +26 -0
  84. data/spec/support/models.rb +1 -0
  85. data/spec/support/models/box.rb +1 -0
  86. data/spec/support/models/clock.rb +1 -1
  87. data/spec/support/models/indexer.rb +26 -0
  88. data/spec/support/models/post.rb +3 -2
  89. data/spec/support/models/widget.rb +2 -0
  90. data/spec/support/search.rb +2 -2
  91. data/spec/support/test_server.rb +23 -11
  92. data/spec/support/test_server.yml.example +1 -1
  93. metadata +159 -135
  94. data/spec/support/mocks.rb +0 -4
data/Rakefile CHANGED
@@ -24,6 +24,25 @@ task :release => :gem do
24
24
  system "gem push pkg/#{gemspec.name}-#{gemspec.version}.gem"
25
25
  end
26
26
 
27
+ desc "Cleans up white space in source files"
28
+ task :clean_whitespace do
29
+ no_file_cleaned = true
30
+
31
+ Dir["**/*.rb"].each do |file|
32
+ contents = File.read(file)
33
+ cleaned_contents = contents.gsub(/([ \t]+)$/, '')
34
+ unless cleaned_contents == contents
35
+ no_file_cleaned = false
36
+ puts " - Cleaned #{file}"
37
+ File.open(file, 'w') { |f| f.write(cleaned_contents) }
38
+ end
39
+ end
40
+
41
+ if no_file_cleaned
42
+ puts "No files with trailing whitespace found"
43
+ end
44
+ end
45
+
27
46
  desc "Run Unit Specs Only"
28
47
  RSpec::Core::RakeTask.new(:spec) do |spec|
29
48
  spec.rspec_opts = %w[--profile --tag ~integration]
@@ -3,6 +3,7 @@ development:
3
3
  http_port: 8098
4
4
  pb_port: 8087
5
5
  host: 127.0.0.1
6
+ source: /usr/local/bin #Default for Homebrew.
6
7
 
7
8
  # The test environment has additional keys for configuring the
8
9
  # Riak::TestServer for your test/spec suite:
@@ -11,7 +11,7 @@ module Ripple
11
11
  check_class_collision
12
12
 
13
13
  def create_model_file
14
- template 'model.rb', "app/models/#{file_path}.rb"
14
+ template 'model.rb.erb', "app/models/#{file_path}.rb"
15
15
  end
16
16
 
17
17
  hook_for :test_framework
@@ -7,7 +7,7 @@ module Ripple
7
7
  check_class_collision :suffix => "Observer"
8
8
 
9
9
  def create_observer_file
10
- template 'observer.rb', File.join("app/models", class_path, "#{file_name}_observer.rb")
10
+ template 'observer.rb.erb', File.join("app/models", class_path, "#{file_name}_observer.rb")
11
11
  end
12
12
 
13
13
  hook_for :test_framework
@@ -1,4 +1,2 @@
1
- <% module_namespacing do -%>
2
1
  class <%= class_name %>Observer < ActiveModel::Observer
3
2
  end
4
- <% end -%>
@@ -0,0 +1,7 @@
1
+ require 'ripple/test_server'
2
+
3
+ After do
4
+ Ripple::TestServer.clear
5
+ end
6
+
7
+ Ripple::TestServer.setup
@@ -6,32 +6,36 @@ module Ripple
6
6
  desc 'Generates test helpers for Ripple. Test::Unit, RSpec and Cucumber are supported.'
7
7
  # Cucumber
8
8
  def create_cucumber_file
9
- if File.directory?(Rails.root + "features/support")
10
- insert_into_file 'features/support/ripple.rb', "\n\nAfter do\n Ripple::TestServer.clear\nend", :after => "Ripple::TestServer.setup"
9
+ if File.directory?("features/support")
10
+ template 'cucumber.rb.erb', 'features/support/ripple.rb'
11
11
  end
12
12
  end
13
13
 
14
14
  # RSpec
15
15
  def create_rspec_file
16
- if File.file?(Rails.root + 'spec/spec_helper.rb')
17
- inject_into_file 'spec/spec_helper.rb', :before => /R[Ss]pec\.configure do \|config\|/ do
18
- "require 'ripple/test_server'\n"
16
+ if File.file?('spec/spec_helper.rb')
17
+ rspec_prelude = /\s*R[Ss]pec\.configure do \|config\|/
18
+ indentation = File.binread('spec/spec_helper.rb').match(rspec_prelude)[0].match(/^\s*/)[0]
19
+ inject_into_file 'spec/spec_helper.rb', :before => rspec_prelude do
20
+ "#{indentation}require 'ripple/test_server'\n"
19
21
  end
20
- inject_into_file 'spec/spec_helper.rb', :after => /R[Ss]pec\.configure do \|config\|/ do
21
- "\n config.before(:all){ Ripple::TestServer.setup }" +
22
- "\n config.after(:each){ Ripple::TestServer.clear }\n"
22
+ inject_into_file 'spec/spec_helper.rb', :after => rspec_prelude do
23
+ "\n#{indentation} config.before(:suite) { Ripple::TestServer.setup }" +
24
+ "\n#{indentation} config.after(:each) { Ripple::TestServer.clear }\n"
23
25
  end
24
26
  end
25
27
  end
26
28
 
27
29
  # Test::Unit
28
30
  def create_test_unit_file
29
- if File.file?(Rails.root + 'test/test_helper.rb')
30
- inject_into_file "test/test_helper.rb", :before => "class ActiveSupport::TestCase" do
31
- "# Setup in-memory test server for Riak\nrequire 'ripple/test_server'\n\n"
31
+ if File.file?('test/test_helper.rb')
32
+ test_case_prelude = /\s*class ActiveSupport::TestCase/
33
+ indentation = File.binread('test/test_helper.rb').match(test_case_prelude)[0].match(/^\s*/)[0]
34
+ inject_into_file "test/test_helper.rb", :before => test_case_prelude do
35
+ "#{indentation}# Setup in-memory test server for Riak\n#{indentation}require 'ripple/test_server'\n\n"
32
36
  end
33
- inject_into_class "test/test_helper.rb", ActiveSupport::TestCase do
34
- " setup { Ripple::TestServer.setup }\n teardown { Ripple::TestServer.clear }\n\n"
37
+ inject_into_class "test/test_helper.rb", 'ActiveSupport::TestCase' do
38
+ "#{indentation} setup { Ripple::TestServer.setup }\n#{indentation} teardown { Ripple::TestServer.clear }\n\n"
35
39
  end
36
40
  end
37
41
  end
@@ -1,3 +1,4 @@
1
+ require 'rails/generators'
1
2
  require "rails/generators/named_base"
2
3
  require "rails/generators/active_model"
3
4
 
@@ -101,6 +101,7 @@ module Ripple
101
101
  private
102
102
  def create_association(type, name, options={})
103
103
  association = associations[name] = Association.new(type, name, options)
104
+ association.validate!(self)
104
105
  association.setup_on(self)
105
106
 
106
107
  define_method(name) do
@@ -120,72 +121,71 @@ module Ripple
120
121
  end
121
122
  end
122
123
 
123
- module InstanceMethods
124
- # @private
125
- def get_proxy(association)
126
- unless proxy = instance_variable_get(association.ivar)
127
- proxy = association.proxy_class.new(self, association)
128
- instance_variable_set(association.ivar, proxy)
129
- end
130
- proxy
124
+
125
+ # @private
126
+ def get_proxy(association)
127
+ unless proxy = instance_variable_get(association.ivar)
128
+ proxy = association.proxy_class.new(self, association)
129
+ instance_variable_set(association.ivar, proxy)
131
130
  end
131
+ proxy
132
+ end
132
133
 
133
- # @private
134
- def reset_associations
135
- self.class.associations.each do |name, assoc_object|
136
- send(name).reset
137
- end
134
+ # @private
135
+ def reset_associations
136
+ self.class.associations.each do |name, assoc_object|
137
+ send(name).reset
138
138
  end
139
+ end
139
140
 
140
- # Adds embedded documents to the attributes
141
- # @private
142
- def attributes_for_persistence
143
- self.class.embedded_associations.inject(super) do |attrs, association|
144
- documents = instance_variable_get(association.ivar)
145
- # We must explicitly check #nil? (rather than just saying `if documents`)
146
- # because documents can be an association proxy that is proxying nil.
147
- # In this case ruby treats documents as true because it is not _really_ nil,
148
- # but #nil? will tell us if it is proxying nil.
149
-
150
- unless documents.nil?
151
- attrs[association.name] = documents.is_a?(Array) ? documents.map(&:attributes_for_persistence) : documents.attributes_for_persistence
152
- end
153
- attrs
141
+ # Adds embedded documents to the attributes
142
+ # @private
143
+ def attributes_for_persistence
144
+ self.class.embedded_associations.inject(super) do |attrs, association|
145
+ documents = instance_variable_get(association.ivar)
146
+ # We must explicitly check #nil? (rather than just saying `if documents`)
147
+ # because documents can be an association proxy that is proxying nil.
148
+ # In this case ruby treats documents as true because it is not _really_ nil,
149
+ # but #nil? will tell us if it is proxying nil.
150
+
151
+ unless documents.nil?
152
+ attrs[association.name] = documents.is_a?(Array) ? documents.map(&:attributes_for_persistence) : documents.attributes_for_persistence
154
153
  end
154
+ attrs
155
155
  end
156
+ end
156
157
 
157
- def propagate_callbacks_to_embedded_associations(name, kind)
158
- self.class.embedded_associations.each do |association|
159
- documents = instance_variable_get(association.ivar)
160
- # We must explicitly check #nil? (rather than just saying `if documents`)
161
- # because documents can be an association proxy that is proxying nil.
162
- # In this case ruby treats documents as true because it is not _really_ nil,
163
- # but #nil? will tell us if it is proxying nil.
164
- next if documents.nil?
165
-
166
- Array(documents).each do |doc|
167
- doc.send("_#{name}_callbacks").each do |callback|
168
- next unless callback.kind == kind
169
- doc.send(callback.filter)
170
- end
158
+ def propagate_callbacks_to_embedded_associations(name, kind)
159
+ self.class.embedded_associations.each do |association|
160
+ documents = instance_variable_get(association.ivar)
161
+ # We must explicitly check #nil? (rather than just saying `if documents`)
162
+ # because documents can be an association proxy that is proxying nil.
163
+ # In this case ruby treats documents as true because it is not _really_ nil,
164
+ # but #nil? will tell us if it is proxying nil.
165
+ next if documents.nil?
166
+
167
+ Array(documents).each do |doc|
168
+ doc.send("_#{name}_callbacks").each do |callback|
169
+ next unless callback.kind == kind
170
+ doc.send(callback.filter)
171
171
  end
172
172
  end
173
173
  end
174
+ end
174
175
 
175
- # Propagates callbacks (save/create/update/destroy) to embedded associated documents.
176
- # This is necessary so that when a parent is saved, the embedded child's before_save
177
- # hooks are run as well.
178
- # @private
179
- def run_callbacks(name, *args, &block)
180
- # validation is already propagated to embedded documents via the
181
- # AssociatedValidator. We don't need to duplicate the propgation here.
182
- return super if name == :validation
183
-
184
- propagate_callbacks_to_embedded_associations(name, :before)
185
- return_value = super
186
- propagate_callbacks_to_embedded_associations(name, :after)
187
- return_value
188
- end
176
+ # Propagates callbacks (save/create/update/destroy) to embedded associated documents.
177
+ # This is necessary so that when a parent is saved, the embedded child's before_save
178
+ # hooks are run as well.
179
+ # @private
180
+ def run_callbacks(name, *args, &block)
181
+ # validation is already propagated to embedded documents via the
182
+ # AssociatedValidator. We don't need to duplicate the propagation here.
183
+ return super if name == :validation
184
+
185
+ propagate_callbacks_to_embedded_associations(name, :before)
186
+ return_value = super
187
+ propagate_callbacks_to_embedded_associations(name, :after)
188
+ return_value
189
189
  end
190
190
  end
191
191
 
@@ -202,6 +202,16 @@ module Ripple
202
202
  @type, @name, @options = type, name, options.to_options
203
203
  end
204
204
 
205
+ def validate!(owner)
206
+ # TODO: Refactor this into an association subclass. See also GH #284
207
+ if @options[:using] == :stored_key
208
+ single_name = ActiveSupport::Inflector.singularize(@name.to_s)
209
+ prop_name = "#{single_name}_key"
210
+ prop_name << "s" if many?
211
+ raise ArgumentError, t('stored_key_requires_property', :name => prop_name) unless owner.properties.include?(prop_name)
212
+ end
213
+ end
214
+
205
215
  # @return String The class name of the associated object(s)
206
216
  def class_name
207
217
  @class_name ||= case
@@ -18,7 +18,7 @@ module Ripple
18
18
  end
19
19
 
20
20
  def assign_references(docs)
21
- Array(docs).each do |doc|
21
+ Array.wrap(docs).each do |doc|
22
22
  next unless doc.respond_to?(:_parent_document=)
23
23
  doc._parent_document = owner
24
24
  end
@@ -7,7 +7,7 @@ module Ripple
7
7
  def replace(value)
8
8
  @reflection.verify_type!(value, @owner)
9
9
  @owner.robject.links -= links
10
- Array(value).compact.each do |doc|
10
+ Array.wrap(value).compact.each do |doc|
11
11
  @owner.robject.links << doc.to_link(@reflection.link_tag)
12
12
  end
13
13
  loaded
@@ -7,7 +7,7 @@ module Ripple
7
7
 
8
8
  def to_ary
9
9
  load_target
10
- Array === target ? target.to_ary : Array(target)
10
+ Array === target ? target.to_ary : Array.wrap(target)
11
11
  end
12
12
 
13
13
  def count
@@ -10,9 +10,10 @@ module Ripple
10
10
 
11
11
  def <<(docs)
12
12
  load_target
13
- @reflection.verify_type!(Array(docs), @owner)
13
+ docs = Array.wrap(docs)
14
+ @reflection.verify_type!(docs, @owner)
14
15
  assign_references(docs)
15
- @target += Array(docs)
16
+ @target += docs
16
17
  self
17
18
  end
18
19
 
@@ -15,7 +15,7 @@ module Ripple
15
15
 
16
16
  def <<(value)
17
17
  if loaded?
18
- new_target = @target.concat(Array(value))
18
+ new_target = @target.concat(Array.wrap(value))
19
19
  replace new_target
20
20
  else
21
21
  @reflection.verify_type!([value], @owner)
@@ -9,11 +9,12 @@ module Ripple
9
9
  include Many
10
10
 
11
11
  def <<(value)
12
- @reflection.verify_type!([value], @owner)
12
+ values = Array.wrap(value)
13
+ @reflection.verify_type!(values, @owner)
13
14
 
14
- assign_key(value)
15
+ values.each {|v| assign_key(v) }
15
16
  load_target
16
- @target << value
17
+ @target.merge values
17
18
 
18
19
  self
19
20
  end
@@ -21,7 +22,7 @@ module Ripple
21
22
  def replace(value)
22
23
  @reflection.verify_type!(value, @owner)
23
24
  delete_all
24
- Array(value).compact.each do |doc|
25
+ Array.wrap(value).compact.each do |doc|
25
26
  assign_key(doc)
26
27
  end
27
28
  loaded
@@ -43,6 +44,7 @@ module Ripple
43
44
  end
44
45
 
45
46
  def target
47
+ load_target
46
48
  @target.to_a
47
49
  end
48
50
 
@@ -90,4 +92,4 @@ module Ripple
90
92
  end
91
93
  end
92
94
  end
93
- end
95
+ end
@@ -20,7 +20,7 @@ module Ripple
20
20
 
21
21
  def initialize(owner, reflection)
22
22
  @owner, @reflection = owner, reflection
23
- Array(reflection.options[:extend]).each { |ext| proxy_extend(ext) }
23
+ Array.wrap(reflection.options[:extend]).each { |ext| proxy_extend(ext) }
24
24
  reset
25
25
  end
26
26
 
@@ -86,7 +86,7 @@ module Ripple
86
86
  end
87
87
 
88
88
  def loaded_documents
89
- loaded? ? Array(target) : []
89
+ loaded? ? Array.wrap(target) : []
90
90
  end
91
91
 
92
92
  def has_changed_documents?
@@ -28,8 +28,10 @@ module Ripple
28
28
  module ClassMethods
29
29
  # @private
30
30
  def property(key, type, options={})
31
- undefine_attribute_methods
32
- super
31
+ super.tap do
32
+ undefine_attribute_methods
33
+ define_attribute_methods
34
+ end
33
35
  end
34
36
 
35
37
  # Generates all the attribute-related methods for properties defined
@@ -39,80 +41,86 @@ module Ripple
39
41
  end
40
42
  end
41
43
 
42
- module InstanceMethods
43
- # A copy of the values of all attributes in the Document. The result
44
- # is not memoized, so use sparingly. This does not include associated objects,
45
- # nor embedded documents.
46
- # @return [Hash] all document attributes, by key
47
- def attributes
48
- raw_attributes.reject { |k, v| !respond_to?(k) }
49
- end
44
+ # A copy of the values of all attributes in the Document. The result
45
+ # is not memoized, so use sparingly. This does not include associated objects,
46
+ # nor embedded documents.
47
+ # @return [Hash] all document attributes, by key
48
+ def attributes
49
+ raw_attributes.reject { |k, v| !respond_to?(k) }
50
+ end
50
51
 
51
- def raw_attributes
52
- self.class.properties.values.inject(@attributes.with_indifferent_access) do |hash, prop|
53
- hash[prop.key] = attribute(prop.key)
54
- hash
55
- end
52
+ def raw_attributes
53
+ self.class.properties.values.inject(@attributes.with_indifferent_access) do |hash, prop|
54
+ hash[prop.key] = attribute(prop.key)
55
+ hash
56
56
  end
57
+ end
58
+
59
+ # Mass assign the document's attributes.
60
+ # @param [Hash] attrs the attributes to assign
61
+ # @param [Hash] options assignment options
62
+ def assign_attributes(attrs, options={})
63
+ raise ArgumentError, t('attribute_hash') unless(Hash === attrs)
57
64
 
58
- # Mass assign the document's attributes.
59
- # @param [Hash] attrs the attributes to assign
60
- def attributes=(attrs)
61
- raise ArgumentError, t('attribute_hash') unless Hash === attrs
62
- sanitize_for_mass_assignment(attrs).each do |k,v|
63
- if respond_to?("#{k}=")
64
- __send__("#{k}=",v)
65
- else
66
- raise ArgumentError, t('undefined_property', :prop => k, :class => self.class.name)
65
+ unless options[:without_protection]
66
+ if method(:sanitize_for_mass_assignment).arity == 1 # ActiveModel 3.0
67
+ if options[:as]
68
+ raise ArgumentError, t('mass_assignment_roles_unsupported')
67
69
  end
70
+ attrs = sanitize_for_mass_assignment(attrs)
71
+ else
72
+ mass_assignment_role = (options[:as] || :default)
73
+ attrs = sanitize_for_mass_assignment(attrs, mass_assignment_role)
68
74
  end
69
75
  end
70
76
 
71
- # @private
72
- def raw_attributes=(attrs)
73
- raise ArgumentError, t('attribute_hash') unless Hash === attrs
74
- attrs.each do |k,v|
75
- next if k.to_sym == :key
76
- if respond_to?("#{k}=")
77
- __send__("#{k}=",v)
78
- else
79
- __send__(:attribute=,k,v)
80
- end
77
+ attrs.each do |k,v|
78
+ if respond_to?("#{k}=")
79
+ __send__("#{k}=",v)
80
+ else
81
+ raise ArgumentError, t('undefined_property', :prop => k, :class => self.class.name)
81
82
  end
82
83
  end
84
+ end
83
85
 
84
- # @private
85
- def initialize(attrs={})
86
- super()
87
- @attributes = attributes_from_property_defaults
88
- self.attributes = attrs
89
- yield self if block_given?
90
- end
86
+ # Mass assign the document's attributes.
87
+ # @param [Hash] attrs the attributes to assign
88
+ def attributes=(attrs)
89
+ assign_attributes(attrs)
90
+ end
91
91
 
92
- # @private
93
- def method_missing(method, *args, &block)
94
- self.class.define_attribute_methods
95
- super
92
+ # @private
93
+ def raw_attributes=(attrs)
94
+ raise ArgumentError, t('attribute_hash') unless Hash === attrs
95
+ attrs.each do |k,v|
96
+ next if k.to_sym == :key
97
+ if respond_to?("#{k}=")
98
+ __send__("#{k}=",v)
99
+ else
100
+ __send__(:attribute=,k,v)
101
+ end
96
102
  end
103
+ end
97
104
 
98
- # @private
99
- def respond_to?(*args)
100
- self.class.define_attribute_methods
101
- super
102
- end
105
+ # @private
106
+ def initialize(attrs={}, options={})
107
+ super()
108
+ @attributes = attributes_from_property_defaults
109
+ assign_attributes(attrs, options)
110
+ yield self if block_given?
111
+ end
103
112
 
104
- protected
105
- # @private
106
- def attribute_method?(attr_name)
107
- self.class.properties.include?(attr_name)
108
- end
113
+ protected
114
+ # @private
115
+ def attribute_method?(attr_name)
116
+ self.class.properties.include?(attr_name)
117
+ end
109
118
 
110
- def attributes_from_property_defaults
111
- self.class.properties.values.inject({}) do |hash, prop|
112
- hash[prop.key] = prop.default unless prop.default.nil?
113
- hash
114
- end.with_indifferent_access
115
- end
119
+ def attributes_from_property_defaults
120
+ self.class.properties.values.inject({}) do |hash, prop|
121
+ hash[prop.key] = prop.default unless prop.default.nil?
122
+ hash
123
+ end.with_indifferent_access
116
124
  end
117
125
  end
118
126
  end