sample_models 1.2.6 → 2.0.0
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.
- data/Gemfile +6 -6
- data/Gemfile.lock +22 -7
- data/README.markdown +21 -42
- data/Rakefile +10 -14
- data/VERSION +1 -1
- data/lib/sample_models/attribute_sequence.rb +182 -0
- data/lib/sample_models/creation.rb +136 -89
- data/lib/sample_models/initializer.rb +48 -0
- data/lib/sample_models/model.rb +52 -143
- data/lib/sample_models/sampler.rb +64 -88
- data/lib/sample_models.rb +15 -95
- data/sample_models.gemspec +14 -18
- data/test/setup/models.rb +205 -0
- data/test/setup/schema.rb +108 -0
- data/test/test_helper.rb +42 -0
- data/test/unit/belongs_to_test.rb +143 -0
- data/test/unit/configuration_test.rb +85 -0
- data/test/unit/has_many_through_test.rb +46 -0
- data/test/unit/named_sample_test.rb +15 -0
- data/test/unit/polymorphic_belongs_to_test.rb +29 -0
- data/test/unit/sample_test.rb +134 -0
- metadata +16 -20
- data/lib/sample_models/finder.rb +0 -86
- data/spec/sample_models_spec.rb +0 -11
- data/spec_or_test/database.yml +0 -6
- data/spec_or_test/setup.rb +0 -288
- data/spec_or_test/specs_or_test_cases.rb +0 -586
- data/test/test_sample_models.rb +0 -28
- data/vendor/ar_query/MIT-LICENSE +0 -20
- data/vendor/ar_query/README +0 -0
- data/vendor/ar_query/ar_query.gemspec +0 -16
- data/vendor/ar_query/init.rb +0 -1
- data/vendor/ar_query/install.rb +0 -1
- data/vendor/ar_query/lib/ar_query.rb +0 -146
- data/vendor/ar_query/spec/ar_query_spec.rb +0 -318
- data/vendor/ar_query/tasks/ar_query_tasks.rake +0 -0
- data/vendor/ar_query/uninstall.rb +0 -1
data/Gemfile
CHANGED
@@ -1,13 +1,8 @@
|
|
1
1
|
source "http://rubygems.org"
|
2
|
-
# Add dependencies required to use your gem here.
|
3
|
-
# Example:
|
4
|
-
# gem "activesupport", ">= 2.3.5"
|
5
2
|
|
6
3
|
gem 'activerecord', ENV['ACTIVE_RECORD_VERSION']
|
7
4
|
gem "activesupport", ENV['ACTIVE_RECORD_VERSION']
|
8
5
|
|
9
|
-
# Add dependencies to develop your gem here.
|
10
|
-
# Include everything needed to run rake, tests, features, etc.
|
11
6
|
group :development do
|
12
7
|
gem "bundler", "~> 1.0.0"
|
13
8
|
gem "jeweler", "~> 1.6.0"
|
@@ -15,5 +10,10 @@ end
|
|
15
10
|
|
16
11
|
group :test do
|
17
12
|
gem 'sqlite3'
|
18
|
-
|
13
|
+
version_str = if ENV['ACTIVE_RECORD_VERSION'] =~ /^3\./
|
14
|
+
"~> 1.5.0"
|
15
|
+
else
|
16
|
+
"~> 1.4.0"
|
17
|
+
end
|
18
|
+
gem "validates_email_format_of", version_str
|
19
19
|
end
|
data/Gemfile.lock
CHANGED
@@ -1,25 +1,40 @@
|
|
1
1
|
GEM
|
2
2
|
remote: http://rubygems.org/
|
3
3
|
specs:
|
4
|
-
|
5
|
-
activesupport (=
|
6
|
-
|
4
|
+
activemodel (3.1.0)
|
5
|
+
activesupport (= 3.1.0)
|
6
|
+
bcrypt-ruby (~> 3.0.0)
|
7
|
+
builder (~> 3.0.0)
|
8
|
+
i18n (~> 0.6)
|
9
|
+
activerecord (3.1.0)
|
10
|
+
activemodel (= 3.1.0)
|
11
|
+
activesupport (= 3.1.0)
|
12
|
+
arel (~> 2.2.1)
|
13
|
+
tzinfo (~> 0.3.29)
|
14
|
+
activesupport (3.1.0)
|
15
|
+
multi_json (~> 1.0)
|
16
|
+
arel (2.2.1)
|
17
|
+
bcrypt-ruby (3.0.1)
|
18
|
+
builder (3.0.0)
|
7
19
|
git (1.2.5)
|
20
|
+
i18n (0.6.0)
|
8
21
|
jeweler (1.6.0)
|
9
22
|
bundler (~> 1.0.0)
|
10
23
|
git (>= 1.2.5)
|
11
24
|
rake
|
25
|
+
multi_json (1.0.3)
|
12
26
|
rake (0.8.7)
|
13
27
|
sqlite3 (1.3.4)
|
14
|
-
|
28
|
+
tzinfo (0.3.29)
|
29
|
+
validates_email_format_of (1.5.2)
|
15
30
|
|
16
31
|
PLATFORMS
|
17
32
|
ruby
|
18
33
|
|
19
34
|
DEPENDENCIES
|
20
|
-
activerecord (=
|
21
|
-
activesupport (=
|
35
|
+
activerecord (= 3.1.0)
|
36
|
+
activesupport (= 3.1.0)
|
22
37
|
bundler (~> 1.0.0)
|
23
38
|
jeweler (~> 1.6.0)
|
24
39
|
sqlite3
|
25
|
-
validates_email_format_of
|
40
|
+
validates_email_format_of (~> 1.5.0)
|
data/README.markdown
CHANGED
@@ -8,7 +8,7 @@ A library for making it extremely fast for Rails developers to set up and save A
|
|
8
8
|
* give you a rich set of features so you can specify associations as concisely as possible
|
9
9
|
* do this with as little configuration as possible
|
10
10
|
|
11
|
-
|
11
|
+
Overview
|
12
12
|
================
|
13
13
|
|
14
14
|
Let's say you've got a set of models that look like this:
|
@@ -69,6 +69,11 @@ You can specify associated records in the sample call:
|
|
69
69
|
sad = Tag.sample(:tag => 'sad')
|
70
70
|
funny_yet_sad = BlogPost.sample(:tags => [funny, sad])
|
71
71
|
|
72
|
+
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:
|
73
|
+
|
74
|
+
jane = User.sample(:first_name => 'Jane')
|
75
|
+
BlogPost.sample(jane, :title => 'What I ate for lunch')
|
76
|
+
|
72
77
|
You can also specify associated records by passing in hashes or arrays:
|
73
78
|
|
74
79
|
bills_post2 = BlogPost.sample(:user => {:first_name => 'Bill'})
|
@@ -78,11 +83,6 @@ You can also specify associated records by passing in hashes or arrays:
|
|
78
83
|
:tags => [{:tag => 'funny'}, {:tag => 'sad'}]
|
79
84
|
)
|
80
85
|
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
86
|
|
87
87
|
Instance attributes
|
88
88
|
=========================
|
@@ -94,12 +94,12 @@ SampleModels reads your validations to get hints about how to craft an instance
|
|
94
94
|
validates_email_format_of
|
95
95
|
-------------------------
|
96
96
|
|
97
|
-
If you use the validates_email_format_of
|
97
|
+
If you use the [validates_email_format_of gem](http://rubygems.org/gems/validates_email_format_of), SampleModels will ensure that the attribute in question is a valid email address.
|
98
98
|
|
99
99
|
validates_presence_of
|
100
100
|
---------------------
|
101
101
|
|
102
|
-
SampleModels already sets
|
102
|
+
SampleModels already sets database columns to be non-blank, but this validation comes in handy if you have an `attr_accessor`:
|
103
103
|
|
104
104
|
class UserWithPassword < ActiveRecord::Base
|
105
105
|
attr_accessor :password
|
@@ -123,35 +123,6 @@ validates_uniqueness_of
|
|
123
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
124
|
|
125
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
126
|
Associations
|
156
127
|
============
|
157
128
|
|
@@ -170,7 +141,7 @@ You can also specify these associations as if you were calling `new` or `create!
|
|
170
141
|
BlogPost.sample(:user => kelley)
|
171
142
|
BlogPost.sample(:user_id => kelley.id)
|
172
143
|
|
173
|
-
If you want, you can simply specify the record at the beginning of the argument list for `sample
|
144
|
+
If you want, you can simply specify the record at the beginning of the argument list for `sample`, and SampleModels will assign them to the appropriate association, as long as there's only one association that fits the class.
|
174
145
|
|
175
146
|
kim = User.sample(:first_name => 'Kim')
|
176
147
|
BlogPost.sample(kim, :title => 'funny')
|
@@ -201,8 +172,8 @@ If you want, you can simply specify the important attributes of the associated v
|
|
201
172
|
You can combine the two syntaxes in deeper associations:
|
202
173
|
|
203
174
|
bb_episode = Video.sample(:show => [amc, {:name => 'Breaking Bad'}])
|
204
|
-
puts bb_episode.show.network.name
|
205
|
-
puts bb_episode.show.name
|
175
|
+
puts bb_episode.show.network.name # => 'AMC'
|
176
|
+
puts bb_episode.show.name # => 'Breaking Bad'
|
206
177
|
|
207
178
|
Polymorphic belongs-to associations
|
208
179
|
-----------------------------------
|
@@ -277,7 +248,7 @@ With `before_save` you can specify a block that runs before the record is saved.
|
|
277
248
|
end
|
278
249
|
end
|
279
250
|
|
280
|
-
You can also take a second argument, which will pass in the hash that was used during the call to `sample
|
251
|
+
You can also take a second argument, which will pass in the hash that was used during the call to `sample`.
|
281
252
|
|
282
253
|
SampleModels.configure(Appointment) do |appt|
|
283
254
|
appt.before_save do |appt_record, sample_attrs|
|
@@ -330,7 +301,7 @@ Named samples can be used to pre-set values for commonly used combinations of at
|
|
330
301
|
bp1 = BlogPost.sample(:funny)
|
331
302
|
puts bp1.title # => 'Laugh already'
|
332
303
|
|
333
|
-
bp2 = BlogPost.
|
304
|
+
bp2 = BlogPost.sample(:funny)
|
334
305
|
puts bp2.title # => 'Laugh already'
|
335
306
|
puts (bp1 == bp2) # => false
|
336
307
|
|
@@ -338,6 +309,14 @@ You can override individual attributes, as well:
|
|
338
309
|
|
339
310
|
bp3 = BlogPost.sample(:funny, :average_rating => 4.0)
|
340
311
|
puts bp3.average_rating # => 4.0
|
312
|
+
|
313
|
+
Backwards-incompatible changes in SampleModels 2
|
314
|
+
================================================
|
315
|
+
|
316
|
+
`sample` always creates a new record now. This is a change from SampleModels 1, which would first attempt to find an existing record in the database that satisfied the stated attributes. In practice, that ended up making tests too confusing.
|
317
|
+
|
318
|
+
`create_sample` and `sample` now do the same thing, and `create_sample` is deprecated.
|
319
|
+
|
341
320
|
|
342
321
|
About
|
343
322
|
=====
|
data/Rakefile
CHANGED
@@ -3,28 +3,24 @@ require 'rake/testtask'
|
|
3
3
|
require 'rake/rdoctask'
|
4
4
|
require 'rubygems'
|
5
5
|
|
6
|
-
|
6
|
+
Rake::TestTask.new do |t|
|
7
|
+
t.test_files = FileList['test/unit/*_test.rb']
|
8
|
+
t.verbose = true
|
9
|
+
end
|
10
|
+
|
11
|
+
ActiveRecordVersions = %w(3.1.1 3.0.10 2.3.14)
|
7
12
|
|
8
|
-
desc "Run all tests"
|
9
|
-
task :
|
13
|
+
desc "Run all tests, for all tested versions of ActiveRecord"
|
14
|
+
task :all_tests do
|
10
15
|
ActiveRecordVersions.each do |ar_version|
|
11
|
-
cmd = "ACTIVE_RECORD_VERSION=#{ar_version}
|
16
|
+
cmd = "ACTIVE_RECORD_VERSION=#{ar_version} rake test"
|
12
17
|
puts cmd
|
13
18
|
puts `cd . && #{cmd}`
|
14
19
|
puts
|
15
20
|
end
|
16
21
|
end
|
17
22
|
|
18
|
-
task :default => :
|
19
|
-
|
20
|
-
desc 'Generate documentation for the sample_models plugin.'
|
21
|
-
Rake::RDocTask.new(:rdoc) do |rdoc|
|
22
|
-
rdoc.rdoc_dir = 'rdoc'
|
23
|
-
rdoc.title = 'SampleModels'
|
24
|
-
rdoc.options << '--line-numbers' << '--inline-source'
|
25
|
-
rdoc.rdoc_files.include('README')
|
26
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
27
|
-
end
|
23
|
+
task :default => :all_tests
|
28
24
|
|
29
25
|
require 'jeweler'
|
30
26
|
Jeweler::Tasks.new do |gem|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
2.0.0
|
@@ -0,0 +1,182 @@
|
|
1
|
+
module SampleModels
|
2
|
+
class AttributeSequence
|
3
|
+
def self.build(*args)
|
4
|
+
Builder.new(*args).run
|
5
|
+
end
|
6
|
+
|
7
|
+
def initialize(model, column, validation, input)
|
8
|
+
@model, @column, @validation, @input = model, column, validation, input
|
9
|
+
@number = 0
|
10
|
+
end
|
11
|
+
|
12
|
+
def belongs_to_association
|
13
|
+
@model.belongs_to_associations.detect { |a|
|
14
|
+
a.foreign_key == @column.name
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
def next
|
19
|
+
@number += 1
|
20
|
+
@input.next if @input
|
21
|
+
value
|
22
|
+
end
|
23
|
+
|
24
|
+
def value
|
25
|
+
case @column.type
|
26
|
+
when :string, :text
|
27
|
+
"#{@column.name} #{@number}"
|
28
|
+
when :integer
|
29
|
+
belongs_to_association ? belongs_to_assoc_foreign_key_value : @number
|
30
|
+
when :datetime
|
31
|
+
Time.now.utc - @number.minutes
|
32
|
+
when :date
|
33
|
+
Date.today - @number
|
34
|
+
when :float
|
35
|
+
@number.to_f
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class Builder
|
40
|
+
def initialize(pass, model, column, force_unique)
|
41
|
+
@pass, @model, @column, @force_unique =
|
42
|
+
pass, model, column, force_unique
|
43
|
+
end
|
44
|
+
|
45
|
+
def base
|
46
|
+
base_class = SampleModels.const_get(
|
47
|
+
"#{@pass.to_s.capitalize}PassBaseAttributeSequence"
|
48
|
+
)
|
49
|
+
base_class.new(@model, @column)
|
50
|
+
end
|
51
|
+
|
52
|
+
def run
|
53
|
+
input = base
|
54
|
+
uniqueness_validation = if @force_unique
|
55
|
+
Model::Validation.new(:validates_uniqueness_of)
|
56
|
+
end
|
57
|
+
@model.validations(@column.name).each do |validation|
|
58
|
+
if validation.type == :validates_uniqueness_of
|
59
|
+
uniqueness_validation = validation
|
60
|
+
elsif s_class = sequence_class(validation)
|
61
|
+
input = s_class.new(@model, @column, validation, input)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
if uniqueness_validation
|
65
|
+
input = ValidatesUniquenessOfAttributeSequence.new(
|
66
|
+
@model, @column, uniqueness_validation, input
|
67
|
+
)
|
68
|
+
end
|
69
|
+
input
|
70
|
+
end
|
71
|
+
|
72
|
+
def sequence_class(validation)
|
73
|
+
sequence_name = validation.type.to_s.camelize + 'AttributeSequence'
|
74
|
+
if SampleModels.const_defined?(sequence_name)
|
75
|
+
SampleModels.const_get(sequence_name)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class FirstPassBaseAttributeSequence < AttributeSequence
|
82
|
+
def initialize(model, column)
|
83
|
+
super(model, column, nil, nil)
|
84
|
+
end
|
85
|
+
|
86
|
+
def belongs_to_assoc_foreign_key_value
|
87
|
+
nil
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class SecondPassBaseAttributeSequence < AttributeSequence
|
92
|
+
def initialize(model, column)
|
93
|
+
super(model, column, nil, nil)
|
94
|
+
@previous_values = {}
|
95
|
+
end
|
96
|
+
|
97
|
+
def belongs_assoc_value_already_used?(record)
|
98
|
+
@previous_values.any? { |prev_num, prev_record|
|
99
|
+
prev_record == record && prev_num != @number
|
100
|
+
}
|
101
|
+
end
|
102
|
+
|
103
|
+
def belongs_to_assoc_foreign_key_value
|
104
|
+
assoc_klass = belongs_to_association.klass
|
105
|
+
unless assoc_klass == @model.ar_class
|
106
|
+
record = (assoc_klass.last || assoc_klass.sample)
|
107
|
+
while belongs_assoc_value_already_used?(record)
|
108
|
+
record = assoc_klass.sample
|
109
|
+
end
|
110
|
+
@previous_values[@number] = record
|
111
|
+
record.id
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
class ValidatesEmailFormatOfAttributeSequence < AttributeSequence
|
117
|
+
def value
|
118
|
+
"john.doe.#{@number}@example.com"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
class ValidatesInclusionOfAttributeSequence < AttributeSequence
|
123
|
+
def value
|
124
|
+
@validation.config[:in].first
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
class ValidatesPresenceOfAttributeSequence < AttributeSequence
|
129
|
+
def belongs_to_value
|
130
|
+
@previous_belongs_to_instances ||= {}
|
131
|
+
if @previous_belongs_to_instances[@number]
|
132
|
+
value = @previous_belongs_to_instances[@number]
|
133
|
+
begin
|
134
|
+
value.reload
|
135
|
+
value.id
|
136
|
+
rescue ActiveRecord::RecordNotFound
|
137
|
+
set_belongs_to_instance
|
138
|
+
@previous_belongs_to_instances[@number].id
|
139
|
+
end
|
140
|
+
else
|
141
|
+
set_belongs_to_instance
|
142
|
+
@previous_belongs_to_instances[@number].id
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def existing_instance_not_previously_returned
|
147
|
+
previous_ids = @previous_belongs_to_instances.values.map(&:id)
|
148
|
+
instance = nil
|
149
|
+
if previous_ids.empty?
|
150
|
+
belongs_to_association.klass.last
|
151
|
+
else
|
152
|
+
belongs_to_association.klass.last(
|
153
|
+
:conditions => ["id not in (?)", previous_ids]
|
154
|
+
)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def set_belongs_to_instance
|
159
|
+
instance = existing_instance_not_previously_returned
|
160
|
+
instance ||= belongs_to_association.klass.sample
|
161
|
+
@previous_belongs_to_instances[@number] = instance
|
162
|
+
end
|
163
|
+
|
164
|
+
def value
|
165
|
+
belongs_to_association ? belongs_to_value : super
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
class ValidatesUniquenessOfAttributeSequence < AttributeSequence
|
170
|
+
def value
|
171
|
+
v = @input.value
|
172
|
+
unless @validation.config[:allow_nil] && v.nil?
|
173
|
+
unless @validation.config[:allow_blank] && v.blank?
|
174
|
+
until @model.unique?(@column.name, v)
|
175
|
+
v = @input.next
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
v
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|