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.
@@ -1,115 +1,91 @@
1
1
  module SampleModels
2
2
  class Sampler
3
- def self.reified_belongs_to_associations(model, attrs)
4
- assocs = {}
5
- model.belongs_to_associations.each do |assoc|
6
- if value = attrs[assoc.name]
7
- if value.is_a?(Hash)
8
- assocs[assoc.name] = assoc.klass.sample(value)
9
- elsif value.is_a?(Array)
10
- assocs[assoc.name] = assoc.klass.sample(*value)
11
- end
12
- end
13
- end
14
- assocs
3
+ attr_accessor :before_save, :named_samples, :polymorphic_default_classes
4
+ attr_reader :defaults
5
+
6
+ def initialize(model_class)
7
+ @model_class = model_class
8
+ @attribute_sequences = Hash.new { |h,k| h[k] = {} }
9
+ @defaults = HashWithIndifferentAccess.new
10
+ @forced_unique = []
11
+ @named_samples = HashWithIndifferentAccess.new
12
+ @polymorphic_default_classes = HashWithIndifferentAccess.new
15
13
  end
16
14
 
17
- def self.reify_association_hashes(model, attrs)
18
- a = attrs.clone
19
- reified_belongs_to_associations(model, attrs).each do |name, value|
20
- a[name] = value
21
- end
22
- model.has_many_associations.each do |assoc|
23
- if values = a[assoc.name]
24
- a[assoc.name] = values.map { |value|
25
- value.is_a?(Hash) ? assoc.klass.sample(value) : value
26
- }
27
- end
15
+ def attribute_sequence(pass, column)
16
+ @attribute_sequences[pass][column.name] ||= begin
17
+ AttributeSequence.build(
18
+ pass, model, column, @forced_unique.include?(column.name)
19
+ )
28
20
  end
29
- a
30
21
  end
31
22
 
32
- attr_accessor :before_save
33
- attr_reader :configured_default_attrs, :model_class, :named_sample_attrs,
34
- :polymorphic_default_classes
35
-
36
- def initialize(model_class)
37
- @model_class = model_class
38
- @configured_default_attrs = {}
39
- @named_sample_attrs = SampleModels.hash_with_indifferent_access_class.new
40
- @polymorphic_default_classes =
41
- SampleModels.hash_with_indifferent_access_class.new
23
+ def configure(block)
24
+ recipient = ConfigureRecipient.new(self)
25
+ block.call(recipient)
42
26
  end
43
27
 
44
- def create_sample(*args)
45
- attrs = preprocessed_attributes(*args)
46
- Creation.new(self, attrs).run
28
+ def first_pass_attribute_sequence(column)
29
+ attribute_sequence(:first, column)
47
30
  end
48
31
 
49
- def fix_deleted_associations(instance, orig_attrs)
50
- needs_save = false
51
- model.belongs_to_associations.each do |assoc|
52
- foreign_key = if assoc.respond_to?(:foreign_key)
53
- assoc.foreign_key
54
- else
55
- assoc.primary_key_name
56
- end
57
- if instance.send(foreign_key) && !instance.send(assoc.name)
58
- instance.send("#{assoc.name}=", assoc.klass.sample)
59
- needs_save = true
60
- end
61
- end
62
- save!(instance, orig_attrs) if needs_save
32
+ def force_unique(attr)
33
+ @forced_unique << attr.to_s
63
34
  end
64
-
35
+
65
36
  def model
66
37
  SampleModels.models[@model_class]
67
38
  end
68
39
 
69
- def preprocessed_attributes(*args)
70
- if args.first.is_a?(Symbol)
71
- preprocessed_named_sample_attrs(args)
72
- else
73
- attrs = args.last.is_a?(Hash) ? args.pop : {}
74
- args.each do |associated_value|
75
- assocs = model.associations(associated_value.class)
76
- if assocs.size == 1
77
- attrs[assocs.first.name] = associated_value
78
- else
79
- raise "Not sure what to do with associated value #{associated_value.inspect}"
80
- end
81
- end
82
- attrs
83
- end
40
+ def sample(*args)
41
+ Creation.new(self, *args).run
84
42
  end
