active_mocker 1.5.2 → 1.6

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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -0
  3. data/README.md +111 -120
  4. data/lib/active_mocker.rb +1 -1
  5. data/lib/active_mocker/active_record.rb +2 -2
  6. data/lib/active_mocker/active_record/relationships.rb +89 -79
  7. data/lib/active_mocker/active_record/scope.rb +16 -6
  8. data/lib/active_mocker/active_record/unknown_class_method.rb +7 -3
  9. data/lib/active_mocker/active_record/unknown_module.rb +19 -14
  10. data/lib/active_mocker/db_to_ruby_type.rb +1 -0
  11. data/lib/active_mocker/field.rb +1 -1
  12. data/lib/{file_reader.rb → active_mocker/file_reader.rb} +5 -0
  13. data/lib/active_mocker/generate.rb +6 -1
  14. data/lib/active_mocker/loaded_mocks.rb +43 -27
  15. data/lib/active_mocker/logger.rb +1 -0
  16. data/lib/active_mocker/mock/base.rb +153 -42
  17. data/lib/active_mocker/mock/collection.rb +6 -1
  18. data/lib/active_mocker/mock/do_nothing_active_record_methods.rb +4 -0
  19. data/lib/active_mocker/mock/exceptions.rb +14 -1
  20. data/lib/active_mocker/mock/has_many.rb +7 -0
  21. data/lib/active_mocker/mock/hash_process.rb +1 -0
  22. data/lib/active_mocker/mock/next_id.rb +1 -0
  23. data/lib/active_mocker/mock/queries.rb +216 -23
  24. data/lib/active_mocker/mock/relation.rb +21 -0
  25. data/lib/active_mocker/mock_template.erb +12 -2
  26. data/lib/active_mocker/model_reader.rb +1 -1
  27. data/lib/active_mocker/model_schema.rb +2 -1
  28. data/lib/active_mocker/public_methods.rb +24 -11
  29. data/lib/active_mocker/reparameterize.rb +1 -0
  30. data/lib/active_mocker/rspec_helper.rb +2 -0
  31. data/lib/active_mocker/schema_reader.rb +1 -0
  32. data/lib/active_mocker/string_reader.rb +14 -0
  33. data/lib/active_mocker/table.rb +1 -1
  34. data/lib/active_mocker/version.rb +1 -1
  35. metadata +4 -4
  36. data/lib/string_reader.rb +0 -9
@@ -1,12 +1,22 @@
1
- module Scope
1
+ module ActiveMocker
2
2
 
3
- def scope(method_name, proc)
4
- get_named_scopes[method_name] = proc
5
- end
3
+ module ActiveRecord
4
+
5
+ module Scope
6
+
7
+ def scope(method_name, proc)
8
+ get_named_scopes[method_name] = proc
9
+ end
10
+
11
+ def get_named_scopes
12
+ @scope_methods ||= {}
13
+ end
14
+
15
+ end
6
16
 
7
- def get_named_scopes
8
- @scope_methods ||= {}
9
17
  end
10
18
 
11
19
  end
12
20
 
21
+
22
+
@@ -1,9 +1,13 @@
1
1
  module ActiveMocker
2
2
 
3
- module UnknownClassMethod
3
+ module ActiveRecord
4
+
5
+ module UnknownClassMethod
6
+
7
+ def method_missing(meth, *args)
8
+ Logger.debug "ActiveMocker :: DEBUG :: #{meth} called from class #{self.name} is unknown and will not be available in mock.\n"
9
+ end
4
10
 
5
- def method_missing(meth, *args)
6
- Logger.debug "ActiveMocker :: DEBUG :: #{meth} called from class #{self.name} is unknown and will not be available in mock.\n"
7
11
  end
8
12
 
9
13
  end
@@ -1,26 +1,31 @@
1
1
  module ActiveMocker
2
2
 
3
- module UnknownModule
3
+ module ActiveRecord
4
4
 
5
- def include(_module)
6
- try_and_log('include', _module, caller)
7
- end
5
+ module UnknownModule
8
6
 
9
- def extend(_module)
10
- try_and_log('extend', _module, caller)
11
- end
7
+ def include(_module)
8
+ try_and_log('include', _module, caller)
9
+ end
12
10
 
