sample_models 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/MIT-LICENSE +20 -0
- data/README.markdown +345 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/init.rb +2 -0
- data/install.rb +1 -0
- data/lib/sample_models/creation.rb +116 -0
- data/lib/sample_models/finder.rb +81 -0
- data/lib/sample_models/model.rb +136 -0
- data/lib/sample_models/sampler.rb +110 -0
- data/lib/sample_models.rb +117 -0
- data/sample_models.gemspec +89 -0
- data/spec/sample_models_spec.rb +11 -0
- data/spec_or_test/database.yml +7 -0
- data/spec_or_test/setup.rb +249 -0
- data/spec_or_test/specs_or_test_cases.rb +569 -0
- data/spec_or_test/vendor/validates_email_format_of/CHANGELOG +11 -0
- data/spec_or_test/vendor/validates_email_format_of/MIT-LICENSE +20 -0
- data/spec_or_test/vendor/validates_email_format_of/README +28 -0
- data/spec_or_test/vendor/validates_email_format_of/TODO +1 -0
- data/spec_or_test/vendor/validates_email_format_of/init.rb +1 -0
- data/spec_or_test/vendor/validates_email_format_of/lib/validates_email_format_of.rb +41 -0
- data/spec_or_test/vendor/validates_email_format_of/rakefile +28 -0
- data/spec_or_test/vendor/validates_email_format_of/test/database.yml +3 -0
- data/spec_or_test/vendor/validates_email_format_of/test/fixtures/people.yml +3 -0
- data/spec_or_test/vendor/validates_email_format_of/test/fixtures/person.rb +3 -0
- data/spec_or_test/vendor/validates_email_format_of/test/schema.rb +5 -0
- data/spec_or_test/vendor/validates_email_format_of/test/test_helper.rb +35 -0
- data/spec_or_test/vendor/validates_email_format_of/test/validates_email_format_of_test.rb +82 -0
- data/tasks/sample_models_tasks.rake +4 -0
- data/test/test_sample_models.rb +28 -0
- data/uninstall.rb +1 -0
- data/vendor/ar_query/MIT-LICENSE +20 -0
- data/vendor/ar_query/README +0 -0
- data/vendor/ar_query/ar_query.gemspec +16 -0
- data/vendor/ar_query/init.rb +1 -0
- data/vendor/ar_query/install.rb +1 -0
- data/vendor/ar_query/lib/ar_query.rb +146 -0
- data/vendor/ar_query/spec/ar_query_spec.rb +318 -0
- data/vendor/ar_query/tasks/ar_query_tasks.rake +0 -0
- data/vendor/ar_query/uninstall.rb +1 -0
- metadata +117 -0
data/.gitignore
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 Francis Hwang
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,345 @@
|
|
1
|
+
SampleModels
|
2
|
+
============
|
3
|
+
|
4
|
+
A library for making it extremely fast for Rails developers to set up and save ActiveRecord instances when writing test cases. It aims to:
|
5
|
+
|
6
|
+
* meet all your validations automatically
|
7
|
+
* only make you specify the attributes you care about
|
8
|
+
* give you a rich set of features so you can specify associations as concisely as possible
|
9
|
+
* do this with as little configuration as possible
|
10
|
+
|
11
|
+
Feature overview
|
12
|
+
================
|
13
|
+
|
14
|
+
Let's say you've got a set of models that look like this:
|
15
|
+
|
16
|
+
class BlogPost < ActiveRecord::Base
|
17
|
+
has_many :blog_post_tags
|
18
|
+
has_many :tags, :through => :blog_post_tags
|
19
|
+
belongs_to :user
|
20
|
+
|
21
|
+
validates_presence_of :title, :user_id
|
22
|
+
end
|
23
|
+
|
24
|
+
class BlogPostTag < ActiveRecord::Base
|
25
|
+
belongs_to :blog_post
|
26
|
+
belongs_to :tag
|
27
|
+
end
|
28
|
+
|
29
|
+
class Tag < ActiveRecord::Base
|
30
|
+
validates_uniqueness_of :tag
|
31
|
+
|
32
|
+
has_many :blog_post_tags
|
33
|
+
has_many :blog_posts, :through => :blog_post_tags
|
34
|
+
end
|
35
|
+
|
36
|
+
class User < ActiveRecord::Base
|
37
|
+
has_many :blog_posts
|
38
|
+
|
39
|
+
validates_inclusion_of :gender, :in => %w(f m)
|
40
|
+
validates_uniqueness_of :email, :login
|
41
|
+
# from http://github.com/alexdunae/validates_email_format_of
|
42
|
+
validates_email_format_of :email
|
43
|
+
end
|
44
|
+
|
45
|
+
You can get a valid instance of a BlogPost by calling BlogPost.sample in a test environment:
|
46
|
+
|
47
|
+
blog_post1 = BlogPost.sample
|
48
|
+
puts blog_post1.title # => some non-empty string
|
49
|
+
puts blog_post1.user.is_a?(User) # => true
|
50
|
+
|
51
|
+
user1 = User.sample
|
52
|
+
puts user1.email # => will be a valid email
|
53
|
+
puts user1.gender # => will be either 'f' or 'm'
|
54
|
+
|
55
|
+
Since SampleModels figures out validations and associations from your ActiveRecord class definitions, it can usually fill in the required values without any configuration at all.
|
56
|
+
|
57
|
+
If you care about specific fields, you can specify them like so:
|
58
|
+
|
59
|
+
blog_post2 = BlogPost.sample(:title => 'What I ate for lunch')
|
60
|
+
puts blog_post2.title # => 'What I ate for lunch'
|
61
|
+
puts blog_post2.user.is_a?(User) # => true
|
62
|
+
|
63
|
+
You can specify associated records in the sample call:
|
64
|
+
|
65
|
+
bill = User.sample(:first_name => 'Bill')
|
66
|
+
bills_post = BlogPost.sample(:user => bill)
|
67
|
+
|
68
|
+
funny = Tag.sample(:tag => 'funny')
|
69
|
+
sad = Tag.sample(:tag => 'sad')
|
70
|
+
funny_yet_sad = BlogPost.sample(:tags => [funny, sad])
|
71
|
+
|
72
|
+
You can also specify associated records by passing in hashes or arrays:
|
73
|
+
|
74
|
+
bills_post2 = BlogPost.sample(:user => {:first_name => 'Bill'})
|
75
|
+
puts bills_post2.user.first_name # => 'Bill'
|
76
|
+
|
77
|
+
funny_yet_sad2 = BlogPost.sample(
|
78
|
+
:tags => [{:tag => 'funny'}, {:tag => 'sad'}]
|
79
|
+
)
|
80
|
+
puts funny_yet_sad2.tags.size # => 2
|
81
|
+
|
82
|
+
You can also specify associated records by passing them in at the beginning of the argument list, if there's only one association that would work with the record's class:
|
83
|
+
|
84
|
+
jane = User.sample(:first_name => 'Jane')
|
85
|
+
BlogPost.sample(jane, :title => 'What I ate for lunch')
|
86
|
+
|
87
|
+
Instance attributes
|
88
|
+
=========================
|
89
|
+
|
90
|
+
By default, SampleModels sets each attribute on a record to a non-blank value that matches the database type. They'll often be nonsensical values like "first_name 5", but the assumption is that if you didn't specify a value, you don't really care what it is as long as it validates. Non-trivial codebases routinely end up having models with many attributes, and when you find yourself writing a test with that model, you may only care about one or two attributes in that test case. SampleModels aims to let you specify only those important attributes while letting SampleModels take care of everything else.
|
91
|
+
|
92
|
+
SampleModels reads your validations to get hints about how to craft an instance that will be valid. The current supported validations are:
|
93
|
+
|
94
|
+
validates_email_format_of
|
95
|
+
-------------------------
|
96
|
+
|
97
|
+
If you use the validates_email_format_of plugin at [http://github.com/alexdunae/validates_email_format_of](http://github.com/alexdunae/validates_email_format_of), SampleModels will ensure that the attribute in question is a valid email address.
|
98
|
+
|
99
|
+
validates_presence_of
|
100
|
+
---------------------
|
101
|
+
|
102
|
+
SampleModels already sets values to be non-blank, but this validation comes in handy if you have an `attr_accessor`:
|
103
|
+
|
104
|
+
class UserWithPassword < ActiveRecord::Base
|
105
|
+
attr_accessor :password
|
106
|
+
|
107
|
+
validates_presence_of :password
|
108
|
+
end
|
109
|
+
|
110
|
+
user_with_password = UserWithPassword.sample
|
111
|
+
puts user_with_password.password # => Some non-blank string
|
112
|
+
|
113
|
+
|
114
|
+
validates_inclusion_of
|
115
|
+
----------------------
|
116
|
+
|
117
|
+
SampleModels will set the attribute to one of the specified values.
|
118
|
+
|
119
|
+
|
120
|
+
validates_uniqueness_of
|
121
|
+
-----------------------
|
122
|
+
|
123
|
+
SampleModels will ensure that new instances will have different values for attributes where uniqueness is required, as discussed below under "New records vs. old records."
|
124
|
+
|
125
|
+
|
126
|
+
New records vs. old records
|
127
|
+
===========================
|
128
|
+
|
129
|
+
Most of the time, consecutive calls to `sample` will return the same record, because this is marginally faster, and the design assumption is that if you're calling `sample` you don't care which instance you get as long as it satisfies the attributes you specified.
|
130
|
+
|
131
|
+
user1 = User.sample
|
132
|
+
user2 = User.sample
|
133
|
+
puts (user1 == user2) # probably true
|
134
|
+
|
135
|
+
rick1 = User.sample(:first_name => 'Rick')
|
136
|
+
puts (user1 == rick1) # probably false, but you never know
|
137
|
+
|
138
|
+
rick2 = User.sample(:first_name => 'Rick')
|
139
|
+
puts (rick1 == rick2) # probably true
|
140
|
+
|
141
|
+
If having a distinct record is important to the test, you should call `create_sample`, which always saves a new record in the DB and returns it.
|
142
|
+
|
143
|
+
blog_post1 = BlogPost.sample
|
144
|
+
blog_post2 = BlogPost.create_sample
|
145
|
+
puts (blog_post1 == blog_post2) # will always be false
|
146
|
+
|
147
|
+
If the class validates the uniqueness of a field, that field will always be distinct for every new instance returned by `create_sample`.
|
148
|
+
|
149
|
+
tag1 = Tag.sample
|
150
|
+
tag2 = Tag.create_sample
|
151
|
+
puts (tag1 == tag2) # will always be false
|
152
|
+
puts (tag1.tag == tag2.tag) # will always be false, because Tag validates
|
153
|
+
# the uniqueness of the `tag` attribute
|
154
|
+
|
155
|
+
Associations
|
156
|
+
============
|
157
|
+
|
158
|
+
If your application has an extensive data model, setting up associations for a test case can be an extremely tedious endeavor. SampleModels aims to make this process easy on the programmer and easy on the reader with a number of features.
|
159
|
+
|
160
|
+
Belongs-to associations
|
161
|
+
-----------------------
|
162
|
+
As demonstrated above, belongs_to associations are automatically set like any other attribute:
|
163
|
+
|
164
|
+
blog_post = BlogPost.sample
|
165
|
+
puts blog_post.user.is_a?(User) # => true
|
166
|
+
|
167
|
+
You can also specify these associations as if you were calling `new` or `create!`:
|
168
|
+
|
169
|
+
kelley = User.sample(:first_name => 'Kelley')
|
170
|
+
BlogPost.sample(:user => kelley)
|
171
|
+
BlogPost.sample(:user_id => kelley.id)
|
172
|
+
|
173
|
+
If you want, you can simply specify the record at the beginning of the argument list for `sample` or `create_sample`, and SampleModels will assign them to the appropriate association, as long as there's only one association that fits the class.
|
174
|
+
|
175
|
+
kim = User.sample(:first_name => 'Kim')
|
176
|
+
BlogPost.sample(kim, :title => 'funny')
|
177
|
+
|
178
|
+
You can do this with multiple belongs-to associations:
|
179
|
+
|
180
|
+
class Network < ActiveRecord::Base
|
181
|
+
end
|
182
|
+
|
183
|
+
class Show < ActiveRecord::Base
|
184
|
+
belongs_to :network
|
185
|
+
end
|
186
|
+
|
187
|
+
class Video < ActiveRecord::Base
|
188
|
+
belongs_to :show
|
189
|
+
belongs_to :network
|
190
|
+
end
|
191
|
+
|
192
|
+
amc = Network.sample(:name => 'AMC')
|
193
|
+
mad_men = Show.sample(:name => 'Mad Men')
|
194
|
+
video = Video.sample(amc, mad_men, :name => 'The Suitcase')
|
195
|
+
|
196
|
+
If you want, you can simply specify the important attributes of the associated value, and SampleModels will stitch it all together for you:
|
197
|
+
|
198
|
+
blog_post = BlogPost.sample(:user => {:first_name => 'Bill'})
|
199
|
+
puts blog_post.user.first_name # => 'Bill'
|
200
|
+
|
201
|
+
You can combine the two syntaxes in deeper associations:
|
202
|
+
|
203
|
+
bb_episode = Video.sample(:show => [amc, {:name => 'Breaking Bad'}])
|
204
|
+
puts bb_episode.show.network.name # => 'AMC'
|
205
|
+
puts bb_episode.show.name # => 'Breaking Bad'
|
206
|
+
|
207
|
+
Polymorphic belongs-to associations
|
208
|
+
-----------------------------------
|
209
|
+
|
210
|
+
In the case of a polymorphic belongs-to association, SampleModels will attach any record it can find, of any model class.
|
211
|
+
|
212
|
+
class Bookmark < ActiveRecord::Base
|
213
|
+
belongs_to :bookmarkable, :polymorphic => true
|
214
|
+
end
|
215
|
+
|
216
|
+
bookmark = Bookmark.sample
|
217
|
+
puts bookmark.bookmarkable.class # could be any model class
|
218
|
+
|
219
|
+
Of course, you can specify the polymorphic association yourself if that's important to the test.
|
220
|
+
|
221
|
+
blog_post = BlogPost.sample(:title => 'Read me later')
|
222
|
+
Bookmark.sample(:bookmarkable => blog_post)
|
223
|
+
|
224
|
+
You can also configure the default class of this polymorphic association with `default_class`, explained below under "Configuration".
|
225
|
+
|
226
|
+
Has-many associations
|
227
|
+
---------------------
|
228
|
+
|
229
|
+
You can set a has-many association with an array of instances, as you'd do with `new` or `create!`:
|
230
|
+
|
231
|
+
funny = Tag.sample(:tag => 'funny')
|
232
|
+
sad = Tag.sample(:tag => 'sad')
|
233
|
+
funny_yet_sad1 = BlogPost.sample(:tags => [funny, sad])
|
234
|
+
|
235
|
+
You can also pass hashes to specify the records:
|
236
|
+
|
237
|
+
funny_yet_sad2 = BlogPost.sample(
|
238
|
+
:tags => [{:tag => 'funny'}, {:tag => 'sad'}]
|
239
|
+
)
|
240
|
+
|
241
|
+
Or you can combine the two if that's more convenient:
|
242
|
+
|
243
|
+
funny_yet_sad3 = BlogPost.sample(:tags => [{:tag => 'sad'}, funny])
|
244
|
+
|
245
|
+
Configuration
|
246
|
+
=============
|
247
|
+
|
248
|
+
The aim of SampleModels is to require as little configuration as possible -- you'll typically find that most of your models won't need any configuration at all. However, there are a few hooks for when you're trying to accommodate advanced creational behavior.
|
249
|
+
|
250
|
+
before_save
|
251
|
+
-----------
|
252
|
+
|
253
|
+
With `before_save` you can specify a block that runs before the record is saved. For example, let's say you've got Users, Appointments, and Calendars, and an Appointment should have the same User as the Calendar it belongs to. You can set this behavior with `before_save`:
|
254
|
+
|
255
|
+
# app/models/appointment.rb
|
256
|
+
class Appointment < ActiveRecord::Base
|
257
|
+
belongs_to :calendar
|
258
|
+
belongs_to :user
|
259
|
+
|
260
|
+
def validate
|
261
|
+
if user_id != calendar.user_id
|
262
|
+
errors.add_to_base("Calendar has a different user than me")
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
# app/models/calendar.rb
|
268
|
+
class Calendar < ActiveRecord::Base
|
269
|
+
has_many :appointments
|
270
|
+
belongs_to :user
|
271
|
+
end
|
272
|
+
|
273
|
+
# test/test_helper.rb
|
274
|
+
SampleModels.configure(Appointment) do |appt|
|
275
|
+
appt.before_save do |appt_record|
|
276
|
+
appt_record.user_id = appt_record.calendar.user_id
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
You can also take a second argument, which will pass in the hash that was used during the call to `sample` or `create_sample`.
|
281
|
+
|
282
|
+
SampleModels.configure(Appointment) do |appt|
|
283
|
+
appt.before_save do |appt_record, sample_attrs|
|
284
|
+
unless sample_attrs.has_key?(:user) or sample_attrs.has_key?(:user_id)
|
285
|
+
appt_record.user_id = appt_record.calendar.user_id
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
default
|
291
|
+
-------
|
292
|
+
`default` will set default values for the field in question.
|
293
|
+
|
294
|
+
SampleModels.configure(Category) do |category|
|
295
|
+
category.parent.default nil
|
296
|
+
end
|
297
|
+
|
298
|
+
SampleModels.configure(Video) do |video|
|
299
|
+
video.view_count.default 0
|
300
|
+
end
|
301
|
+
|
302
|
+
A word to the wise: Be sparing with these global defaults. It's easy to tell yourself "Oh, this should be the default value everywhere" -- and then a day later find yourself wanting to override the default all over the place. In many cases you many want to used named samples (see below) instead.
|
303
|
+
|
304
|
+
default_class
|
305
|
+
-------------
|
306
|
+
By default, SampleModels fills polymorphic associations with any record, chosen practically at random. You may want to specify this to a more sensible default:
|
307
|
+
|
308
|
+
SampleModels.configure(Bookmark) do |bookmark|
|
309
|
+
bookmark.bookmarkable.default_class BlogPost
|
310
|
+
end
|
311
|
+
|
312
|
+
force_unique
|
313
|
+
------------
|
314
|
+
Use `force_unique` if you want to ensure that for every newly created instance, the field will be unique. This has the same effect as `validates_uniqueness_of`, but won't change how production code behaves.
|
315
|
+
|
316
|
+
SampleModels.configure(BlogPost) do |bp|
|
317
|
+
bp.published_at.force_unique
|
318
|
+
end
|
319
|
+
|
320
|
+
|
321
|
+
Named samples
|
322
|
+
=============
|
323
|
+
Named samples can be used to pre-set values for commonly used combinations of attributes.
|
324
|
+
|
325
|
+
SampleModels.configure(BlogPost) do |bp|
|
326
|
+
bp.funny_sample :title => 'Laugh already', :average_rating => 3.0
|
327
|
+
bp.sad_sample :title => 'Boo hoo', :average_rating => 2.0
|
328
|
+
end
|
329
|
+
|
330
|
+
bp1 = BlogPost.sample(:funny)
|
331
|
+
puts bp1.title # => 'Laugh already'
|
332
|
+
|
333
|
+
bp2 = BlogPost.create_sample(:funny)
|
334
|
+
puts bp2.title # => 'Laugh already'
|
335
|
+
puts (bp1 == bp2) # => false
|
336
|
+
|
337
|
+
You can override individual attributes, as well:
|
338
|
+
|
339
|
+
bp3 = BlogPost.sample(:funny, :average_rating => 4.0)
|
340
|
+
puts bp3.average_rating # => 4.0
|
341
|
+
|
342
|
+
About
|
343
|
+
=====
|
344
|
+
|
345
|
+
Copyright (c) 2010 Francis Hwang, released under the MIT license.
|
data/Rakefile
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
require 'rubygems'
|
5
|
+
gem 'rspec'
|
6
|
+
require 'spec/rake/spectask'
|
7
|
+
|
8
|
+
desc 'Default: run specs.'
|
9
|
+
task :default => [:test, :spec]
|
10
|
+
|
11
|
+
desc "Run all specs"
|
12
|
+
task :spec do
|
13
|
+
cmd = "spec spec/sample_models_spec.rb"
|
14
|
+
puts cmd
|
15
|
+
puts `#{cmd}`
|
16
|
+
end
|
17
|
+
|
18
|
+
desc "Run all tests"
|
19
|
+
Rake::TestTask.new do |t|
|
20
|
+
t.test_files = FileList['test/*.rb']
|
21
|
+
end
|
22
|
+
|
23
|
+
desc 'Generate documentation for the sample_models plugin.'
|
24
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
25
|
+
rdoc.rdoc_dir = 'rdoc'
|
26
|
+
rdoc.title = 'SampleModels'
|
27
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
28
|
+
rdoc.rdoc_files.include('README')
|
29
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
30
|
+
end
|
31
|
+
|
32
|
+
begin
|
33
|
+
require 'jeweler'
|
34
|
+
Jeweler::Tasks.new do |gem|
|
35
|
+
gem.name = "sample_models"
|
36
|
+
gem.summary = %Q{A library for making it extremely fast for Rails developers to set up and save ActiveRecord instances when writing test cases}
|
37
|
+
gem.description = %Q{
|
38
|
+
A library for making it extremely fast for Rails developers to set up and save ActiveRecord instances when writing test cases. It aims to:
|
39
|
+
|
40
|
+
* meet all your validations automatically
|
41
|
+
* only make you specify the attributes you care about
|
42
|
+
* give you a rich set of features so you can specify associated values as concisely as possible
|
43
|
+
* do this with as little configuration as possible
|
44
|
+
}
|
45
|
+
gem.email = "francis.hwang@profitably.com"
|
46
|
+
gem.homepage = "http://github.com/fhwang/sample_models"
|
47
|
+
gem.authors = ["Francis Hwang"]
|
48
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
49
|
+
end
|
50
|
+
Jeweler::GemcutterTasks.new
|
51
|
+
rescue LoadError
|
52
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
53
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.9.0
|
data/init.rb
ADDED
data/install.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Install hook code here
|
@@ -0,0 +1,116 @@
|
|
1
|
+
module SampleModels
|
2
|
+
class Creation
|
3
|
+
def initialize(sampler, attrs)
|
4
|
+
@sampler = sampler
|
5
|
+
@orig_attrs = HashWithIndifferentAccess.new attrs
|
6
|
+
@attrs = Attributes.new(@sampler, attrs)
|
7
|
+
end
|
8
|
+
|
9
|
+
def model
|
10
|
+
SampleModels.models[@sampler.model_class]
|
11
|
+
end
|
12
|
+
|
13
|
+
def polymorphic_assoc_value(assoc)
|
14
|
+
if klass = @sampler.polymorphic_default_classes[assoc.name]
|
15
|
+
klass.sample
|
16
|
+
else
|
17
|
+
SampleModels.samplers.values.map(&:model_class).detect { |m|
|
18
|
+
m != @sampler.model_class
|
19
|
+
}.sample
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def run
|
24
|
+
@instance = @sampler.model_class.new @attrs
|
25
|
+
save!
|
26
|
+
update_associations
|
27
|
+
@instance
|
28
|
+
end
|
29
|
+
|
30
|
+
def save!
|
31
|
+
@sampler.save!(@instance, @orig_attrs)
|
32
|
+
end
|
33
|
+
|
34
|
+
def update_associations
|
35
|
+
needs_another_save = false
|
36
|
+
model.belongs_to_associations.each do |assoc|
|
37
|
+
unless @instance.send(assoc.name) || @attrs.has_key?(assoc.name) ||
|
38
|
+
@attrs.has_key?(assoc.association_foreign_key)
|
39
|
+
if assoc.options[:polymorphic]
|
40
|
+
needs_another_save = true
|
41
|
+
@instance.send "#{assoc.name}=", polymorphic_assoc_value(assoc)
|
42
|
+
elsif @sampler.model_class != assoc.klass
|
43
|
+
needs_another_save = true
|
44
|
+
@instance.send "#{assoc.name}=", assoc.klass.sample
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
save! if needs_another_save
|
49
|
+
end
|
50
|
+
|
51
|
+
class Attributes < HashWithIndifferentAccess
|
52
|
+
def initialize(sampler, hash)
|
53
|
+
@sampler = sampler
|
54
|
+
hash = Sampler.reify_association_hashes model, hash
|
55
|
+
super(hash)
|
56
|
+
fill_based_on_validations
|
57
|
+
fill_based_on_configured_defaults
|
58
|
+
model.columns.each do |column|
|
59
|
+
unless has_key?(column.name)
|
60
|
+
fill_based_on_column_type column
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def model
|
66
|
+
SampleModels.models[@sampler.model_class]
|
67
|
+
end
|
68
|
+
|
69
|
+
def fill_based_on_column_type(column)
|
70
|
+
case column.type
|
71
|
+
when :string
|
72
|
+
fill_based_on_string_column_type(column)
|
73
|
+
when :integer
|
74
|
+
unless model.belongs_to_associations.any? { |assoc|
|
75
|
+
assoc.primary_key_name == column.name
|
76
|
+
}
|
77
|
+
self[column.name] = 1
|
78
|
+
end
|
79
|
+
when :datetime
|
80
|
+
self[column.name] = Time.now.utc
|
81
|
+
when :float
|
82
|
+
self[column.name] = 1.0
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def fill_based_on_configured_defaults
|
87
|
+
@sampler.configured_default_attrs.each do |attr, val|
|
88
|
+
self[attr] = val unless has_key?(attr)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def fill_based_on_string_column_type(column)
|
93
|
+
unless model.belongs_to_associations.any? { |assoc|
|
94
|
+
assoc.options[:polymorphic] &&
|
95
|
+
assoc.options[:foreign_type] = column.name
|
96
|
+
}
|
97
|
+
self[column.name] = "#{column.name}"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def fill_based_on_validations
|
102
|
+
model.validation_collections.each do |field, validation_collection|
|
103
|
+
assoc_key = nil
|
104
|
+
if assoc = model.belongs_to_associations.detect { |a|
|
105
|
+
a.association_foreign_key == field.to_s
|
106
|
+
}
|
107
|
+
assoc_key = assoc.name
|
108
|
+
end
|
109
|
+
unless has_key?(field) || (assoc_key && has_key?(assoc_key))
|
110
|
+
self[field] = validation_collection.satisfying_value
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module SampleModels
|
2
|
+
class Finder
|
3
|
+
def initialize(model, attrs)
|
4
|
+
@model = model
|
5
|
+
attrs = Sampler.reify_association_hashes @model, attrs.clone
|
6
|
+
@attrs = HashWithIndifferentAccess.new attrs
|
7
|
+
@ar_query = ARQuery.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def add_empty_has_many_subselect(assoc)
|
11
|
+
value = @attrs[assoc.name]
|
12
|
+
not_matching_subselect = @model.construct_finder_sql(
|
13
|
+
:select => "#{@model.table_name}.id", :joins => assoc.name,
|
14
|
+
:group => "#{@model.table_name}.id"
|
15
|
+
)
|
16
|
+
@ar_query.condition_sqls << "id not in (#{not_matching_subselect})"
|
17
|
+
end
|
18
|
+
|
19
|
+
def add_non_empty_has_many_subselect(assoc)
|
20
|
+
@ar_query.condition_sqls << has_many_matching_subselect(assoc)
|
21
|
+
value = @attrs[assoc.name]
|
22
|
+
not_matching_subselect = @model.construct_finder_sql(
|
23
|
+
:select => "#{@model.table_name}.id", :joins => assoc.name,
|
24
|
+
:conditions => [
|
25
|
+
"#{assoc.klass.table_name}.id not in (?)", value.map(&:id)
|
26
|
+
],
|
27
|
+
:group => "#{@model.table_name}.id"
|
28
|
+
)
|
29
|
+
@ar_query.condition_sqls << "id not in (#{not_matching_subselect})"
|
30
|
+
end
|
31
|
+
|
32
|
+
def attach_belongs_to_associations_to_query
|
33
|
+
@model.belongs_to_associations.each do |assoc|
|
34
|
+
if @attrs.keys.include?(assoc.name.to_s)
|
35
|
+
@ar_query.conditions[assoc.primary_key_name] = if @attrs[assoc.name]
|
36
|
+
@attrs[assoc.name].id
|
37
|
+
else
|
38
|
+
@attrs[assoc.name]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def attach_non_associated_attrs_to_query
|
45
|
+
@attrs.each do |k,v|
|
46
|
+
if @model.column_names.include?(k.to_s)
|
47
|
+
@ar_query.conditions[k] = v
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def has_many_matching_subselect(assoc)
|
53
|
+
value = @attrs[assoc.name]
|
54
|
+
matching_inner_subselect = @model.construct_finder_sql(
|
55
|
+
:select =>
|
56
|
+
"#{@model.table_name}.id, count(#{assoc.klass.table_name}.id) as count",
|
57
|
+
:joins => assoc.name,
|
58
|
+
:conditions => [
|
59
|
+
"#{assoc.klass.table_name}.id in (?)", value.map(&:id)
|
60
|
+
],
|
61
|
+
:group => "#{@model.table_name}.id"
|
62
|
+
)
|
63
|
+
"id in (select matching.id from (#{matching_inner_subselect}) as matching where matching.count = #{value.size})"
|
64
|
+
end
|
65
|
+
|
66
|
+
def instance
|
67
|
+
attach_non_associated_attrs_to_query
|
68
|
+
attach_belongs_to_associations_to_query
|
69
|
+
@model.has_many_associations.each do |assoc|
|
70
|
+
if @attrs.keys.include?(assoc.name.to_s)
|
71
|
+
if @attrs[assoc.name].empty?
|
72
|
+
add_empty_has_many_subselect assoc
|
73
|
+
else
|
74
|
+
add_non_empty_has_many_subselect assoc
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
@model.first @ar_query.to_hash
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|