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