13
- private
11
+ def extend(_module)
12
+ try_and_log('extend', _module, caller)
13
+ end
14
14
 
15
- def try_and_log(type, name, _caller)
16
- begin
17
- super _module
18
- rescue => e
19
- Logger.debug "ActiveMocker :: Debug :: Can't #{type} module #{name} from class #{self.name}.\n\t\t\t\t\t\t\t\t#{_caller}\n"
20
- Logger.debug "\t\t\t\t\t\t\t\t#{e}"
15
+ private
16
+
17
+ def try_and_log(type, name, _caller)
18
+ begin
19
+ super _module
20
+ rescue => e
21
+ Logger.debug "ActiveMocker :: Debug :: Can't #{type} module #{name} from class #{self.name}.\n\t\t\t\t\t\t\t\t#{_caller}\n"
22
+ Logger.debug "\t\t\t\t\t\t\t\t#{e}"
23
+ end
21
24
  end
25
+
22
26
  end
23
27
 
24
28
  end
25
29
 
30
+
26
31
  end
@@ -1,4 +1,5 @@
1
1
  module ActiveMocker
2
+ # @api private
2
3
  class DBToRubyType
3
4
 
4
5
  def self.call(type)
@@ -1,5 +1,5 @@
1
1
  module ActiveMocker
2
-
2
+ # @api private
3
3
  class Field
4
4
 
5
5
  attr_accessor :name, :type, :options
@@ -1,7 +1,12 @@
1
+ module ActiveMocker
2
+ # @api private
1
3
  class FileReader
2
4
 
3
5
  def self.read(file_and_path)
4
6
  File.open(file_and_path).read
5
7
  end
6
8
 
9
+ end
10
+
11
+ private_constant :FileReader
7
12
  end
@@ -27,10 +27,13 @@ class Generate
27
27
  config(&block)
28
28
  end
29
29
 
30
+ # Method will be deprecated in v2
30
31
  def self.mock(model_name, force_reload: false)
31
32
  load_mock(model_name)
32
33
  end
33
34
 
35
+ private
36
+
34
37
  def self.load_mock(model_name)
35
38
  load File.join(mock_dir, "#{model_name.tableize.singularize}_mock.rb")
36
39
  "#{model_name}Mock".constantize
@@ -58,11 +61,13 @@ class Generate
58
61
 
59
62
  def create_template
60
63
  mocks_created = 0
64
+ FileUtils.rm_rf("#{mock_dir}/", secure: true)
65
+ FileUtils::mkdir_p mock_dir unless File.directory? mock_dir
61
66
  generate_model_schema.each do |model|
62
67
  begin
63
68
 
64
69
  klass_str = model.render(File.open(File.join(File.expand_path('../', __FILE__), 'mock_template.erb')).read, mock_append_name)
65
- FileUtils::mkdir_p mock_dir unless File.directory? mock_dir
70
+
66
71
  File.open(File.join(mock_dir,"#{model.table_name.singularize}_mock.rb"), 'w').write(klass_str)
67
72
  logger.info "saving mock #{model.class_name} to #{mock_dir}"
68
73
 
@@ -4,47 +4,65 @@ module ActiveMocker
4
4
 
5
5
  class << self
6
6
 
7
- def add(mocks_to_add)
8
- mocks.merge!({mocks_to_add.name => mocks_to_add})
9
- end
10
-
11
- def add_subclass(subclass)
12
- subclasses.merge!({subclass.mocked_class => subclass})
13
- end
14
-
15
- def subclasses
16
- @subclasses ||= {}
17
- end
18
-
19
- def clear_subclasses
20
- subclasses.clear
21
- end
22
-
23
- def mocks
24
- @mocks ||= {}
25
- end
26
-
7
+ # Input ActiveRecord Model as String returns ActiveMock equivalent class.
8
+ # +find('User')+ => UserMock
27
9
  def find(klass)
28
10
  class_name_to_mock[klass]
29
11
  end
30
12
 
13
+ # Returns Hash key being a string of the active_record class name
14
+ # and the value being the mocked class.
15
+ # ActiveMocker::LoadedMocks.all
16
+ # => {'PersonMock' => PersonMock}
31
17
  def all
32
18
  mocks
33
19
  end
