active_mocker 2.0.0.beta1 → 2.0.0.pre1

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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -1
  3. data/README.md +11 -18
  4. data/lib/active_mocker.rb +5 -0
  5. data/lib/active_mocker/config.rb +7 -8
  6. data/lib/active_mocker/{mock → deprecated_components}/mock_abilities.rb +19 -7
  7. data/lib/active_mocker/deprecated_components/rspec.rb +12 -0
  8. data/lib/active_mocker/display_errors.rb +64 -0
  9. data/lib/active_mocker/error_object.rb +46 -0
  10. data/lib/active_mocker/generate.rb +30 -64
  11. data/lib/active_mocker/mock.rb +1 -2
  12. data/lib/active_mocker/mock/association.rb +0 -2
  13. data/lib/active_mocker/mock/base.rb +270 -256
  14. data/lib/active_mocker/mock/belongs_to.rb +14 -20
  15. data/lib/active_mocker/mock/collection.rb +0 -6
  16. data/lib/active_mocker/mock/do_nothing_active_record_methods.rb +39 -41
  17. data/lib/active_mocker/mock/exceptions.rb +4 -11
  18. data/lib/active_mocker/mock/has_and_belongs_to_many.rb +0 -2
  19. data/lib/active_mocker/mock/has_many.rb +4 -5
  20. data/lib/active_mocker/mock/has_one.rb +5 -11
  21. data/lib/active_mocker/mock/hash_process.rb +14 -17
  22. data/lib/active_mocker/mock/mock_relation.rb +10 -0
  23. data/lib/active_mocker/mock/object_inspect.rb +29 -32
  24. data/lib/active_mocker/mock/queries.rb +14 -18
  25. data/lib/active_mocker/mock/records.rb +45 -43
  26. data/lib/active_mocker/mock/relation.rb +1 -4
  27. data/lib/active_mocker/mock/single_relation.rb +13 -17
  28. data/lib/active_mocker/mock/template_methods.rb +1 -4
  29. data/lib/active_mocker/mock_creator.rb +4 -5
  30. data/lib/active_mocker/mock_template/_associations.erb +6 -6
  31. data/lib/active_mocker/mock_template/_class_methods.erb +1 -1
  32. data/lib/active_mocker/mock_template/_scopes.erb +3 -3
  33. data/lib/active_mocker/parent_class.rb +1 -1
  34. data/lib/active_mocker/public_methods.rb +5 -2
  35. data/lib/active_mocker/rspec.rb +0 -8
  36. data/lib/active_mocker/rspec_helper.rb +0 -2
  37. data/lib/active_mocker/task.rake +0 -2
  38. data/lib/active_mocker/version.rb +1 -1
  39. metadata +36 -19
  40. data/lib/active_mocker/logger.rb +0 -15
  41. data/lib/active_mocker/output_capture.rb +0 -32
@@ -7,19 +7,18 @@ require 'active_support/core_ext'
7
7
  require 'virtus'
8
8
 
9
9
  require 'active_mocker/version'
10
- require 'active_mocker/logger'
11
10
  require 'active_mocker/loaded_mocks'
12
11
  require 'active_mocker/mock/hash_process'
13
12
  require 'active_mocker/mock/collection'
14
13
  require 'active_mocker/mock/queries'
15
14
  require 'active_mocker/mock/relation'
15
+ require 'active_mocker/mock/mock_relation'
16
16
  require 'active_mocker/mock/association'
17
17
  require 'active_mocker/mock/has_many'
18
18
  require 'active_mocker/mock/single_relation'
19
19
  require 'active_mocker/mock/has_one'
20
20
  require 'active_mocker/mock/has_and_belongs_to_many'
21
21
  require 'active_mocker/mock/belongs_to'
22
- require 'active_mocker/mock/mock_abilities'
23
22
  require 'active_mocker/mock/exceptions'
24
23
  require 'active_mocker/mock/template_methods'
25
24
  require 'active_mocker/mock/do_nothing_active_record_methods'
@@ -1,6 +1,4 @@
1
1
  module ActiveMocker
2
- module Mock
3
2
  class Association < Relation
4
3
  end
5
- end
6
4
  end
@@ -1,325 +1,339 @@
1
1
  module ActiveMocker