85
43
 
86
- def preprocessed_named_sample_attrs(args)
87
- attrs = named_sample_attrs[args.shift]
88
- attrs = attrs.merge(args.first) unless args.empty?
89
- attrs
44
+ def second_pass_attribute_sequence(column)
45
+ attribute_sequence(:second, column)
90
46
  end
91
47
 
92
- def sample(*args)
93
- attrs = preprocessed_attributes(*args)
94
- instance = Finder.new(model, attrs).instance
95
- if instance
96
- fix_deleted_associations(instance, attrs)
97
- else
98
- instance = create_sample attrs
48
+ class ConfigureRecipient
49
+ def initialize(sampler)
50
+ @sampler = sampler
99
51
  end
100
- instance
101
- end
102
-
103
- def save!(instance, orig_attrs)
104
- if @before_save
105
- if @before_save.arity == 1
106
- @before_save.call instance
52
+
53
+ def method_missing(meth, *args, &block)
54
+ if @sampler.model.column_names.include?(meth.to_s)
55
+ Attribute.new(@sampler, meth)
56
+ elsif @sampler.model.belongs_to_association(meth)
57
+ Attribute.new(@sampler, meth)
58
+ elsif @sampler.model.instance_methods.include?(meth.to_s) &&
59
+ @sampler.model.instance_methods.include?("#{meth.to_s}=")
60
+ Attribute.new(@sampler, meth)
61
+ elsif meth.to_s =~ /(.*)_sample$/
62
+ @sampler.named_samples[$1] = args.first
107
63
  else
108
- @before_save.call instance, orig_attrs
64
+ super
65
+ end
66
+ end
67
+
68
+ def before_save(&proc)
69
+ @sampler.before_save = proc
70
+ end
71
+
72
+ class Attribute
73
+ def initialize(sampler, attribute)
74
+ @sampler, @attribute = sampler, attribute
75
+ end
76
+
77
+ def default(default)
78
+ @sampler.defaults[@attribute] = default
79
+ end
80
+
81
+ def default_class(dc)
82
+ @sampler.polymorphic_default_classes[@attribute] = dc
83
+ end
84
+
85
+ def force_unique
86
+ @sampler.force_unique(@attribute)
109
87
  end
110
88
  end
111
- instance.save!
112
89
  end
113
90
  end
114
91
  end
115
-
data/lib/sample_models.rb CHANGED
@@ -8,119 +8,39 @@ module SampleModels
8
8
  @@samplers = Hash.new { |h, model_class|
9
9
  h[model_class] = Sampler.new(model_class)
10
10
  }
11
-
12
- def self.configure(model_class, opts ={})
13
- yield ConfigureRecipient.new(model_class) if block_given?
11
+
12
+ def self.configure(model_class, &block)
13
+ samplers[model_class].configure(block)
14
14
  end
15
15
 
16
- def self.hash_with_indifferent_access_class
17
- HashWithIndifferentAccess
16
+ def self.init
17
+ Initializer.new.run
18
18
  end
19
-
19
+
20
20
  protected
21
21
 
22
- def self.included( mod )
22
+ def self.included(mod)
23
23
  mod.extend ARClassMethods
24
24
  super
25
25
  end
26
-
27
- class ConfigureRecipient
28
- def initialize(model_class)
29
- @model_class = model_class
30
- end
31
-
32
- def before_save(&proc)
33
- sampler.before_save = proc
34
- end
35
-
36
- def method_missing(meth, *args)
37
- if meth.to_s =~ /(.*)_sample$/
38
- sampler.named_sample_attrs[$1] = args.first
39
- else
40
- Attribute.new(sampler, meth)
41
- end
42
- end
43
-
44
- def sampler
45
- SampleModels.samplers[@model_class]
46
- end
47
-
48
- class Attribute
49
- def initialize(sampler, attribute)
50
- @sampler, @attribute = sampler, attribute
51
- end
52
-
53
- def default_class(dc)
54
- @sampler.polymorphic_default_classes[@attribute] = dc
55
- end
56
-
57
- def default(default)
58
- if default.blank? and model.validates_presence_of?(@attribute)
59
- raise "#{model.name} requires #{@attribute} to not be blank"
60
- else
61
- @sampler.configured_default_attrs[@attribute] = default
62
- end
63
- end
64
-
65
- def force_unique
66
- model.record_validation :validates_uniqueness_of, @attribute
67
- end
68
-
69
- def model
70
- SampleModels.models[@sampler.model_class]
71
- end
72
- end
73
- end
74
-
26
+
75
27
  module ARClassMethods
