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
@@ -16,7 +16,7 @@ module Mock
|
|
16
16
|
end
|
17
17
|
|
18
18
|
extend ::Forwardable
|
19
|
-
def_delegators :@collection, :take, :push, :clear, :first, :last, :concat, :replace, :distinct, :uniq, :count, :size, :length, :empty?, :any?, :include?, :delete
|
19
|
+
def_delegators :@collection, :take, :push, :clear, :first, :last, :concat, :replace, :distinct, :uniq, :count, :size, :length, :empty?, :any?, :many?, :include?, :delete
|
20
20
|
alias distinct uniq
|
21
21
|
|
22
22
|
def select(&block)
|
@@ -51,6 +51,11 @@ module Mock
|
|
51
51
|
@collection == val
|
52
52
|
end
|
53
53
|
|
54
|
+
# Returns true if relation is blank.
|
55
|
+
def blank?
|
56
|
+
to_a.blank?
|
57
|
+
end
|
58
|
+
|
54
59
|
protected
|
55
60
|
|
56
61
|
attr_accessor :collection
|
@@ -12,7 +12,17 @@ module Mock
|
|
12
12
|
class FileTypeMismatchError < StandardError
|
13
13
|
end
|
14
14
|
|
15
|
-
|
15
|
+
# Raised when unknown attributes are supplied via mass assignment.
|
16
|
+
class UnknownAttributeError < NoMethodError
|
17
|
+
|
18
|
+
attr_reader :record, :attribute
|
19
|
+
|
20
|
+
def initialize(record, attribute)
|
21
|
+
@record = record
|
22
|
+
@attribute = attribute.to_s
|
23
|
+
super("unknown attribute: #{attribute}")
|
24
|
+
end
|
25
|
+
|
16
26
|
end
|
17
27
|
|
18
28
|
class Unimplemented < Exception
|
@@ -21,5 +31,8 @@ module Mock
|
|
21
31
|
class IdNotNumber < Exception
|
22
32
|
end
|
23
33
|
|
34
|
+
class Error < Exception
|
35
|
+
end
|
36
|
+
|
24
37
|
end
|
25
38
|
end
|
@@ -16,6 +16,13 @@ module Mock
|
|
16
16
|
@foreign_id = foreign_id
|
17
17
|
self.class.include "#{relation_class.name}::Scopes".constantize
|
18
18
|
super(collection)
|
19
|
+
set_foreign_key if ActiveMocker::Mock.config.experimental
|
20
|
+
end
|
21
|
+
|
22
|
+
def set_foreign_key
|
23
|
+
collection.each do |item|
|
24
|
+
item.send(:write_attribute, foreign_key, foreign_id) if item.respond_to?("#{foreign_key}=")
|
25
|
+
end
|
19
26
|
end
|
20
27
|
|
21
28
|
private
|
@@ -9,9 +9,9 @@ module Mock
|
|
9
9
|
@record = record
|
10
10
|
end
|
11
11
|
|
12
|
-
def is_of(
|
13
|
-
|
14
|
-
next match.any? { |m| @record.send(col) == m } if match.
|
12
|
+
def is_of(conditions={})
|
13
|
+
conditions.all? do |col, match|
|
14
|
+
next match.any? { |m| @record.send(col) == m } if match.is_a? Enumerable
|
15
15
|
@record.send(col) == match
|
16
16
|
end
|
17
17
|
end
|
@@ -25,58 +25,231 @@ module Mock
|
|
25
25
|
@parent_class = parent_class
|
26
26
|
end
|
27
27
|
|
28
|
-
def not(
|
28
|
+
def not(conditions={})
|
29
29
|
@parent_class.call(@collection.reject do |record|
|
30
|
-
Find.new(record).is_of(
|
30
|
+
Find.new(record).is_of(conditions)
|
31
31
|
end)
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
|
-
|
36
|
-
|
35
|
+
# Deletes the records matching +conditions+ by instantiating each
|
36
|
+
# record and calling its +delete+ method.
|
37
|
+
#
|
38
|
+
# ==== Parameters
|
39
|
+
#
|
40
|
+
# * +conditions+ - A string, array, or hash that specifies which records
|
41
|
+
# to destroy. If omitted, all records are destroyed.
|
42
|
+
#
|
43
|
+
# ==== Examples
|
44
|
+
#
|
45
|
+
# PersonMock.destroy_all(status: "inactive")
|
46
|
+
# PersonMock.where(age: 0..18).destroy_all
|
47
|
+
#
|
48
|
+
# If a limit scope is supplied, +delete_all+ raises an ActiveMocker error:
|
49
|
+
#
|
50
|
+
# Post.limit(100).delete_all
|
51
|
+
# # => ActiveMocker::Mock::Error: delete_all doesn't support limit scope
|
52
|
+
def delete_all(conditions=nil)
|
53
|
+
raise ActiveMocker::Mock::Error.new("delete_all doesn't support limit scope") if from_limit?
|
54
|
+
if conditions.nil?
|
37
55
|
to_a.map(&:delete)
|
38
56
|
return to_a.clear
|
39
57
|
end
|
40
|
-
where(
|
58
|
+
where(conditions).map { |r| r.delete }.count
|
41
59
|
end
|
42
60
|
|
43
|
-
|
44
|
-
delete_all
|
45
|
-
end
|
61
|
+
alias_method :destroy_all, :delete_all
|
46
62
|
|
47
|
-
|
48
|
-
|
63
|
+
# Returns a new relation, which is the result of filtering the current relation
|
64
|
+
# according to the conditions in the arguments.
|
65
|
+
#
|
66
|
+
# === hash
|
67
|
+
#
|
68
|
+
# #where will accept a hash condition, in which the keys are fields and the values
|
69
|
+
# are values to be searched for.
|
70
|
+
#
|
71
|
+
# Fields can be symbols or strings. Values can be single values, arrays, or ranges.
|
72
|
+
#
|
73
|
+
# User.where({ name: "Joe", email: "joe@example.com" })
|
74
|
+
#
|
75
|
+
# User.where({ name: ["Alice", "Bob"]})
|
76
|
+
#
|
77
|
+
# User.where({ created_at: (Time.now.midnight - 1.day)..Time.now.midnight })
|
78
|
+
#
|
79
|
+
# In the case of a belongs_to relationship, an association key can be used
|
80
|
+
# to specify the model if an ActiveRecord object is used as the value.
|
81
|
+
#
|
82
|
+
# author = Author.find(1)
|
83
|
+
#
|
84
|
+
# # The following queries will be equivalent:
|
85
|
+
# Post.where(author: author)
|
86
|
+
# Post.where(author_id: author)
|
87
|
+
#
|
88
|
+
# This also works with polymorphic belongs_to relationships:
|
89
|
+
#
|
90
|
+
# treasure = Treasure.create(name: 'gold coins')
|
91
|
+
# treasure.price_estimates << PriceEstimate.create(price: 125)
|
92
|
+
#
|
93
|
+
# # The following queries will be equivalent:
|
94
|
+
# PriceEstimate.where(estimate_of: treasure)
|
95
|
+
# PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: treasure)
|
96
|
+
#
|
97
|
+
# === no argument
|
98
|
+
#
|
99
|
+
# If no argument is passed, #where returns a new instance of WhereChain, that
|
100
|
+
# can be chained with #not to return a new relation that negates the where clause.
|
101
|
+
#
|
102
|
+
# User.where.not(name: "Jon")
|
103
|
+
#
|
104
|
+
# See WhereChain for more details on #not.
|
105
|
+
def where(conditions=nil)
|
106
|
+
return WhereNotChain.new(all, method(:new_relation)) if conditions.nil?
|
49
107
|
new_relation(to_a.select do |record|
|
50
|
-
Find.new(record).is_of(
|
108
|
+
Find.new(record).is_of(conditions)
|
51
109
|
end)
|
52
110
|
end
|
53
111
|
|
112
|
+
# Find by id - This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
|
113
|
+
# If no record can be found for all of the listed ids, then RecordNotFound will be raised. If the primary key
|
114
|
+
# is an integer, find by id coerces its arguments using +to_i+.
|
115
|
+
#
|
116
|
+
# Person.find(1) # returns the object for ID = 1
|
117
|
+
# Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
|
118
|
+
# Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
|
119
|
+
# Person.find([1]) # returns an array for the object with ID = 1
|
120
|
+
#
|
121
|
+
# <tt>ActiveRecord::RecordNotFound</tt> will be raised if one or more ids are not found.
|
54
122
|
def find(ids)
|
55
123
|
results = [*ids].map do |id|
|
56
|
-
|
124
|
+
find_by!(id: id)
|
57
125
|
end
|
58
126
|
return new_relation(results) if ids.class == Array
|
59
127
|
results.first
|
60
128
|
end
|
61
129
|
|
62
|
-
|
63
|
-
|
130
|
+
# Updates all records with details given if they match a set of conditions supplied, limits and order can
|
131
|
+
# also be supplied.
|
132
|
+
#
|
133
|
+
# ==== Parameters
|
134
|
+
#
|
135
|
+
# * +updates+ - A string, array, or hash.
|
136
|
+
#
|
137
|
+
# ==== Examples
|
138
|
+
#
|
139
|
+
# # Update all customers with the given attributes
|
140
|
+
# Customer.update_all wants_email: true
|
141
|
+
#
|
142
|
+
# # Update all books with 'Rails' in their title
|
143
|
+
# BookMock.where(title: 'Rails').update_all(author: 'David')
|
144
|
+
#
|
145
|
+
# # Update all books that match conditions, but limit it to 5 ordered by date
|
146
|
+
# BookMock.where(title: 'Rails').order(:created_at).limit(5).update_all(author: 'David')
|
147
|
+
def update_all(conditions)
|
148
|
+
all.each { |i| i.update(conditions) }
|
149
|
+
end
|
150
|
+
|
151
|
+
# Updates an object (or multiple objects) and saves it.
|
152
|
+
#
|
153
|
+
# ==== Parameters
|
154
|
+
#
|
155
|
+
# * +id+ - This should be the id or an array of ids to be updated.
|
156
|
+
# * +attributes+ - This should be a hash of attributes or an array of hashes.
|
157
|
+
#
|
158
|
+
# ==== Examples
|
159
|
+
#
|
160
|
+
# # Updates one record
|
161
|
+
# Person.update(15, user_name: 'Samuel', group: 'expert')
|
162
|
+
#
|
163
|
+
# # Updates multiple records
|
164
|
+
# people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } }
|
165
|
+
# Person.update(people.keys, people.values)
|
166
|
+
def update(id, attributes)
|
167
|
+
if id.is_a?(Array)
|
168
|
+
id.map.with_index { |one_id, idx| update(one_id, attributes[idx]) }
|
169
|
+
else
|
170
|
+
object = find(id)
|
171
|
+
object.update(attributes)
|
172
|
+
object
|
173
|
+
end
|
64
174
|
end
|
65
175
|
|
66
|
-
|
67
|
-
|
176
|
+
# Finds the first record matching the specified conditions. There
|
177
|
+
# is no implied ordering so if order matters, you should specify it
|
178
|
+
# yourself.
|
179
|
+
#
|
180
|
+
# If no record is found, returns <tt>nil</tt>.
|
181
|
+
#
|
182
|
+
# Post.find_by name: 'Spartacus', rating: 4
|
183
|
+
def find_by(conditions = {})
|
184
|
+
send(:where, conditions).first
|
68
185
|
end
|
69
186
|
|
70
|
-
|
71
|
-
|
72
|
-
|
187
|
+
# Like <tt>find_by</tt>, except that if no record is found, raises
|
188
|
+
# an <tt>ActiveRecord::RecordNotFound</tt> error.
|
189
|
+
def find_by!(conditions={})
|
190
|
+
result = find_by(conditions)
|
191
|
+
raise RecordNotFound if result.nil?
|
73
192
|
result
|
74
193
|
end
|
75
194
|
|
195
|
+
# Finds the first record with the given attributes, or creates a record
|
196
|
+
# with the attributes if one is not found:
|
197
|
+
#
|
198
|
+
# # Find the first user named "Penélope" or create a new one.
|
199
|
+
# UserMock.find_or_create_by(first_name: 'Penélope')
|
200
|
+
# # => #<User id: 1, first_name: "Penélope", last_name: nil>
|
201
|
+
#
|
202
|
+
# # Find the first user named "Penélope" or create a new one.
|
203
|
+
# # We already have one so the existing record will be returned.
|
204
|
+
# UserMock.find_or_create_by(first_name: 'Penélope')
|
205
|
+
# # => #<User id: 1, first_name: "Penélope", last_name: nil>
|
206
|
+
#
|
207
|
+
# This method accepts a block, which is passed down to +create+. The last example
|
208
|
+
# above can be alternatively written this way:
|
209
|
+
#
|
210
|
+
# # Find the first user named "Scarlett" or create a new one with a
|
211
|
+
# # different last name.
|
212
|
+
# User.find_or_create_by(first_name: 'Scarlett') do |user|
|
213
|
+
# user.last_name = 'Johansson'
|
214
|
+
# end
|
215
|
+
# # => #<User id: 2, first_name: "Scarlett", last_name: "Johansson">
|
216
|
+
#
|
217
|
+
def find_or_create_by(attributes, &block)
|
218
|
+
find_by(attributes) || create(attributes, &block)
|
219
|
+
end
|
220
|
+
|
221
|
+
alias_method :find_or_create_by!, :find_or_create_by
|
222
|
+
|
223
|
+
# Like <tt>find_or_create_by</tt>, but calls <tt>new</tt> instead of <tt>create</tt>.
|
224
|
+
def find_or_initialize_by(attributes, &block)
|
225
|
+
find_by(attributes) || new(attributes, &block)
|
226
|
+
end
|
227
|
+
|
228
|
+
# Count the records.
|
229
|
+
#
|
230
|
+
# PersonMock.count
|
231
|
+
# # => the total count of all people
|
232
|
+
#
|
233
|
+
# PersonMock.count(:age)
|
234
|
+
# # => returns the total count of all people whose age is present in database
|
235
|
+
def count(column_name = nil)
|
236
|
+
return all.size if column_name.nil?
|
237
|
+
where.not(column_name => nil).size
|
238
|
+
end
|
239
|
+
|
240
|
+
# Specifies a limit for the number of records to retrieve.
|
241
|
+
#
|
242
|
+
# User.limit(10)
|
76
243
|
def limit(num)
|
77
|
-
new_relation(all.take(num))
|
244
|
+
relation = new_relation(all.take(num))
|
245
|
+
relation.send(:set_from_limit)
|
246
|
+
relation
|
78
247
|
end
|
79
248
|
|
249
|
+
# Calculates the sum of values on a given column. The value is returned
|
250
|
+
# with the same data type of the column, 0 if there's no row.
|
251
|
+
#
|
252
|
+
# Person.sum(:age) # => 4562
|
80
253
|
def sum(key)
|
81
254
|
values = values_by_key(key)
|
82
255
|
values.inject(0) do |sum, n|
|
@@ -84,24 +257,44 @@ module Mock
|
|
84
257
|
end
|
85
258
|
end
|
86
259
|
|
260
|
+
# Calculates the average value on a given column. Returns +nil+ if there's
|
261
|
+
# no row.
|
262
|
+
#
|
263
|
+
# PersonMock.average(:age) # => 35.8
|
87
264
|
def average(key)
|
88
265
|
values = values_by_key(key)
|
89
266
|
total = values.inject { |sum, n| sum + n }
|
90
267
|
BigDecimal.new(total) / BigDecimal.new(values.count)
|
91
268
|
end
|
92
269
|
|
270
|
+
# Calculates the minimum value on a given column. The value is returned
|
271
|
+
# with the same data type of the column, or +nil+ if there's no row.
|
272
|
+
#
|
273
|
+
# Person.minimum(:age) # => 7
|
93
274
|
def minimum(key)
|
94
275
|
values_by_key(key).min_by { |i| i }
|
95
276
|
end
|
96
277
|
|
278
|
+
# Calculates the maximum value on a given column. The value is returned
|
279
|
+
# with the same data type of the column, or +nil+ if there's no row.
|
280
|
+
#
|
281
|
+
# Person.maximum(:age) # => 93
|
97
282
|
def maximum(key)
|
98
283
|
values_by_key(key).max_by { |i| i }
|
99
284
|
end
|
100
285
|
|
286
|
+
# Allows to specify an order attribute:
|
287
|
+
#
|
288
|
+
# User.order('name')
|
289
|
+
#
|
290
|
+
# User.order(:name)
|
101
291
|
def order(key)
|
102
292
|
new_relation(all.sort_by { |item| item.send(key) })
|
103
293
|
end
|
104
294
|
|
295
|
+
# Reverse the existing order clause on the relation.
|
296
|
+
#
|
297
|
+
# User.order('name').reverse_order
|
105
298
|
def reverse_order
|
106
299
|
new_relation(to_a.reverse)
|
107
300
|
end
|
@@ -4,6 +4,27 @@ module Mock
|
|
4
4
|
|
5
5
|
include Queries
|
6
6
|
|
7
|
+
def initialize(collection=[])
|
8
|
+
super
|
9
|
+
@from_limit = false
|
10
|
+
end
|
11
|
+
|
12
|
+
def inspect
|
13
|
+
entries = to_a.take(11).map!(&:inspect)
|
14
|
+
entries[10] = '...' if entries.size == 11
|
15
|
+
"#<#{self.class.name} [#{entries.join(', ')}]>"
|
16
|
+
end
|
17
|
+
|
18
|
+
def from_limit?
|
19
|
+
@from_limit
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def set_from_limit
|
25
|
+
@from_limit = true
|
26
|
+
end
|
27
|
+
|
7
28
|
end
|
8
29
|
end
|
9
30
|
end
|
@@ -30,6 +30,8 @@ class <%= class_name + @mock_append_name %> < ActiveMocker::Mock::Base
|
|
30
30
|
'<%= class_name %>'
|
31
31
|
end
|
32
32
|
|
33
|
+
private :mocked_class
|
34
|
+
|
33
35
|
def attribute_names
|
34
36
|
@attribute_names ||= <%= attribute_names %>
|
35
37
|
end
|
@@ -52,7 +54,7 @@ class <%= class_name + @mock_append_name %> < ActiveMocker::Mock::Base
|
|
52
54
|
<% association = belongs_to_foreign_key(meth.name) -%>
|
53
55
|
write_attribute(:<%= meth.name %>, val)
|
54
56
|
<% if association -%>
|
55
|
-
association = classes('<%= association.class_name %>').try(:
|
57
|
+
association = classes('<%= association.class_name %>').try(:find_by, id: <%= meth.name %>)
|
56
58
|
write_association(:<%= association.name %>,association) unless association.nil?
|
57
59
|
<% end -%>
|
58
60
|
end
|
@@ -70,6 +72,10 @@ class <%= class_name + @mock_append_name %> < ActiveMocker::Mock::Base
|
|
70
72
|
def <%= meth.name %>=(val)
|
71
73
|
@associations[:<%= meth.name %>] = val
|
72
74
|
write_attribute(:<%= meth.foreign_key %>, val.id) if val.respond_to?(:persisted?) && val.persisted?
|
75
|
+
if ActiveMocker::Mock.config.experimental
|
76
|
+
val.<%= class_name.tableize %> << self if val.respond_to?(:<%= class_name.tableize %>)
|
77
|
+
end
|
78
|
+
val
|
73
79
|
end
|
74
80
|
|
75
81
|
def build_<%= meth.name %>(attributes={}, &block)
|
@@ -92,11 +98,15 @@ class <%= class_name + @mock_append_name %> < ActiveMocker::Mock::Base
|
|
92
98
|
<%= '# has_one' unless has_one.empty? -%>
|
93
99
|
<% has_one.each do |meth| %>
|
94
100
|
def <%= meth.name %>
|
95
|
-
|
101
|
+
read_association('<%= meth.name %>')
|
96
102
|
end
|
97
103
|
|
98
104
|
def <%= meth.name %>=(val)
|
99
105
|
@associations['<%= meth.name %>'] = val
|
106
|
+
if ActiveMocker::Mock.config.experimental
|
107
|
+
<%= meth.name %>.send(:write_association, <%= class_name.tableize.singularize %>, self) if val.respond_to?(:<%= class_name.tableize.singularize %>=)
|
108
|
+
end
|
109
|
+
<%= meth.name %>
|
100
110
|
end
|
101
111
|
|
102
112
|
def build_<%= meth.name %>(attributes={}, &block)
|