2
- module Mock
3
- class Base
2
+ class Base
3
+ include DoNothingActiveRecordMethods
4
+ include TemplateMethods
5
+ extend Queries
4
6
 
5
- include DoNothingActiveRecordMethods
6
- include MockAbilities
7
- include TemplateMethods
8
- extend Queries
9
-
10
- def self.inherited(subclass)
11
- return ActiveMocker::LoadedMocks.send(:add, subclass) if subclass.superclass == Base
12
- end
13
-
14
- class << self
15
-
16
- # Creates an object (or multiple objects) and saves it to memory.
17
- #
18
- # The +attributes+ parameter can be either a Hash or an Array of Hashes. These Hashes describe the
19
- # attributes on the objects that are to be created.
20
- #
21
- # ==== Examples
22
- # # Create a single new object
23
- # User.create(first_name: 'Jamie')
24
- #
25
- # # Create an Array of new objects
26
- # User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }])
27
- #
28
- # # Create a single object and pass it into a block to set other attributes.
29
- # User.create(first_name: 'Jamie') do |u|
30
- # u.is_admin = false
31
- # end
32
- #
33
- # # Creating an Array of new objects using a block, where the block is executed for each object:
34
- # User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }]) do |u|
35
- # u.is_admin = false
36
- # end
37
- def create(attributes = {}, &block)
38
- if attributes.is_a?(Array)
39
- attributes.collect { |attr| create(attr, &block) }
40
- else
41
- record = new(id: attributes[:id] || attributes['id'])
42
- record.save
43
- record.assign_attributes(attributes, &block)
44
- record._create_caller_locations = caller_locations
45
- record
46
- end
47
- end
7
+ def self.inherited(subclass)
8
+ return ActiveMocker::LoadedMocks.send(:add, subclass) if subclass.superclass == Base
9
+ end
48
10
 
49
- alias_method :create!, :create
11
+ class << self
50
12
 
51
- def records
52
- @records ||= Records.new
13
+ # Creates an object (or multiple objects) and saves it to memory.
14
+ #
15
+ # The +attributes+ parameter can be either a Hash or an Array of Hashes. These Hashes describe the
16
+ # attributes on the objects that are to be created.
17
+ #
18
+ # ==== Examples
19
+ # # Create a single new object
20
+ # User.create(first_name: 'Jamie')
21
+ #
22
+ # # Create an Array of new objects
23
+ # User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }])
24
+ #
25
+ # # Create a single object and pass it into a block to set other attributes.
26
+ # User.create(first_name: 'Jamie') do |u|
27
+ # u.is_admin = false
28
+ # end
29
+ #
30
+ # # Creating an Array of new objects using a block, where the block is executed for each object:
31
+ # User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }]) do |u|
32
+ # u.is_admin = false
33
+ # end
34
+ def create(attributes = {}, &block)
35
+ if attributes.is_a?(Array)
36
+ attributes.collect { |attr| create(attr, &block) }
37
+ else
38
+ record = new(id: attributes.delete(:id) || attributes.delete("id"))
39
+ record.save
40
+ record.assign_attributes(attributes, &block)
41
+ record._create_caller_locations = caller_locations
42
+ record
53
43
  end
44
+ end
54
45
 
55
- private :records
56
-
57
- delegate :insert, :exists?, :to_a, :to => :records
58
- delegate :first, :last, :to => :all
59
-
60
- # Delete an object (or multiple objects) that has the given id.
61
- #
62
- # This essentially finds the object (or multiple objects) with the given id and then calls delete on it.
63
- #
64
- # ==== Parameters
65
- #
66
- # * +id+ - Can be either an Integer or an Array of Integers.
67
- #
68
- # ==== Examples
69
- #
70
- # # Destroy a single object
71
- # TodoMock.delete(1)
72
- #
73
- # # Destroy multiple objects
74
- # todos = [1,2,3]
75
- # TodoMock.delete(todos)
76
- def delete(id)
77
- if id.is_a?(Array)
78
- id.map { |one_id| delete(one_id) }
79
- else
80
- find(id).delete
81
- end
82
- end
46
+ alias_method :create!, :create
83
47
 
84
- alias_method :destroy, :delete
48
+ def records
49
+ @records ||= Records.new
50
+ end
85
51
 