34
20
 
21
+ # Calls clear_all for all mocks, which deletes all saved records and removes mock_class/instance_method.
22
+ # It will also clear any sub classed mocks from the list
23
+ # Method will be deprecated in v2 because mocking is deprecated
35
24
  def clear_all
36
25
  all_mocks.each { |m| m.clear_mock }
37
26
  clear_subclasses
38
27
  end
39
28
 
29
+ # Calls delete_all for all mocks, which deletes all saved records.
30
+ #
40
31
  def delete_all
41
32
  all_mocks.each { |m| m.delete_all }
42
33
  end
43
34
 
35
+ # Reloads the mocks file from disk.
36
+ # === Experimental Feature
44
37
  def reload_all
45
38
  all_mocks.each { |m| m.send(:reload) }
46
39
  end
47
40
 
41
+ # Returns Hash +{"ActiveRecordModel" => MockVersion}+, key being a string of the active_record class name
42
+ # and the value being the mocked class. Any sub classed mocked will override the original mock until clear_all is called.
43
+ #
44
+ # ActiveMocker::LoadedMocks.class_name_to_mock
45
+ # => {'Person' => PersonMock}
46
+ def class_name_to_mock
47
+ mocks.values.each_with_object({}) do |mock_constant, hash|
48
+ hash[mock_constant.send(:mocked_class)] = mock_constant
49
+ end.merge(subclasses)
50
+ end
51
+
52
+ private
53
+
54
+ def add(mocks_to_add)
55
+ mocks.merge!({mocks_to_add.name => mocks_to_add})
56
+ end
57
+
58
+ def add_subclass(subclass)
59
+ subclasses.merge!({subclass.send(:mocked_class) => subclass})
60
+ end
61
+
62
+ def mocks
63
+ @mocks ||= {}
64
+ end
65
+
48
66
  def undefine_all
49
67
  all_mocks_as_str.each do |n|
50
68
  Object.send(:remove_const, n) if Object.const_defined?(n)
@@ -55,21 +73,19 @@ module ActiveMocker
55
73
  mocks.keys + subclasses.keys
56
74
  end
57
75
 
58
- def class_name_to_mock
59
- hash = mocks.values.each_with_object({}) do |mock_constant, hash|
60
- hash[mock_constant.mocked_class] = mock_constant
61
- end
62
- hash.merge(subclasses)
76
+ def subclasses
77
+ @subclasses ||= {}
63
78
  end
64
79
 
65
- private
80
+ def clear_subclasses
81
+ subclasses.clear
82
+ end
66
83
 
67
84
  def internal_clear
68
85
  clear_subclasses
69
86
  mocks.clear
70
87
  end
71
88
 
72
-
73
89
  def all_mocks
74
90
  mocks.values + subclasses.values
75
91
  end
@@ -1,4 +1,5 @@
1
1
  module ActiveMocker
2
+ # @api private
2
3
  class Logger
3
4
 
4
5
  def self.set(logger)
@@ -8,52 +8,96 @@ class Base
8
8
  extend Queries
9
9
 
10
10
  def self.inherited(subclass)
11
- return ActiveMocker::LoadedMocks.add(subclass) if subclass.superclass == Base
12
- ActiveMocker::LoadedMocks.add_subclass(subclass)
11
+ return ActiveMocker::LoadedMocks.send(:add, subclass) if subclass.superclass == Base
12
+ ActiveMocker::LoadedMocks.send(:add_subclass, subclass)
13
13
  end
14
14
 
15
15
  class << self
16
16
 
17
+ # Creates an object (or multiple objects) and saves it to memory.
18
+ #
19
+ # The +attributes+ parameter can be either a Hash or an Array of Hashes. These Hashes describe the
20
+ # attributes on the objects that are to be created.
21
+ #
22
+ # ==== Examples
23
+ # # Create a single new object
24
+ # User.create(first_name: 'Jamie')
25
+ #
26
+ # # Create an Array of new objects
27
+ # User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }])
28
+ #
29
+ # # Create a single object and pass it into a block to set other attributes.
30
+ # User.create(first_name: 'Jamie') do |u|
31
+ # u.is_admin = false
32
+ # end
33
+ #
34
+ # # Creating an Array of new objects using a block, where the block is executed for each object:
35
+ # User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }]) do |u|
36
+ # u.is_admin = false
37
+ # end
17
38
  def create(attributes = {}, &block)
