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.
- data/.gitignore +10 -6
- data/Gemfile +6 -15
- data/Gemfile.rails30 +3 -0
- data/Gemfile.rails31 +3 -0
- data/Gemfile.rails32 +3 -0
- data/Guardfile +3 -1
- data/LICENSE +16 -0
- data/README.markdown +173 -0
- data/RELEASE_NOTES.textile +286 -0
- data/Rakefile +19 -0
- data/lib/rails/generators/ripple/configuration/templates/ripple.yml +1 -0
- data/lib/rails/generators/ripple/model/model_generator.rb +1 -1
- data/lib/rails/generators/ripple/model/templates/{model.rb → model.rb.erb} +0 -0
- data/lib/rails/generators/ripple/observer/observer_generator.rb +1 -1
- data/lib/rails/generators/ripple/observer/templates/{observer.rb → observer.rb.erb} +0 -2
- data/lib/rails/generators/ripple/test/templates/cucumber.rb.erb +7 -0
- data/lib/rails/generators/ripple/test/test_generator.rb +17 -13
- data/lib/rails/generators/ripple_generator.rb +1 -0
- data/lib/ripple/associations.rb +65 -55
- data/lib/ripple/associations/embedded.rb +1 -1
- data/lib/ripple/associations/linked.rb +1 -1
- data/lib/ripple/associations/many.rb +1 -1
- data/lib/ripple/associations/many_embedded_proxy.rb +3 -2
- data/lib/ripple/associations/many_linked_proxy.rb +1 -1
- data/lib/ripple/associations/many_reference_proxy.rb +7 -5
- data/lib/ripple/associations/proxy.rb +2 -2
- data/lib/ripple/attribute_methods.rb +69 -61
- data/lib/ripple/attribute_methods/dirty.rb +2 -2
- data/lib/ripple/attribute_methods/read.rb +4 -2
- data/lib/ripple/callbacks.rb +23 -26
- data/lib/ripple/conflict/basic_resolver.rb +6 -2
- data/lib/ripple/conflict/document_hooks.rb +26 -0
- data/lib/ripple/conflict/resolver.rb +10 -2
- data/lib/ripple/conflict/test_helper.rb +3 -2
- data/lib/ripple/conversion.rb +1 -0
- data/lib/ripple/core_ext.rb +1 -0
- data/lib/ripple/core_ext/casting.rb +2 -0
- data/lib/ripple/core_ext/indexes.rb +89 -0
- data/lib/ripple/document.rb +23 -22
- data/lib/ripple/document/key.rb +12 -14
- data/lib/ripple/document/persistence.rb +99 -84
- data/lib/ripple/embedded_document.rb +9 -10
- data/lib/ripple/embedded_document/persistence.rb +42 -44
- data/lib/ripple/i18n.rb +4 -1
- data/lib/ripple/indexes.rb +151 -0
- data/lib/ripple/locale/en.yml +4 -0
- data/lib/ripple/locale/fr.yml +24 -0
- data/lib/ripple/nested_attributes.rb +92 -90
- data/lib/ripple/properties.rb +2 -1
- data/lib/ripple/railtie.rb +9 -0
- data/lib/ripple/railties/ripple.rake +32 -15
- data/lib/ripple/serialization.rb +50 -52
- data/lib/ripple/test_server.rb +1 -2
- data/lib/ripple/timestamps.rb +6 -8
- data/lib/ripple/validations.rb +19 -21
- data/lib/ripple/version.rb +1 -1
- data/ripple.gemspec +6 -5
- data/spec/generators/ripple/configuration_generator_spec.rb +9 -0
- data/spec/generators/ripple/js_generator_spec.rb +14 -0
- data/spec/generators/ripple/model_generator_spec.rb +64 -0
- data/spec/generators/ripple/observer_generator_spec.rb +20 -0
- data/spec/generators/ripple/test_generator_spec.rb +116 -0
- data/spec/generators/ripple_generator_spec.rb +11 -0
- data/spec/integration/ripple/conflict_resolution_spec.rb +35 -4
- data/spec/integration/ripple/indexes_spec.rb +47 -0
- data/spec/ripple/associations/many_embedded_proxy_spec.rb +50 -60
- data/spec/ripple/associations/many_linked_proxy_spec.rb +2 -2
- data/spec/ripple/associations/many_reference_proxy_spec.rb +1 -1
- data/spec/ripple/associations_spec.rb +16 -7
- data/spec/ripple/attribute_methods_spec.rb +43 -2
- data/spec/ripple/callbacks_spec.rb +120 -101
- data/spec/ripple/conversion_spec.rb +5 -13
- data/spec/ripple/core_ext_spec.rb +93 -15
- data/spec/ripple/finders_spec.rb +0 -2
- data/spec/ripple/indexes_spec.rb +111 -0
- data/spec/ripple/observable_spec.rb +1 -2
- data/spec/ripple/persistence_spec.rb +55 -32
- data/spec/ripple/properties_spec.rb +1 -1
- data/spec/ripple/ripple_spec.rb +5 -5
- data/spec/ripple/timestamps_spec.rb +9 -2
- data/spec/ripple/validations_spec.rb +50 -52
- data/spec/spec_helper.rb +9 -2
- data/spec/support/generator_setup.rb +26 -0
- data/spec/support/models.rb +1 -0
- data/spec/support/models/box.rb +1 -0
- data/spec/support/models/clock.rb +1 -1
- data/spec/support/models/indexer.rb +26 -0
- data/spec/support/models/post.rb +3 -2
- data/spec/support/models/widget.rb +2 -0
- data/spec/support/search.rb +2 -2
- data/spec/support/test_server.rb +23 -11
- data/spec/support/test_server.yml.example +1 -1
- metadata +159 -135
- 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]
|
File without changes
|
@@ -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
|
@@ -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?(
|
10
|
-
|
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?(
|
17
|
-
|
18
|
-
|
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 =>
|
21
|
-
"\n config.before(:
|
22
|
-
|
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?(
|
30
|
-
|
31
|
-
|
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
|
data/lib/ripple/associations.rb
CHANGED
@@ -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
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
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
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
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
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
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
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
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
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
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
|
@@ -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
|
@@ -10,9 +10,10 @@ module Ripple
|
|
10
10
|
|
11
11
|
def <<(docs)
|
12
12
|
load_target
|
13
|
-
|
13
|
+
docs = Array.wrap(docs)
|
14
|
+
@reflection.verify_type!(docs, @owner)
|
14
15
|
assign_references(docs)
|
15
|
-
@target +=
|
16
|
+
@target += docs
|
16
17
|
self
|
17
18
|
end
|
18
19
|
|
@@ -9,11 +9,12 @@ module Ripple
|
|
9
9
|
include Many
|
10
10
|
|
11
11
|
def <<(value)
|
12
|
-
|
12
|
+
values = Array.wrap(value)
|
13
|
+
@reflection.verify_type!(values, @owner)
|
13
14
|
|
14
|
-
assign_key(
|
15
|
+
values.each {|v| assign_key(v) }
|
15
16
|
load_target
|
16
|
-
@target
|
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
|
-
|
32
|
-
|
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
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
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
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
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
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
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
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
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
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
113
|
+
protected
|
114
|
+
# @private
|
115
|
+
def attribute_method?(attr_name)
|
116
|
+
self.class.properties.include?(attr_name)
|
117
|
+
end
|
109
118
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
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
|