86
- # Deletes the records matching +conditions+.
87
- #
88
- # Post.where(person_id: 5).where(category: ['Something', 'Else']).delete_all
89
- def delete_all(conditions=nil)
90
- return records.reset if conditions.nil?
91
- super
92
- end
52
+ private :records
93
53
 
94
- alias_method :destroy_all, :delete_all
54
+ delegate :insert, :exists?, :to_a, :to => :records
55
+ delegate :first, :last, :to => :all
95
56
 
96
- # @api private
97
- def from_limit?
98
- false
57
+ # Delete an object (or multiple objects) that has the given id.
58
+ #
59
+ # This essentially finds the object (or multiple objects) with the given id and then calls delete on it.
60
+ #
61
+ # ==== Parameters
62
+ #
63
+ # * +id+ - Can be either an Integer or an Array of Integers.
64
+ #
65
+ # ==== Examples
66
+ #
67
+ # # Destroy a single object
68
+ # TodoMock.delete(1)
69
+ #
70
+ # # Destroy multiple objects
71
+ # todos = [1,2,3]
72
+ # TodoMock.delete(todos)
73
+ def delete(id)
74
+ if id.is_a?(Array)
75
+ id.map { |one_id| delete(one_id) }
76
+ else
77
+ find(id).delete
99
78
  end
79
+ end
100
80
 
101
- def abstract_class?
102
- true
103
- end
81
+ alias_method :destroy, :delete
104
82
 
105
- def build_type(type)
106
- @@built_types ||= {}
107
- @@built_types[type] ||= Virtus::Attribute.build(type)
108
- end
83
+ # Deletes the records matching +conditions+.
84
+ #
85
+ # Post.where(person_id: 5).where(category: ['Something', 'Else']).delete_all
86
+ def delete_all(conditions=nil)
87
+ return records.reset if conditions.nil?
88
+ super
89
+ end
109
90
 
110
- def classes(klass)
111
- ActiveMocker::LoadedMocks.find(klass)
112
- end
91
+ alias_method :destroy_all, :delete_all
113
92
 
114
- def new_relation(collection)
115
- ScopeRelation.new(collection)
116
- end
93
+ # @api private
94
+ def from_limit?
95
+ false
96
+ end
117
97
 
118
- private :classes, :build_type, :new_relation
98
+ def abstract_class?
99
+ true
100
+ end
119
101
 
120
- public
102
+ def build_type(type)
103
+ @@built_types ||= {}
104
+ @@built_types[type] ||= Virtus::Attribute.build(type)
105
+ end
121
106
 
122
- def clear_mock
123
- clear_mocked_methods
124
- delete_all
125
- end
107
+ def classes(klass)
108
+ ActiveMocker::LoadedMocks.find(klass)
109
+ end
126
110
 
127
- def _find_associations_by_class(klass_name)
128
- associations_by_class[klass_name.to_s]
129
- end
111
+ # @param [Array<ActiveMocker::Base>] collection, an array of mock instances
112
+ # @return [ScopeRelation] for the given mock so that it will include any scoped methods
113
+ def __new_relation__(collection)
114
+ ScopeRelation.new(collection)
115
+ end
130
116
 
131
- def created_with(version)
132
- raise UpdateMocksError.new(self.name, version, ActiveMocker::VERSION) if version != ActiveMocker::VERSION
133
- end
117
+ private :classes, :build_type, :__new_relation__
134
118
 
135
- private :created_with
119
+ public
136
120
 
121
+ # @deprecated
122
+ def clear_mock
123
+ delete_all
137
124
  end
138
125
 
139
- def classes(klass)
140
- self.class.send(:classes, klass)
126
+ def _find_associations_by_class(klass_name)
127
+ associations_by_class[klass_name.to_s]
141
128
  end
142
129
 
143
- private :classes
130
+ private
144
131
 