76
28
  def create_sample(*args)
77
- sampler = SampleModels.samplers[self]
78
- sampler.create_sample(*args)
29
+ ActiveSupport::Deprecation.warn("#{self.name}.create_sample is deprecated and will be removed in the next major version of SampleModels. Call #{self.name}.sample instead")
30
+ sample(*args)
79
31
  end
80
32
 
81
33
  def sample(*args)
82
- sampler = SampleModels.samplers[self]
83
- sampler.sample(*args)
34
+ SampleModels.samplers[self].sample(*args)
84
35
  end
85
36
  end
86
37
  end
87
38
 
88
- module ActiveRecord
89
- class Base
90
- include SampleModels
39
+ Dir.entries(File.dirname(__FILE__) + "/sample_models").each do |entry|
40
+ if entry =~ /(.*)\.rb$/
41
+ require "sample_models/#{$1}"
91
42
  end
92
43
  end
93
44
 
94
- validation_recipients = [ActiveRecord::Validations::ClassMethods]
95
- if Object.const_defined?('ActiveModel')
96
- validation_recipients << ActiveModel::Validations::HelperMethods
97
- end
98
- validations_to_intercept = [
99
- :validates_email_format_of, :validates_inclusion_of, :validates_presence_of,
100
- :validates_uniqueness_of
101
- ]
102
- optional_interceptions = [:validates_email_format_of]
103
- validations_to_intercept.each do |validation|
104
- recipient = validation_recipients.detect { |vr|
105
- vr.method_defined?(validation)
106
- }
107
- if recipient
108
- method_name = "#{validation}_with_sample_models".to_sym
109
- recipient.send(:define_method, method_name) do |*args|
110
- send "#{validation}_without_sample_models".to_sym, *args
111
- SampleModels.models[self].record_validation(validation, *args)
112
- end
113
- recipient.alias_method_chain validation, :sample_models
114
- else
115
- unless optional_interceptions.include?(validation)
116
- raise "Can't find who defines the validation method #{validation}"
117
- end
118
- end
119
- end
120
-
121
- require "#{File.dirname(__FILE__)}/sample_models/creation"
122
- require "#{File.dirname(__FILE__)}/sample_models/finder"
123
- require "#{File.dirname(__FILE__)}/sample_models/model"
124
- require "#{File.dirname(__FILE__)}/sample_models/sampler"
125
- require "#{File.dirname(__FILE__)}/../vendor/ar_query/lib/ar_query"
45
+ SampleModels.init
126
46
 
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{sample_models}
8
- s.version = "1.2.6"
8
+ s.version = "2.0.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Francis Hwang"]
12
- s.date = %q{2011-11-18}
12
+ s.date = %q{2011-11-22}
13
13
  s.description = %q{
14
14
  A library for making it extremely fast for Rails developers to set up and save ActiveRecord instances when writing test cases. It aims to:
15
15
 
@@ -32,28 +32,24 @@ A library for making it extremely fast for Rails developers to set up and save A
32
32
  "init.rb",
33
33
  "install.rb",
34
34
  "lib/sample_models.rb",
35
+ "lib/sample_models/attribute_sequence.rb",
35
36
  "lib/sample_models/creation.rb",
36
- "lib/sample_models/finder.rb",
37
+ "lib/sample_models/initializer.rb",
37
38
  "lib/sample_models/model.rb",
38
39
  "lib/sample_models/sampler.rb",
39
40
  "sample_models.gemspec",
40
- "spec/sample_models_spec.rb",
41
- "spec_or_test/database.yml",
42
- "spec_or_test/setup.rb",
43
- "spec_or_test/specs_or_test_cases.rb",
44
41
  "tasks/sample_models_tasks.rake",
45
42
  "test/db/placeholder",
46
- "test/test_sample_models.rb",
47
- "uninstall.rb",
48
- "vendor/ar_query/MIT-LICENSE",
49
- "vendor/ar_query/README",
50
- "vendor/ar_query/ar_query.gemspec",
51
- "vendor/ar_query/init.rb",
52
- "vendor/ar_query/install.rb",
53
- "vendor/ar_query/lib/ar_query.rb",
54
- "vendor/ar_query/spec/ar_query_spec.rb",
55
- "vendor/ar_query/tasks/ar_query_tasks.rake",
56
- "vendor/ar_query/uninstall.rb"
43
+ "test/setup/models.rb",
44
+ "test/setup/schema.rb",
45
+ "test/test_helper.rb",
46
+ "test/unit/belongs_to_test.rb",
47
+ "test/unit/configuration_test.rb",
48
+ "test/unit/has_many_through_test.rb",
49
+ "test/unit/named_sample_test.rb",
50
+ "test/unit/polymorphic_belongs_to_test.rb",
51
+ "test/unit/sample_test.rb",
52
+ "uninstall.rb"
57
53
  ]