18
- record = new
19
- record.save
20
- record.send(:set_properties, attributes) unless block_given?
21
- record.send(:set_properties_block, attributes, &block) if block_given?
22
- record
39
+ if attributes.is_a?(Array)
40
+ attributes.collect { |attr| create(attr, &block) }
41
+ else
42
+ record = new
43
+ record.save
44
+ record.assign_attributes(attributes, &block)
45
+ record
46
+ end
23
47
  end
24
48
 
25
49
  alias_method :create!, :create
26
50
 
27
- def find_or_create_by(attributes)
28
- find_by(attributes) || create(attributes)
29
- end
30
-
31
- def find_or_initialize_by(attributes)
32
- find_by(attributes) || new(attributes)
33
- end
34
-
35
51
  def records
36
52
  @records ||= Records.new
37
53
  end
38
54
 
39
55
  private :records
40
56
 
41
- delegate :count, :insert, :exists?, :to_a, :to => :records
57
+ delegate :insert, :exists?, :to_a, :to => :records
42
58
  delegate :first, :last, :to => :all
43
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)
44
76
  def delete(id)
45
- find(id).delete
77
+ if id.is_a?(Array)
78
+ id.map { |one_id| delete(one_id) }
79
+ else
80
+ find(id).delete
81
+ end
46
82
  end
47
83
 
48
84
  alias_method :destroy, :delete
49
85
 
50
- def delete_all(options=nil)
51
- return records.reset if options.nil?
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?
52
91
  super
53
92
  end
54
93
 
55
94
  alias_method :destroy_all, :delete_all
56
95
 
96
+ # @api private
97
+ def from_limit?
98
+ false
99
+ end
100
+
57
101
  def build_type(type)
58
102
  Virtus::Attribute.build(type)
59
103
  end
@@ -85,9 +129,17 @@ class Base
85
129
 
86
130
  attr_reader :associations, :types, :attributes
87
131
 
132
+ # New objects can be instantiated as either empty (pass no construction parameter) or pre-set with
133
+ # attributes but not yet saved (pass a hash with key names matching the associated table column names).
134
+ # In both instances, valid attribute keys are determined by the column names of the associated table --
135
+ # hence you can't have attributes that aren't part of the table columns.
136
+ #
137
+ # ==== Example:
138
+ # # Instantiates a single new object
139
+ # UserMock.new(first_name: 'Jamie')
88
140
  def initialize(attributes = {}, &block)
89
141
  setup_instance_variables
90
- set_properties_block(attributes, &block)
142
+ assign_attributes(attributes, &block)
91
143
  end
92
144
 
93
145
  def setup_instance_variables
@@ -98,27 +150,35 @@ class Base
98
150
 
99
151
  private :setup_instance_variables
100
152
 
101
- def set_properties_block(attributes = {}, &block)
102
- yield self if block_given?
103
- set_properties(attributes)
104
- end
105
- private :set_properties_block
106
-
107
153
  def update(attributes={})
108
- set_properties(attributes)
154
+ assign_attributes(attributes)
109
155
  end
110
156
 
111
- def set_properties(attributes={})
112
- attributes.each do |key, value|
113
- begin
114
- send "#{key}=", value
115
- rescue NoMethodError
116
- raise RejectedParams, "{:#{key}=>#{value.inspect}} for #{self.class.name}"
117
- end
157
+ # @api private
158
+ def assign_attributes(new_attributes, &block)
159
+ yield self if block_given?
160
+ unless new_attributes.respond_to?(:stringify_keys)
161
+ raise ArgumentError, "When assigning attributes, you must pass a hash as an argument."
162
+ end
163
+ return nil if new_attributes.blank?
164
+ attributes = new_attributes.stringify_keys
165
+ attributes.each do |k, v|
166
+ _assign_attribute(k, v)
118
167
  end
119
168
  end
120
169
 
121
- private :set_properties
170
+ alias attributes= assign_attributes
171
+
172
+ # @api private
173
+ def _assign_attribute(k, v)
174
+ public_send("#{k}=", v)
175
+ rescue NoMethodError
176
+ if respond_to?("#{k}=")
177
+ raise
178
+ else
179
+ raise UnknownAttributeError.new(self, k)
180
+ end
181
+ end
122
182
 
