active_mocker 1.5.2 → 1.6

Sign up to get free protection for your applications and to get access to all the features.
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