datamapper 0.2.5 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +5 -1
- data/FAQ +96 -0
- data/QUICKLINKS +12 -0
- data/README +57 -155
- data/environment.rb +61 -43
- data/example.rb +30 -12
- data/lib/data_mapper.rb +6 -1
- data/lib/data_mapper/adapters/abstract_adapter.rb +0 -57
- data/lib/data_mapper/adapters/data_object_adapter.rb +203 -97
- data/lib/data_mapper/adapters/mysql_adapter.rb +4 -0
- data/lib/data_mapper/adapters/postgresql_adapter.rb +7 -1
- data/lib/data_mapper/adapters/sql/coersion.rb +3 -2
- data/lib/data_mapper/adapters/sql/commands/load_command.rb +29 -10
- data/lib/data_mapper/adapters/sql/mappings/associations_set.rb +4 -0
- data/lib/data_mapper/adapters/sql/mappings/column.rb +13 -9
- data/lib/data_mapper/adapters/sql/mappings/conditions.rb +172 -0
- data/lib/data_mapper/adapters/sql/mappings/table.rb +43 -17
- data/lib/data_mapper/adapters/sqlite3_adapter.rb +9 -2
- data/lib/data_mapper/associations.rb +75 -3
- data/lib/data_mapper/associations/belongs_to_association.rb +70 -36
- data/lib/data_mapper/associations/has_and_belongs_to_many_association.rb +195 -86
- data/lib/data_mapper/associations/has_many_association.rb +168 -61
- data/lib/data_mapper/associations/has_n_association.rb +23 -3
- data/lib/data_mapper/attributes.rb +73 -0
- data/lib/data_mapper/auto_migrations.rb +2 -6
- data/lib/data_mapper/base.rb +5 -9
- data/lib/data_mapper/database.rb +4 -3
- data/lib/data_mapper/embedded_value.rb +66 -30
- data/lib/data_mapper/identity_map.rb +1 -3
- data/lib/data_mapper/is/tree.rb +121 -0
- data/lib/data_mapper/migration.rb +155 -0
- data/lib/data_mapper/persistence.rb +532 -218
- data/lib/data_mapper/property.rb +306 -0
- data/lib/data_mapper/query.rb +164 -0
- data/lib/data_mapper/support/blank.rb +2 -2
- data/lib/data_mapper/support/connection_pool.rb +5 -6
- data/lib/data_mapper/support/enumerable.rb +3 -3
- data/lib/data_mapper/support/errors.rb +10 -1
- data/lib/data_mapper/support/inflector.rb +174 -238
- data/lib/data_mapper/support/object.rb +54 -0
- data/lib/data_mapper/support/serialization.rb +19 -1
- data/lib/data_mapper/support/string.rb +7 -16
- data/lib/data_mapper/support/symbol.rb +3 -15
- data/lib/data_mapper/support/typed_set.rb +68 -0
- data/lib/data_mapper/types/base.rb +44 -0
- data/lib/data_mapper/types/string.rb +34 -0
- data/lib/data_mapper/validations/number_validator.rb +40 -0
- data/lib/data_mapper/validations/string_validator.rb +20 -0
- data/lib/data_mapper/validations/validator.rb +13 -0
- data/performance.rb +26 -1
- data/profile_data_mapper.rb +1 -1
- data/rakefile.rb +42 -2
- data/spec/acts_as_tree_spec.rb +11 -3
- data/spec/adapters/data_object_adapter_spec.rb +31 -0
- data/spec/associations/belongs_to_association_spec.rb +98 -0
- data/spec/associations/has_and_belongs_to_many_association_spec.rb +377 -0
- data/spec/associations/has_many_association_spec.rb +337 -0
- data/spec/attributes_spec.rb +23 -1
- data/spec/auto_migrations_spec.rb +86 -29
- data/spec/callbacks_spec.rb +107 -0
- data/spec/column_spec.rb +5 -2
- data/spec/count_command_spec.rb +33 -1
- data/spec/database_spec.rb +18 -0
- data/spec/dependency_spec.rb +4 -2
- data/spec/embedded_value_spec.rb +8 -8
- data/spec/fixtures/people.yaml +1 -1
- data/spec/fixtures/projects.yaml +10 -1
- data/spec/fixtures/tasks.yaml +6 -0
- data/spec/fixtures/tasks_tasks.yaml +2 -0
- data/spec/fixtures/tomatoes.yaml +1 -0
- data/spec/is_a_tree_spec.rb +149 -0
- data/spec/load_command_spec.rb +71 -9
- data/spec/magic_columns_spec.rb +17 -2
- data/spec/migration_spec.rb +267 -0
- data/spec/models/animal.rb +1 -1
- data/spec/models/candidate.rb +8 -0
- data/spec/models/career.rb +1 -1
- data/spec/models/chain.rb +8 -0
- data/spec/models/comment.rb +1 -1
- data/spec/models/exhibit.rb +1 -1
- data/spec/models/fence.rb +7 -0
- data/spec/models/fruit.rb +2 -2
- data/spec/models/job.rb +8 -0
- data/spec/models/person.rb +2 -3
- data/spec/models/post.rb +1 -1
- data/spec/models/project.rb +21 -1
- data/spec/models/section.rb +1 -1
- data/spec/models/serializer.rb +1 -1
- data/spec/models/task.rb +9 -0
- data/spec/models/tomato.rb +27 -0
- data/spec/models/user.rb +8 -2
- data/spec/models/zoo.rb +2 -7
- data/spec/paranoia_spec.rb +1 -1
- data/spec/{base_spec.rb → persistence_spec.rb} +207 -18
- data/spec/postgres_spec.rb +48 -6
- data/spec/property_spec.rb +90 -9
- data/spec/query_spec.rb +71 -5
- data/spec/save_command_spec.rb +11 -0
- data/spec/spec_helper.rb +14 -11
- data/spec/support/blank_spec.rb +8 -0
- data/spec/support/inflector_spec.rb +41 -0
- data/spec/support/object_spec.rb +9 -0
- data/spec/{serialization_spec.rb → support/serialization_spec.rb} +1 -1
- data/spec/support/silence_spec.rb +15 -0
- data/spec/{support_spec.rb → support/string_spec.rb} +3 -3
- data/spec/support/struct_spec.rb +12 -0
- data/spec/support/typed_set_spec.rb +66 -0
- data/spec/table_spec.rb +3 -3
- data/spec/types/string.rb +81 -0
- data/spec/validates_uniqueness_of_spec.rb +17 -0
- data/spec/validations/number_validator.rb +59 -0
- data/spec/validations/string_validator.rb +14 -0
- metadata +59 -17
- data/do_performance.rb +0 -153
- data/lib/data_mapper/support/active_record_impersonation.rb +0 -103
- data/lib/data_mapper/support/weak_hash.rb +0 -46
- data/spec/active_record_impersonation_spec.rb +0 -129
- data/spec/associations_spec.rb +0 -232
- data/spec/conditions_spec.rb +0 -49
- data/spec/has_many_association_spec.rb +0 -173
- data/spec/models/animals_exhibit.rb +0 -8
@@ -2,105 +2,156 @@ require 'data_mapper/associations/has_n_association'
|
|
2
2
|
|
3
3
|
module DataMapper
|
4
4
|
module Associations
|
5
|
-
|
5
|
+
|
6
6
|
class HasManyAssociation < HasNAssociation
|
7
|
-
|
7
|
+
|
8
|
+
def dependency
|
9
|
+
@options[:dependent]
|
10
|
+
end
|
11
|
+
|
8
12
|
# Define the association instance method (i.e. Project#tasks)
|
9
13
|
def define_accessor(klass)
|
10
14
|
klass.class_eval <<-EOS
|
11
15
|
def #{@association_name}
|
12
16
|
@#{@association_name} || (@#{@association_name} = DataMapper::Associations::HasManyAssociation::Set.new(self, #{@association_name.inspect}))
|
13
17
|
end
|
14
|
-
|
18
|
+
|
15
19
|
def #{@association_name}=(value)
|
16
20
|
#{@association_name}.set(value)
|
17
21
|
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def #{@association_name}_keys=(value)
|
25
|
+
#{@association_name}.clear
|
26
|
+
|
27
|
+
associated_constant = #{@association_name}.association.associated_constant
|
28
|
+
associated_table = #{@association_name}.association.associated_table
|
29
|
+
associated_constant.all(associated_table.key => [*value]).each do |entry|
|
30
|
+
#{@association_name} << entry
|
31
|
+
end
|
32
|
+
end
|
18
33
|
EOS
|
19
34
|
end
|
20
|
-
|
35
|
+
|
21
36
|
def to_disassociate_sql
|
22
37
|
"UPDATE #{associated_table.to_sql} SET #{foreign_key_column.to_sql} = NULL WHERE #{foreign_key_column.to_sql} = ?"
|
23
38
|
end
|
24
|
-
|
39
|
+
|
40
|
+
def to_delete_sql
|
41
|
+
"DELETE FROM #{associated_table.to_sql} WHERE #{foreign_key_column.to_sql} = ?"
|
42
|
+
end
|
43
|
+
|
25
44
|
def instance_variable_name
|
26
45
|
class << self
|
27
46
|
attr_reader :instance_variable_name
|
28
47
|
end
|
29
|
-
|
48
|
+
|
30
49
|
@instance_variable_name = "@#{@association_name}"
|
31
50
|
end
|
32
|
-
|
51
|
+
|
33
52
|
class Set < Associations::Reference
|
34
|
-
|
53
|
+
|
35
54
|
include Enumerable
|
36
|
-
|
37
|
-
|
38
|
-
|
55
|
+
|
56
|
+
# Returns true if the association has zero items
|
57
|
+
def nil?
|
58
|
+
loaded_members.blank?
|
59
|
+
end
|
60
|
+
|
61
|
+
def dirty?(cleared = ::Set.new)
|
62
|
+
loaded_members.any? { |member| cleared.include?(member) || member.dirty?(cleared) }
|
39
63
|
end
|
40
|
-
|
64
|
+
|
41
65
|
def validate_recursively(event, cleared)
|
42
|
-
|
66
|
+
loaded_members.all? { |member| cleared.include?(member) || member.validate_recursively(event, cleared) }
|
43
67
|
end
|
44
|
-
|
45
|
-
def save_without_validation(database_context)
|
46
|
-
|
68
|
+
|
69
|
+
def save_without_validation(database_context, cleared)
|
70
|
+
|
47
71
|
adapter = @instance.database_context.adapter
|
48
|
-
|
72
|
+
|
73
|
+
members = loaded_members
|
74
|
+
|
49
75
|
adapter.connection do |db|
|
50
|
-
|
51
|
-
|
76
|
+
|
77
|
+
sql = association.to_disassociate_sql
|
78
|
+
parameters = [@instance.key]
|
79
|
+
|
80
|
+
member_keys = members.map { |member| member.key }.compact
|
81
|
+
|
82
|
+
unless member_keys.empty?
|
83
|
+
sql << " AND #{association.associated_table.key} NOT IN ?"
|
84
|
+
parameters << member_keys
|
85
|
+
end
|
86
|
+
|
87
|
+
db.create_command(sql).execute_non_query(*parameters)
|
52
88
|
end
|
53
|
-
|
54
|
-
unless
|
55
|
-
|
56
|
-
|
89
|
+
|
90
|
+
unless members.blank?
|
91
|
+
|
57
92
|
setter_method = "#{@association_name}=".to_sym
|
58
93
|
ivar_name = association.foreign_key_column.instance_variable_name
|
59
|
-
|
60
|
-
|
61
|
-
|
94
|
+
original_value_name = association.foreign_key_column.name
|
95
|
+
|
96
|
+
members.each do |member|
|
97
|
+
member.original_values.delete(original_value_name)
|
98
|
+
member.instance_variable_set(ivar_name, @instance.key)
|
99
|
+
@instance.database_context.adapter.save_without_validation(database_context, member, cleared)
|
62
100
|
end
|
63
101
|
end
|
64
102
|
end
|
65
|
-
|
103
|
+
|
66
104
|
def each
|
67
105
|
items.each { |item| yield item }
|
68
106
|
end
|
69
|
-
|
70
|
-
def <<(associated_item)
|
71
|
-
(@items || @items = []) << associated_item
|
72
|
-
|
73
|
-
# TODO: Optimize!
|
74
|
-
fk = association.foreign_key_column
|
75
|
-
foreign_association = association.associated_table.associations.find do |mapping|
|
76
|
-
mapping.is_a?(BelongsToAssociation) && mapping.foreign_key_column == fk
|
77
|
-
end
|
78
|
-
|
79
|
-
associated_item.send("#{foreign_association.name}=", @instance) if foreign_association
|
80
|
-
|
81
|
-
return @items
|
82
|
-
end
|
83
107
|
|
108
|
+
# Builds a new item and returns it.
|
84
109
|
def build(options)
|
85
110
|
item = association.associated_constant.new(options)
|
86
111
|
self << item
|
87
112
|
item
|
88
113
|
end
|
89
114
|
|
115
|
+
# Builds and saves a new item, then returns it.
|
90
116
|
def create(options)
|
91
117
|
item = build(options)
|
92
118
|
item.save
|
93
119
|
item
|
94
120
|
end
|
95
|
-
|
121
|
+
|
96
122
|
def set(value)
|
97
123
|
values = value.is_a?(Enumerable) ? value : [value]
|
98
|
-
@items =
|
124
|
+
@items = Support::TypedSet.new(association.associated_constant)
|
99
125
|
values.each do |item|
|
100
126
|
self << item
|
101
127
|
end
|
102
128
|
end
|
103
|
-
|
129
|
+
|
130
|
+
# Adds a new item to the association. The entire item collection is then returned.
|
131
|
+
def <<(member)
|
132
|
+
shallow_append(member)
|
133
|
+
|
134
|
+
if complement = association.complementary_association
|
135
|
+
member.send("#{complement.name}_association").shallow_append(@instance)
|
136
|
+
end
|
137
|
+
|
138
|
+
return self
|
139
|
+
end
|
140
|
+
|
141
|
+
def clear
|
142
|
+
@pending_members = nil
|
143
|
+
@items = Support::TypedSet.new(association.associated_constant)
|
144
|
+
end
|
145
|
+
|
146
|
+
def shallow_append(member)
|
147
|
+
if @items
|
148
|
+
self.items << member
|
149
|
+
else
|
150
|
+
pending_members << member
|
151
|
+
end
|
152
|
+
return self
|
153
|
+
end
|
154
|
+
|
104
155
|
def method_missing(symbol, *args, &block)
|
105
156
|
if items.respond_to?(symbol)
|
106
157
|
items.send(symbol, *args, &block)
|
@@ -112,17 +163,17 @@ module DataMapper
|
|
112
163
|
end
|
113
164
|
end
|
114
165
|
results.flatten
|
115
|
-
elsif items.size == 1 && items.first.respond_to?(symbol)
|
116
|
-
items.first.send(symbol, *args, &block)
|
166
|
+
elsif items.size == 1 && items.entries.first.respond_to?(symbol)
|
167
|
+
items.entries.first.send(symbol, *args, &block)
|
117
168
|
else
|
118
169
|
super
|
119
170
|
end
|
120
171
|
end
|
121
|
-
|
172
|
+
|
122
173
|
def respond_to?(symbol)
|
123
174
|
items.respond_to?(symbol) || super
|
124
175
|
end
|
125
|
-
|
176
|
+
|
126
177
|
def reload!
|
127
178
|
@items = nil
|
128
179
|
end
|
@@ -130,10 +181,10 @@ module DataMapper
|
|
130
181
|
def items
|
131
182
|
@items || begin
|
132
183
|
if @instance.loaded_set.nil?
|
133
|
-
@items =
|
134
|
-
else
|
184
|
+
@items = Support::TypedSet.new(association.associated_constant)
|
185
|
+
else
|
135
186
|
associated_items = fetch_sets
|
136
|
-
|
187
|
+
|
137
188
|
# This is where @items is set, by calling association=,
|
138
189
|
# which in turn calls HasManyAssociation::Set#set.
|
139
190
|
association_ivar_name = association.instance_variable_name
|
@@ -141,36 +192,92 @@ module DataMapper
|
|
141
192
|
@instance.loaded_set.each do |entry|
|
142
193
|
entry.send(setter_method, associated_items[entry.key])
|
143
194
|
end # @instance.loaded_set.each
|
144
|
-
|
145
|
-
return @items
|
146
195
|
end # if @instance.loaded_set.nil?
|
196
|
+
|
197
|
+
if @pending_members
|
198
|
+
pending_members.each do |member|
|
199
|
+
@items << member
|
200
|
+
end
|
201
|
+
|
202
|
+
pending_members.clear
|
203
|
+
end
|
204
|
+
|
205
|
+
return @items
|
147
206
|
end # begin
|
148
207
|
end # def items
|
149
|
-
|
208
|
+
|
150
209
|
def inspect
|
151
210
|
entries.inspect
|
152
211
|
end
|
153
|
-
|
212
|
+
|
213
|
+
def first
|
214
|
+
items.entries.first
|
215
|
+
end
|
216
|
+
|
217
|
+
def last
|
218
|
+
items.entries.last
|
219
|
+
end
|
220
|
+
|
154
221
|
def ==(other)
|
155
|
-
(items.size == 1 ?
|
222
|
+
(items.size == 1 ? first : items) == other
|
223
|
+
end
|
224
|
+
|
225
|
+
def deactivate
|
226
|
+
case association.dependency
|
227
|
+
when :destroy
|
228
|
+
items.entries.each do |member|
|
229
|
+
status = member.destroy! unless member.new_record?
|
230
|
+
return false unless status
|
231
|
+
end
|
232
|
+
when :delete
|
233
|
+
@instance.database_context.adapter.connection do |db|
|
234
|
+
sql = association.to_delete_sql
|
235
|
+
parameters = [@instance.key]
|
236
|
+
db.create_command(sql).execute_non_query(*parameters)
|
237
|
+
end
|
238
|
+
when :protect
|
239
|
+
unless items.empty?
|
240
|
+
raise AssociationProtectedError.new("You cannot delete this model while it has items associated with it.")
|
241
|
+
end
|
242
|
+
when :nullify
|
243
|
+
nullify_association
|
244
|
+
else
|
245
|
+
nullify_association
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
def nullify_association
|
250
|
+
@instance.database_context.adapter.connection do |db|
|
251
|
+
sql = association.to_disassociate_sql
|
252
|
+
parameters = [@instance.key]
|
253
|
+
db.create_command(sql).execute_non_query(*parameters)
|
254
|
+
end
|
156
255
|
end
|
157
|
-
|
256
|
+
|
158
257
|
private
|
258
|
+
def loaded_members
|
259
|
+
pending_members + @items
|
260
|
+
end
|
261
|
+
|
262
|
+
def pending_members
|
263
|
+
@pending_members || @pending_members = Support::TypedSet.new(association.associated_constant)
|
264
|
+
end
|
265
|
+
|
159
266
|
def fetch_sets
|
160
267
|
finder_options = { association.foreign_key_column.to_sym => @instance.loaded_set.map { |item| item.key } }
|
161
268
|
finder_options.merge!(association.finder_options)
|
162
|
-
|
269
|
+
|
163
270
|
foreign_key_ivar_name = association.foreign_key_column.instance_variable_name
|
164
|
-
|
271
|
+
|
165
272
|
@instance.database_context.all(
|
166
273
|
association.associated_constant,
|
167
274
|
finder_options
|
168
275
|
).group_by { |entry| entry.instance_variable_get(foreign_key_ivar_name) }
|
169
276
|
end
|
170
|
-
|
277
|
+
|
171
278
|
end
|
172
279
|
|
173
280
|
end
|
174
|
-
|
281
|
+
|
175
282
|
end
|
176
283
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
module DataMapper
|
2
2
|
|
3
3
|
class ForeignKeyNotFoundError < StandardError; end
|
4
|
+
class AssociationProtectedError < StandardError; end
|
4
5
|
|
5
6
|
module Associations
|
6
7
|
|
@@ -11,7 +12,8 @@ module DataMapper
|
|
11
12
|
OPTIONS = [
|
12
13
|
:class,
|
13
14
|
:class_name,
|
14
|
-
:foreign_key
|
15
|
+
:foreign_key,
|
16
|
+
:dependent
|
15
17
|
]
|
16
18
|
|
17
19
|
def initialize(klass, association_name, options)
|
@@ -106,6 +108,24 @@ module DataMapper
|
|
106
108
|
associated_table.columns.reject { |column| column.lazy? }
|
107
109
|
end
|
108
110
|
|
111
|
+
def complementary_association
|
112
|
+
@complementary_association || begin
|
113
|
+
@complementary_association = associated_table.associations.find do |mapping|
|
114
|
+
mapping.is_a?(BelongsToAssociation) &&
|
115
|
+
mapping.foreign_key_column == foreign_key_column &&
|
116
|
+
mapping.key_table.name == key_table.name
|
117
|
+
end
|
118
|
+
|
119
|
+
if @complementary_association
|
120
|
+
class << self
|
121
|
+
attr_accessor :complementary_association
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
return @complementary_association
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
109
129
|
def finder_options
|
110
130
|
@finder_options || @finder_options = @options.reject { |k,v| self.class::OPTIONS.include?(k) }
|
111
131
|
end
|
@@ -114,10 +134,10 @@ module DataMapper
|
|
114
134
|
"JOIN #{associated_table.to_sql} ON #{foreign_key_column.to_sql(true)} = #{primary_key_column.to_sql(true)}"
|
115
135
|
end
|
116
136
|
|
117
|
-
def activate!
|
137
|
+
def activate!(force = false)
|
118
138
|
foreign_key_column
|
119
139
|
end
|
120
140
|
end
|
121
141
|
|
122
142
|
end
|
123
|
-
end
|
143
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module DataMapper
|
2
|
+
|
3
|
+
module Attributes
|
4
|
+
|
5
|
+
def self.included(klass)
|
6
|
+
klass.const_set('ATTRIBUTES', Set.new) unless klass.const_defined?('ATTRIBUTES')
|
7
|
+
end
|
8
|
+
|
9
|
+
def attributes
|
10
|
+
__get_attributes(true)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Mass-assign mapped fields.
|
14
|
+
def attributes=(values_hash)
|
15
|
+
__set_attributes(values_hash, true)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def __method_defined?(name, public_only = true)
|
21
|
+
if public_only
|
22
|
+
self.class.public_method_defined?(name)
|
23
|
+
else
|
24
|
+
self.class.private_method_defined?(name) ||
|
25
|
+
self.class.protected_method_defined?(name) ||
|
26
|
+
self.class.public_method_defined?(name)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def __get_attributes(public_only)
|
31
|
+
pairs = {}
|
32
|
+
|
33
|
+
self.class::ATTRIBUTES.each do |name|
|
34
|
+
getter = if __method_defined?(name, public_only)
|
35
|
+
name
|
36
|
+
elsif __method_defined?(name.to_s.ensure_ends_with('?'), public_only)
|
37
|
+
name.to_s.ensure_ends_with('?')
|
38
|
+
else
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
|
42
|
+
if getter
|
43
|
+
value = send(getter)
|
44
|
+
pairs[name] = value.is_a?(Class) ? value.to_s : value
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
pairs
|
49
|
+
end
|
50
|
+
|
51
|
+
def __set_attributes(values_hash, public_only)
|
52
|
+
values_hash.each_pair do |k,v|
|
53
|
+
setter_name = k.to_s.sub(/\?$/, '').ensure_ends_with('=')
|
54
|
+
if __method_defined?(setter_name, public_only)
|
55
|
+
send(setter_name, v)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
self
|
60
|
+
end
|
61
|
+
|
62
|
+
# return all attributes, regardless of their visibility
|
63
|
+
def private_attributes
|
64
|
+
__get_attributes(false)
|
65
|
+
end
|
66
|
+
|
67
|
+
# private method for setting any/all attribute values, regardless of visibility
|
68
|
+
def private_attributes=(values_hash)
|
69
|
+
__set_attributes(values_hash, false)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|