145
- attr_reader :associations, :types, :attributes
146
- attr_accessor :_create_caller_locations
147
- # New objects can be instantiated as either empty (pass no construction parameter) or pre-set with
148
- # attributes but not yet saved (pass a hash with key names matching the associated table column names).
149
- # In both instances, valid attribute keys are determined by the column names of the associated table --
150
- # hence you can't have attributes that aren't part of the table columns.
151
- #
152
- # ==== Example:
153
- # # Instantiates a single new object
154
- # UserMock.new(first_name: 'Jamie')
155
- def initialize(attributes = {}, &block)
156
- if self.class.abstract_class?
157
- raise NotImplementedError, "#{self.class.name} is an abstract class and cannot be instantiated."
158
- end
159
- setup_instance_variables
160
- assign_attributes(attributes, &block)
132
+ def created_with(version)
133
+ raise UpdateMocksError.new(self.name, version, ActiveMocker::VERSION) if version != ActiveMocker::VERSION
161
134
  end
162
135
 
163
- def setup_instance_variables
164
- @types = self.class.send(:types)
165
- @attributes = self.class.send(:attributes).dup
166
- @associations = self.class.send(:associations).dup
136
+ # @deprecated
137
+ def call_mock_method(method:, caller:, arguments: [])
138
+ is_implemented(method, '::', caller)
167
139
  end
168
140
 
169
- private :setup_instance_variables
170
-
171
- def update(attributes={})
172
- assign_attributes(attributes)
141
+ # @deprecated
142
+ def is_implemented(method, type, call_stack)
143
+ raise NotImplementedError, "#{type}#{method} for Class: #{name}. To continue stub the method.", call_stack
173
144
  end
145
+ end
174
146
 
175
- # @api private
176
- def assign_attributes(new_attributes, &block)
177
- yield self if block_given?
178
- unless new_attributes.respond_to?(:stringify_keys)
179
- raise ArgumentError, "When assigning attributes, you must pass a hash as an argument."
180
- end
181
- return nil if new_attributes.blank?
182
- attributes = new_attributes.stringify_keys
183
- attributes.each do |k, v|
184
- _assign_attribute(k, v)
185
- end
186
- end
147
+ # @deprecated
148
+ def call_mock_method(method:, caller:, arguments: [])
149
+ self.class.send(:is_implemented, method, '#', caller)
150
+ end
187
151
 
188
- alias attributes= assign_attributes
152
+ private :call_mock_method
189
153
 
190
- # @api private
191
- def _assign_attribute(k, v)
192
- public_send("#{k}=", v)
193
- rescue NoMethodError
194
- if respond_to?("#{k}=")
195
- raise
196
- else
197
- raise UnknownAttributeError.new(self, k)
198
- end
199
- end
154
+ def classes(klass)
155
+ self.class.send(:classes, klass)
156
+ end
200
157
 
201
- def save(*args)
202
- unless self.class.exists?(self)
203
- self.class.send(:insert, self)
204
- end
205
- true
158
+ private :classes
159
+
160
+ attr_reader :associations, :types, :attributes
161
+ # @private
162
+ attr_accessor :_create_caller_locations
163
+ # New objects can be instantiated as either empty (pass no construction parameter) or pre-set with
164
+ # attributes but not yet saved (pass a hash with key names matching the associated table column names).
165
+ # In both instances, valid attribute keys are determined by the column names of the associated table --
166
+ # hence you can't have attributes that aren't part of the table columns.
167
+ #
168
+ # ==== Example:
169
+ # # Instantiates a single new object
170
+ # UserMock.new(first_name: 'Jamie')
171
+ def initialize(attributes = {}, &block)
172
+ if self.class.abstract_class?
173
+ raise NotImplementedError, "#{self.class.name} is an abstract class and cannot be instantiated."
206
174
  end
175
+ setup_instance_variables
176
+ assign_attributes(attributes, &block)
177
+ end
207
178
 
208
- alias save! save
179
+ def setup_instance_variables
180
+ @types = self.class.send(:types)
181
+ @attributes = self.class.send(:attributes).dup
182
+ @associations = self.class.send(:associations).dup
183
+ end
209
184
 
210
- def records
211
- self.class.send(:records)
212
- end
185
+ private :setup_instance_variables
213
186
 
214
- private :records
187
+ def update(attributes={})
188
+ assign_attributes(attributes)
189
+ end
215
190
 
216
- def delete
217
- records.delete(self)
191
+ # @api private
192
+ def assign_attributes(new_attributes, &block)
193
+ yield self if block_given?
194
+ unless new_attributes.respond_to?(:stringify_keys)
195
+ raise ArgumentError, "When assigning attributes, you must pass a hash as an argument."
218
196
  end