58
54
  s.homepage = %q{http://github.com/fhwang/sample_models}
59
55
  s.licenses = ["MIT"]
@@ -0,0 +1,205 @@
1
+
2
+ class Appointment < ActiveRecord::Base
3
+ belongs_to :calendar
4
+ belongs_to :category
5
+ belongs_to :user
6
+
7
+ validates_presence_of :calendar_id, :user_id
8
+ validates_uniqueness_of :start_time
9
+ validate :validate_calendar_has_same_user_id
10
+
11
+ def validate_calendar_has_same_user_id
12
+ if calendar.user_id != user_id
13
+ errors.add "Appointment needs same user as the calendar"
14
+ end
15
+ end
16
+ end
17
+
18
+ class BlogPost < ActiveRecord::Base
19
+ has_many :blog_post_tags
20
+ belongs_to :merged_into,
21
+ :class_name => 'BlogPost', :foreign_key => 'merged_into_id'
22
+ has_many :tags, :through => :blog_post_tags
23
+ belongs_to :user
24
+
25
+ validates_presence_of :body
26
+ validates_presence_of :title
27
+ validates_presence_of :user_id
28
+
29
+ attr_accessor :instance_attribute
30
+ end
31
+
32
+ class BlogPostTag < ActiveRecord::Base
33
+ belongs_to :blog_post
34
+ belongs_to :tag
35
+ end
36
+
37
+ class Bookmark < ActiveRecord::Base
38
+ belongs_to :bookmarkable, :polymorphic => true
39
+ end
40
+
41
+ class Calendar < ActiveRecord::Base
42
+ belongs_to :user
43
+
44
+ validates_presence_of :user_id
45
+ end
46
+
47
+ class Category < ActiveRecord::Base
48
+ belongs_to :parent, :class_name => 'Category'
49
+ end
50
+
51
+ class Comment < ActiveRecord::Base
52
+ end
53
+
54
+ class Episode < ActiveRecord::Base
55
+ belongs_to :show
56
+
57
+ validates_presence_of :show_id, :original_air_date
58
+ end
59
+
60
+ class ExternalUser < ActiveRecord::Base
61
+ end
62
+
63
+ class Network < ActiveRecord::Base
64
+ end
65
+
66
+ class Show < ActiveRecord::Base
67
+ belongs_to :network
68
+ end
69
+
70
+ class Subscription < ActiveRecord::Base
71
+ belongs_to :subscribable, :polymorphic => true
72
+ belongs_to :user
73
+ end
74
+
75
+ class Tag < ActiveRecord::Base
76
+ validates_uniqueness_of :tag
77
+
78
+ has_many :blog_post_tags
79
+ has_many :blog_posts, :through => :blog_post_tags
80
+ end
81
+
82
+ class Topic < ActiveRecord::Base
83
+ belongs_to :parent, :class_name => 'Topic'
84
+
85
+ validates_presence_of :parent_id
86
+ end
87
+
88
+ class User < ActiveRecord::Base
89
+ belongs_to :favorite_blog_post,
90
+ :class_name => 'BlogPost', :foreign_key => 'favorite_blog_post_id'
91
+ belongs_to :external_user
92
+
93
+ validates_email_format_of :email
94
+ validates_inclusion_of :gender, :in => %w(f m)
95
+ validates_uniqueness_of :email, :login, :case_sensitive => false
96
+ validates_uniqueness_of :external_user_id, :allow_nil => true
97
+ end
98
+
99
+ class User2 < ActiveRecord::Base
100
+ validates_presence_of :login
101
+ validates_uniqueness_of :login
102
+ end
103
+
104
+ class UserWithPassword < ActiveRecord::Base
105
+ attr_accessor :password
106
+
107
+ validates_presence_of :password
108
+ end
109
+
110
+ class Video < ActiveRecord::Base
111
+ belongs_to :show
112
+ belongs_to :network
113
+ belongs_to :episode
114
+
115
+ validate :validate_episode_has_same_show_id
116
+
117
+ def validate_episode_has_same_show_id
118
+ if episode && episode.show_id != show_id
119
+ msg = "Video needs same show as the episode; show_id is #{show_id.inspect} while episode.show_id is #{episode.show_id.inspect}"
120
+ if errors.respond_to?(:add_to_base)
121
+ errors.add_to_base(msg)
122
+ else
123
+ errors[:base] << msg
124
+ end
125
+ end
126
+ end
127
+ end
128
+
129
+ class VideoFavorite < ActiveRecord::Base
130
+ belongs_to :video
131
+ belongs_to :user
132
+
133
+ validates_presence_of :user_id, :video_id
134
+ validates_uniqueness_of :video_id, :scope => :user_id
135
+ end
136
+
137
+ class VideoTakedownEvent < ActiveRecord::Base
138
+ belongs_to :video
139
+
140
+ validates_presence_of :video_id
141
+ validates_uniqueness_of :video_id
142
+ end
143
+
144
+ SampleModels.configure(Appointment) do |appointment|
145
+ appointment.before_save do |a|
146
+ a.user_id = a.calendar.user_id
147
+ end
148
+ end
149
+
150
+ SampleModels.configure(BlogPost) do |bp|
151
+ bp.instance_attribute.default 'I am an instance attribute'
152
+ bp.published_at.force_unique
153
+
154
+ bp.funny_sample :title => 'Funny haha', :average_rating => 3.0
155
+ end
156
+
157
+ SampleModels.configure(Category) do |category|
158
+ category.parent.default nil
159
+ end
160
+
161
+ SampleModels.configure(Subscription) do |sub|
162
+ sub.subscribable.default_class BlogPost
163
+ end
164
+
165
+ SampleModels.configure(Topic) do |topic|
166
+ topic.parent.default nil
167
+
168
+ topic.before_save do |t, sample_attrs|
169
+ if t.parent.nil?
170
+ root = Topic.find_by_root(true)
171
+ unless root
172
+ # presumably if you're being this strict about self-parenting, you'd
173
+ # hook right into the table's primary key sequence. The logic below
174
+ # should work well enough for the test cases though.
175
+ root = Topic.new(:name => 'Root', :root => true)
176
+ max_h = Topic.connection.select_one("select max(id) from topics")
177
+ if max_h['max']
178
+ root.parent_id = max_h['max'].to_i + 1
179
+ else
180
+ root.parent_id = 1
181
+ end
182
+ root.save!
183
+ end
184
+ t.parent = root
185
+ end
186
+ end
187
+ end
188
+
189
+ SampleModels.configure(User) do |user|
190
+ user.external_user_id.default nil
191
+ end
192
+
193
+ SampleModels.configure(Video) do |video|
194
+ video.before_save do |v, sample_attrs|
195
+ if v.episode && v.episode.show != v.show
196
+ if sample_attrs[:show]
197
+ v.episode.show = v.show
198
+ else
199
+ v.show = v.episode.show
200
+ end
201
+ end
202
+ end
203
+ video.view_count.default 0
204
+ end
205
+
@@ -0,0 +1,108 @@
1
+ require 'active_record'
2
+ require 'active_record/base'
3
+ require 'logger'
4
+
5
+ ActiveRecord::Base.logger = Logger.new(
6
+ File.dirname(__FILE__) + '/../../log/test.log'
7
+ )
8
+ ActiveRecord::Base.establish_connection(
9
+ :adapter => 'sqlite3', :database => 'test/db/test.sqlite3'
10
+ )
11
+
12
+ silence_stream(STDOUT) do
13
+ ActiveRecord::Schema.define do
14
+ create_table 'appointments', :force => true do |appointment|
15
+ appointment.datetime 'start_time', 'end_time'
16
+ appointment.integer 'user_id', 'calendar_id', 'category_id'
17
+ end
18
+
19
+ create_table 'blog_posts', :force => true do |blog_post|
20
+ blog_post.datetime 'published_at'
21
+ blog_post.integer 'merged_into_id', 'user_id'
22
+ blog_post.string 'title'
23
+ blog_post.float 'average_rating'
24
+ blog_post.text 'body'
25
+ blog_post.timestamps
26
+ end
27
+
28
+ create_table "blog_post_tags", :force => true do |t|
29
+ t.integer "blog_post_id"
30
+ t.integer "tag_id"
31
+ end
32
+
33
+ create_table "bookmarks", :force => true do |t|
34
+ t.integer "bookmarkable_id"
35
+ t.string "bookmarkable_type"
36
+ end
37
+
38
+ create_table 'calendars', :force => true do |calendar|
39
+ calendar.integer 'user_id'
40
+ end
41
+
42
+ create_table 'categories', :force => true do |category|
43
+ category.string 'name'
44
+ category.integer 'parent_id'
45
+ end
46
+
47
+ create_table 'comments', :force => true do |comment|
48
+ comment.boolean 'flagged_as_spam', :default => false
49
+ end
50
+
51
+ create_table 'episodes', :force => true do |episode|
52
+ episode.integer 'show_id'
53
+ episode.string 'name'
54
+ episode.date 'original_air_date'
55
+ end
56
+
57
+ create_table 'external_users', :force => true do |external_user|
58
+ end
59
+
60
+ create_table 'networks', :force => true do |network|
61
+ network.string 'name'
62
+ end
63
+
64
+ create_table 'shows', :force => true do |show|
65
+ show.integer 'network_id', 'subscription_price'
66
+ show.string 'name'
67
+ end
68
+
69
+ create_table 'subscriptions', :force => true do |subscription|
70
+ subscription.integer 'subscribable_id', 'user_id'
71
+ subscription.string 'subscribable_type'
72
+ end
73
+
74
+ create_table "tags", :force => true do |t|
75
+ t.string "tag"
76
+ end
77
+
78
+ create_table 'topics', :force => true do |t|
79
+ t.string 'name'
80
+ t.integer 'parent_id'
81
+ t.boolean 'root', :default => false
82
+ end
83
+
84
+ create_table 'users', :force => true do |user|
85
+ user.integer 'favorite_blog_post_id', 'external_user_id'
86
+ user.string 'email', 'gender', 'homepage', 'login', 'password'
87
+ end
88
+
89
+ create_table 'user2s', :force => true do |user2|
90
+ user2.string 'login'
91
+ end
92
+
93
+ create_table 'user_with_passwords', :force => true do |user|
94
+ end
95
+
96
+ create_table 'videos', :force => true do |video|
97
+ video.integer 'episode_id', 'show_id', 'network_id', 'view_count'
98
+ end
99
+
100
+ create_table 'video_favorites', :force => true do |video_favorite|
101
+ video_favorite.integer 'user_id', 'video_id'
102
+ end
103
+
104
+ create_table 'video_takedown_events', :force => true do |vte|
105
+ vte.integer 'video_id'
106
+ end
107
+ end
108
+ end