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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +25 -0
- data/README.md +111 -120
- data/lib/active_mocker.rb +1 -1
- data/lib/active_mocker/active_record.rb +2 -2
- data/lib/active_mocker/active_record/relationships.rb +89 -79
- data/lib/active_mocker/active_record/scope.rb +16 -6
- data/lib/active_mocker/active_record/unknown_class_method.rb +7 -3
- data/lib/active_mocker/active_record/unknown_module.rb +19 -14
- data/lib/active_mocker/db_to_ruby_type.rb +1 -0
- data/lib/active_mocker/field.rb +1 -1
- data/lib/{file_reader.rb → active_mocker/file_reader.rb} +5 -0
- data/lib/active_mocker/generate.rb +6 -1
- data/lib/active_mocker/loaded_mocks.rb +43 -27
- data/lib/active_mocker/logger.rb +1 -0
- data/lib/active_mocker/mock/base.rb +153 -42
- data/lib/active_mocker/mock/collection.rb +6 -1
- data/lib/active_mocker/mock/do_nothing_active_record_methods.rb +4 -0
- data/lib/active_mocker/mock/exceptions.rb +14 -1
- data/lib/active_mocker/mock/has_many.rb +7 -0
- data/lib/active_mocker/mock/hash_process.rb +1 -0
- data/lib/active_mocker/mock/next_id.rb +1 -0
- data/lib/active_mocker/mock/queries.rb +216 -23
- data/lib/active_mocker/mock/relation.rb +21 -0
- data/lib/active_mocker/mock_template.erb +12 -2
- data/lib/active_mocker/model_reader.rb +1 -1
- data/lib/active_mocker/model_schema.rb +2 -1
- data/lib/active_mocker/public_methods.rb +24 -11
- data/lib/active_mocker/reparameterize.rb +1 -0
- data/lib/active_mocker/rspec_helper.rb +2 -0
- data/lib/active_mocker/schema_reader.rb +1 -0
- data/lib/active_mocker/string_reader.rb +14 -0
- data/lib/active_mocker/table.rb +1 -1
- data/lib/active_mocker/version.rb +1 -1
- metadata +4 -4
- data/lib/string_reader.rb +0 -9
@@ -1,12 +1,22 @@
|
|
1
|
-
module
|
1
|
+
module ActiveMocker
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
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
|
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
|
3
|
+
module ActiveRecord
|
4
4
|
|
5
|
-
|
6
|
-
try_and_log('include', _module, caller)
|
7
|
-
end
|
5
|
+
module UnknownModule
|
8
6
|
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
def include(_module)
|
8
|
+
try_and_log('include', _module, caller)
|
9
|
+
end
|
12
10
|
|
13
|
-
|
11
|
+
def extend(_module)
|
12
|
+
try_and_log('extend', _module, caller)
|
13
|
+
end
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
data/lib/active_mocker/field.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
8
|
-
|
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
|
59
|
-
|
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
|
-
|
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
|
data/lib/active_mocker/logger.rb
CHANGED
@@ -8,52 +8,96 @@ class Base
|
|
8
8
|
extend Queries
|
9
9
|
|
10
10
|
def self.inherited(subclass)
|
11
|
-
return ActiveMocker::LoadedMocks.add
|
12
|
-
ActiveMocker::LoadedMocks.add_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
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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 :
|
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
|
-
|
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
|
-
|
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?
|
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
|
-
|
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
|
-
|
154
|
+
assign_attributes(attributes)
|
109
155
|
end
|
110
156
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
159
|
-
|
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
|