197
+ return nil if new_attributes.blank?
198
+ attributes = new_attributes.stringify_keys
199
+ attributes.each do |k, v|
200
+ _assign_attribute(k, v)
201
+ end
202
+ end
219
203
 
220
- alias_method :destroy, :delete
221
-
222
- delegate :[], :[]=, to: :attributes
204
+ alias attributes= assign_attributes
223
205
 
224
- # Returns true if this object hasn't been saved yet; otherwise, returns false.
225
- def new_record?
226
- records.new_record?(self)
206
+ # @api private
207
+ def _assign_attribute(k, v)
208
+ public_send("#{k}=", v)
209
+ rescue NoMethodError
210
+ if respond_to?("#{k}=")
211
+ raise
212
+ else
213
+ raise UnknownAttributeError.new(self, k)
227
214
  end
215
+ end
228
216
 
229
- # Indicates if the model is persisted. Default is +false+.
230
- #
231
- # person = Person.new(id: 1, name: 'bob')
232
- # person.persisted? # => false
233
- def persisted?
234
- records.persisted?(id)
217
+ def save(*args)
218
+ unless self.class.exists?(self)
219
+ self.class.send(:insert, self)
235
220
  end
221
+ true
222
+ end
236
223
 
237
- # Returns +true+ if the given attribute is in the attributes hash, otherwise +false+.
238
- #
239
- # person = Person.new
240
- # person.has_attribute?(:name) # => true
241
- # person.has_attribute?('age') # => true
242
- # person.has_attribute?(:nothing) # => false
243
- def has_attribute?(attr_name)
244
- @attributes.has_key?(attr_name.to_s)
245
- end
224
+ alias save! save
246
225
 
247
- # Returns +true+ if the specified +attribute+ has been set and is neither +nil+ nor <tt>empty?</tt> (the latter only applies
248
- # to objects that respond to <tt>empty?</tt>, most notably Strings). Otherwise, +false+.
249
- # Note that it always returns +true+ with boolean attributes.
250
- #
251
- # person = Task.new(title: '', is_done: false)
252
- # person.attribute_present?(:title) # => false
253
- # person.attribute_present?(:is_done) # => true
254
- # person.name = 'Francesco'
255
- # person.is_done = true
256
- # person.attribute_present?(:title) # => true
257
- # person.attribute_present?(:is_done) # => true
258
- def attribute_present?(attribute)
259
- value = read_attribute(attribute)
260
- !value.nil? && !(value.respond_to?(:empty?) && value.empty?)
261
- end
226
+ def records
227
+ self.class.send(:records)
228
+ end
262
229
 
263
- # Returns an array of names for the attributes available on this object.
264
- #
265
- # person = Person.new
266
- # person.attribute_names
267
- # # => ["id", "created_at", "updated_at", "name", "age"]
268
- def attribute_names
269
- self.class.attribute_names
270
- end
230
+ private :records
271
231
 
272
- def inspect
273
- ObjectInspect.new(self.class.name, attributes).to_s
274
- end
232
+ def delete
233
+ records.delete(self)
234
+ end
275
235
 
276
- # Will not allow attributes to be changed
277
- #
278
- # Will freeze attributes forever. Querying for the record again will not unfreeze it because records exist in memory
279
- # and are not initialized upon a query. This behaviour differs from ActiveRecord, beware of any side effect this may
280
- # have when using this method.
281
- def freeze
282
- @attributes.freeze; self
283
- end
236
+ alias_method :destroy, :delete
284
237
 
285
- module PropertiesGetterAndSetter
238
+ delegate :[], :[]=, to: :attributes
286
239
 
287
- # Returns the value of the attribute identified by <tt>attr_name</tt> after
288
- # it has been typecast (for example, "2004-12-12" in a date column is cast
289
- # to a date object, like Date.new(2004, 12, 12))
290
- def read_attribute(attr)
291
- @attributes[attr]
292
- end
240
+ # Returns true if this object hasn't been saved yet; otherwise, returns false.
241
+ def new_record?
242
+ records.new_record?(self)
243
+ end
293
244
 