123
183
  def save(*args)
124
184
  unless self.class.exists?(self)
@@ -135,6 +195,7 @@ class Base
135
195
 
136
196
  private :records
137
197
 
198
+
138
199
  def delete
139
200
  records.delete(self)
140
201
  end
@@ -143,20 +204,52 @@ class Base
143
204
 
144
205
  delegate :[], :[]=, to: :attributes
145
206
 
146
- def reload
147
- self
148
- end
149
-
207
+ # Returns true if this object hasn't been saved yet; otherwise, returns false.
150
208
  def new_record?
151
- !records.new_record?(self)
209
+ records.new_record?(self)
152
210
  end
153
211
 
212
+ # Indicates if the model is persisted. Default is +false+.
213
+ #
214
+ # person = Person.new(id: 1, name: 'bob')
215
+ # person.persisted? # => false
154
216
  def persisted?
155
217
  records.persisted?(id)
156
218
  end
157
219
 
158
- def to_hash
159
- attributes
220
+ # Returns +true+ if the given attribute is in the attributes hash, otherwise +false+.
221
+ #
222
+ # person = Person.new
223
+ # person.has_attribute?(:name) # => true
224
+ # person.has_attribute?('age') # => true
225
+ # person.has_attribute?(:nothing) # => false
226
+ def has_attribute?(attr_name)
227
+ @attributes.has_key?(attr_name.to_s)
228
+ end
229
+
230
+ # Returns +true+ if the specified +attribute+ has been set and is neither +nil+ nor <tt>empty?</tt> (the latter only applies
231
+ # to objects that respond to <tt>empty?</tt>, most notably Strings). Otherwise, +false+.
232
+ # Note that it always returns +true+ with boolean attributes.
233
+ #
234
+ # person = Task.new(title: '', is_done: false)
235
+ # person.attribute_present?(:title) # => false
236
+ # person.attribute_present?(:is_done) # => true
237
+ # person.name = 'Francesco'
238
+ # person.is_done = true
239
+ # person.attribute_present?(:title) # => true
240
+ # person.attribute_present?(:is_done) # => true
241
+ def attribute_present?(attribute)
242
+ value = read_attribute(attribute)
243
+ !value.nil? && !(value.respond_to?(:empty?) && value.empty?)
244
+ end
245
+
246
+ # Returns an array of names for the attributes available on this object.
247
+ #
248
+ # person = Person.new
249
+ # person.attribute_names
250
+ # # => ["id", "created_at", "updated_at", "name", "age"]
251
+ def attribute_names
252
+ self.class.attribute_names
160
253
  end
161
254
 
162
255
  def inspect
@@ -175,18 +268,26 @@ class Base
175
268
 
176
269
  module PropertiesGetterAndSetter
177
270
 
271
+ # Returns the value of the attribute identified by <tt>attr_name</tt> after
272
+ # it has been typecast (for example, "2004-12-12" in a date column is cast
273
+ # to a date object, like Date.new(2004, 12, 12))
178
274
  def read_attribute(attr)
179
275
  @attributes[attr]
180
276
  end
181
277
 
278
+ # Updates the attribute identified by <tt>attr_name</tt> with the
279
+ # specified +value+. Empty strings for fixnum and float columns are
280
+ # turned into +nil+.
182
281
  def write_attribute(attr, value)
183
282
  @attributes[attr] = types[attr].coerce(value)
184
283
  end
185
284
 
285
+ # @api private
186
286
  def read_association(attr)
187
287
  @associations[attr]
188
288
  end
189
289
 
290
+ # @api private
190
291
  def write_association(attr, value)
191
292
  @associations[attr] = value
192
293
  end
@@ -199,6 +300,16 @@ class Base
199
300
 
200
301
  class ScopeRelation < ::ActiveMocker::Mock::Association
201
302
  end
303
+
304
+ end
305
+
306
+ def self.config
307
+ @config ||= Config.new
202
308
  end
309
+
310
+ class Config
311
+ attr_accessor :experimental
312
+ end
313
+
203
314
  end
204
315
  end