294
- # Updates the attribute identified by <tt>attr_name</tt> with the
295
- # specified +value+. Empty strings for fixnum and float columns are
296
- # turned into +nil+.
297
- def write_attribute(attr, value)
298
- @attributes[attr] = types[attr].coerce(value)
299
- end
245
+ # Indicates if the model is persisted. Default is +false+.
246
+ #
247
+ # person = Person.new(id: 1, name: 'bob')
248
+ # person.persisted? # => false
249
+ def persisted?
250
+ records.persisted?(id)
251
+ end
300
252
 
301
- # @api private
302
- def read_association(attr, assign_if_value_nil=nil)
303
- @associations[attr.to_sym] ||= assign_if_value_nil.try(:call)
304
- end
253
+ # Returns +true+ if the given attribute is in the attributes hash, otherwise +false+.
254
+ #
255
+ # person = Person.new
256
+ # person.has_attribute?(:name) # => true
257
+ # person.has_attribute?('age') # => true
258
+ # person.has_attribute?(:nothing) # => false
259
+ def has_attribute?(attr_name)
260
+ @attributes.has_key?(attr_name.to_s)
261
+ end
305
262
 
306
- # @api private
307
- def write_association(attr, value)
308
- @associations[attr.to_sym] = value
309
- end
263
+ # Returns +true+ if the specified +attribute+ has been set and is neither +nil+ nor <tt>empty?</tt> (the latter only applies
264
+ # to objects that respond to <tt>empty?</tt>, most notably Strings). Otherwise, +false+.
265
+ # Note that it always returns +true+ with boolean attributes.
266
+ #
267
+ # person = Task.new(title: '', is_done: false)
268
+ # person.attribute_present?(:title) # => false
269
+ # person.attribute_present?(:is_done) # => true
270
+ # person.name = 'Francesco'
271
+ # person.is_done = true
272
+ # person.attribute_present?(:title) # => true
273
+ # person.attribute_present?(:is_done) # => true
274
+ def attribute_present?(attribute)
275
+ value = read_attribute(attribute)
276
+ !value.nil? && !(value.respond_to?(:empty?) && value.empty?)
277
+ end
278
+
279
+ # Returns an array of names for the attributes available on this object.
280
+ #
281
+ # person = Person.new
282
+ # person.attribute_names
283
+ # # => ["id", "created_at", "updated_at", "name", "age"]
284
+ def attribute_names
285
+ self.class.attribute_names
286
+ end
310
287
 
311
- protected :read_attribute, :write_attribute, :read_association, :write_association
288
+ def inspect
289
+ ObjectInspect.new(self.class.name, attributes).to_s
290
+ end
291
+
292
+ # Will not allow attributes to be changed
293
+ #
294
+ # Will freeze attributes forever. Querying for the record again will not unfreeze it because records exist in memory
295
+ # and are not initialized upon a query. This behaviour differs from ActiveRecord, beware of any side effect this may
296
+ # have when using this method.
297
+ def freeze
298
+ @attributes.freeze; self
299
+ end
300
+
301
+ module PropertiesGetterAndSetter
312
302
 
303
+ # Returns the value of the attribute identified by <tt>attr_name</tt> after
304
+ # it has been typecast (for example, "2004-12-12" in a date column is cast
305
+ # to a date object, like Date.new(2004, 12, 12))
306
+ def read_attribute(attr)
307
+ @attributes[attr]
313
308
  end
314
309
 
315
- include PropertiesGetterAndSetter
310
+ # Updates the attribute identified by <tt>attr_name</tt> with the
311
+ # specified +value+. Empty strings for fixnum and float columns are
312
+ # turned into +nil+.
313
+ def write_attribute(attr, value)
314
+ @attributes[attr] = types[attr].coerce(value)
315
+ end
316
316
 
317
- class ScopeRelation < Association
317
+ # @api private
318
+ def read_association(attr, assign_if_value_nil=nil)
319
+ @associations[attr.to_sym] ||= assign_if_value_nil.try(:call)
318
320
  end
319
321
 
320
- module Scopes
322
+ # @api private
323
+ def write_association(attr, value)
324
+ @associations[attr.to_sym] = value
321
325
  end
322
326
 
327
+ protected :read_attribute, :write_attribute, :read_association, :write_association
328
+
329
+ end
330
+
331
+ include PropertiesGetterAndSetter
332
+
333
+ class ScopeRelation < Association
334
+ end
335
+
336
+ module Scopes
323
337
  end
324
338
  end
325
339
  end