sample_models 1.2.6 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,126 +1,173 @@
1
+ require 'delegate'
2
+
1
3
  module SampleModels
2
4
  class Creation
3
- def initialize(sampler, attrs)
5
+ def initialize(sampler, *args)
4
6
  @sampler = sampler
5
- @orig_attrs = SampleModels.hash_with_indifferent_access_class.new attrs
6
- @attrs = Attributes.new(@sampler, attrs)
7
+ @specified_attrs = SpecifiedAttributes.new(sampler, *args).result
7
8
  end
8
9
 
9
- def model
10
- SampleModels.models[@sampler.model_class]
10
+ def belongs_to_assoc(column_name)
11
+ model.belongs_to_associations.detect { |assoc|
12
+ assoc.foreign_key == column_name
13
+ }
11
14
  end
12
15
 
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
16
+ def deferred_belongs_to_assocs
17
+ @deferred_belongs_to_assocs ||= begin
18
+ model.belongs_to_associations.select { |a|
19
+ @instance.send(a.foreign_key).nil? &&
20
+ !@specified_attrs.member?(a.foreign_key) &&
21
+ !@specified_attrs.member?(a.name) &&
22
+ !@sampler.defaults.member?(a.foreign_key) &&
23
+ !@sampler.defaults.member?(a.name)
24
+ }
20
25
  end
21
26
  end
22
27
 
28
+ def model
29
+ @sampler.model
30
+ end
31
+
23
32
  def run
24
- @instance = @sampler.model_class.new @attrs.to_hash
33
+ attrs = @specified_attrs.clone
34
+ @sampler.defaults.each do |attr, val|
35
+ attrs[attr] = val unless attrs.member?(attr)
36
+ end
37
+ columns_to_fill = model.columns.clone
38
+ model.validated_attr_accessors.each do |attr|
39
+ columns_to_fill << VirtualColumn.new(attr)
40
+ end
41
+ columns_to_fill.each do |column|
42
+ unless attrs.member?(column.name) ||
43
+ specified_association_value?(column.name) ||
44
+ ((assoc = belongs_to_assoc(column.name)) && attrs.member?(assoc.name)) || timestamp?(column.name)
45
+ sequence = @sampler.first_pass_attribute_sequence(column)
46
+ attrs[column.name] = sequence.value
47
+ end
48
+ end
49
+ @instance = model.new(attrs)
25
50
  save!
26
- update_associations
51
+ update_with_deferred_associations!
27
52
  @instance
28
53
  end
29
54
 
30
55
  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
56
+ if @sampler.before_save
57
+ if @sampler.before_save.arity == 1
58
+ @sampler.before_save.call(@instance)
59
+ else
60
+ @sampler.before_save.call(@instance, @specified_attrs)
46
61
  end
47
62
  end
48
- save! if needs_another_save
63
+ @instance.save!
49
64
  end
50
65
 
51
- class Attributes < SampleModels.hash_with_indifferent_access_class
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
66
+ def specified_association_value?(column_name)
67
+ @specified_attrs.any? { |attr, val|
68
+ if assoc = model.belongs_to_association(attr)
69
+ assoc.foreign_key == column_name
62
70
  end
63
- end
64
-
65
- def model
66
- SampleModels.models[@sampler.model_class]
67
- end
71
+ }
72
+ end
68
73
 
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
- foreign_key = if assoc.respond_to?(:foreign_key)
76
- assoc.foreign_key
77
- else
78
- assoc.primary_key_name
79
- end
80
- foreign_key == column.name
81
- }
82
- self[column.name] = 1
74
+ def timestamp?(column_name)
75
+ %w(created_at updated_at created_on updated_on).include?(column_name)
76
+ end
77
+
78
+ def update_with_deferred_associations!
79
+ unless deferred_belongs_to_assocs.empty?
80
+ deferred_belongs_to_assocs.each do |a|
81
+ if a.polymorphic?
82
+ klass = @sampler.polymorphic_default_classes[a.name]
83
+ klass ||= SampleModels.samplers.values.map(&:model).detect { |m|
84
+ m != @sampler.model
85
+ }
86
+ @instance.send("#{a.name}=", klass.sample)
87
+ else
88
+ column = model.columns.detect { |c| c.name == a.foreign_key }
89
+ @instance.send(
90
+ "#{a.foreign_key}=",
91
+ @sampler.second_pass_attribute_sequence(column).next
92
+ )
83
93
  end
84
- when :datetime
85
- self[column.name] = Time.now.utc
86
- when :float
87
- self[column.name] = 1.0
88
94
  end
95
+ save!
89
96
  end
97
+ end
90
98
 
91
- def fill_based_on_configured_defaults
92
- @sampler.configured_default_attrs.each do |attr, val|
93
- self[attr] = val unless has_key?(attr)
99
+ class SpecifiedAttributes
100
+ attr_reader :result
101
+
102
+ def initialize(sampler, *args)
103
+ @sampler = sampler
104
+ @result = if args.first.is_a?(Symbol)
105
+ sample_name = args.shift
106
+ @sampler.named_samples[sample_name].clone
107
+ else
108
+ {}
109
+ end
110
+ @result.merge!(args.pop) if args.last.is_a?(Hash)
111
+ args.each do |associated_value|
112
+ assign_associated_record_from_args(associated_value)
113
+ end
114
+ model.belongs_to_associations.each do |assoc|
115
+ build_belongs_to_record_from_shortcut_args(assoc)
94
116
  end
117
+ model.has_many_associations.each do |assoc|
118
+ build_has_many_record_from_shortcut_args(assoc)
119
+ end
120
+ @result = HashWithIndifferentAccess.new(@result)
95
121
  end
96
-
97
- def fill_based_on_string_column_type(column)
98
- unless model.belongs_to_associations.any? { |assoc|
99
- assoc.options[:polymorphic] &&
100
- assoc.options[:foreign_type] = column.name
122
+
123
+ def assign_associated_record_from_args(associated_value)
124
+ assocs = model.associations.select { |a|
125
+ begin
126
+ a.klass == associated_value.class
127
+ rescue NameError
128
+ false
129
+ end
101
130
  }
102
- self[column.name] = "#{column.name}"
131
+ if assocs.size == 1
132
+ @result[assocs.first.name] = associated_value
133
+ else
134
+ raise "Not sure what to do with associated value #{associated_value.inspect}"
103
135
  end
104
136
  end
105
-
106
- def fill_based_on_validations
107
- model.validation_collections.each do |field, validation_collection|
108
- assoc_key = nil
109
- if assoc = model.belongs_to_associations.detect { |a|
110
- foreign_key = if a.respond_to?(:foreign_key)
111
- a.foreign_key
112
- else
113
- a.primary_key_name
114
- end
115
- foreign_key == field.to_s
116
- }
117
- assoc_key = assoc.name
118
- end
119
- unless has_key?(field) || (assoc_key && has_key?(assoc_key))
120
- self[field] = validation_collection.satisfying_value
137
+
138
+ def build_belongs_to_record_from_shortcut_args(assoc)
139
+ if value = @result[assoc.name]
140
+ if value.is_a?(Hash)
141
+ @result[assoc.name] = assoc.klass.sample(value)
142
+ elsif value.is_a?(Array)
143
+ @result[assoc.name] = assoc.klass.sample(*value)
121
144
  end
122
145
  end
123
146
  end
147
+
148
+ def build_has_many_record_from_shortcut_args(assoc)
149
+ if values = @result[assoc.name]
150
+ @result[assoc.name] = values.map { |value|
151
+ value.is_a?(Hash) ? assoc.klass.sample(value) : value
152
+ }
153
+ end
154
+ end
155
+
156
+ def model
157
+ @sampler.model
158
+ end
159
+ end
160
+
161
+ class VirtualColumn
162
+ attr_reader :name
163
+
164
+ def initialize(name)
165
+ @name = name
166
+ end
167
+
168
+ def type
169
+ :string
170
+ end
124
171
  end
125
172
  end
126
173
  end
@@ -0,0 +1,48 @@
1
+ module SampleModels
2
+ class Initializer
3
+ def run
4
+ ActiveRecord::Base.send(:include, SampleModels)
5
+ intercept_validation_definitions
6
+ end
7
+
8
+ def intercept_validation_definition(validation, recipient)
9
+ method_name = "#{validation}_with_sample_models".to_sym
10
+ recipient.send(:define_method, method_name) do |*args|
11
+ send "#{validation}_without_sample_models".to_sym, *args
12
+ SampleModels.models[self].record_validation(validation, *args)
13
+ end
14
+ recipient.alias_method_chain validation, :sample_models
15
+ end
16
+
17
+ def intercept_validation_definitions
18
+ validations_to_intercept = [
19
+ :validates_email_format_of, :validates_inclusion_of,
20
+ :validates_presence_of, :validates_uniqueness_of
21
+ ]
22
+ optional_interceptions = [:validates_email_format_of]
23
+ validations_to_intercept.each do |validation|
24
+ recipient = validation_recipients.detect { |vr|
25
+ vr.method_defined?(validation)
26
+ }
27
+ if recipient
28
+ intercept_validation_definition(validation, recipient)
29
+ else
30
+ unless optional_interceptions.include?(validation)
31
+ raise "Can't find who defines the validation method #{validation}"
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ def validation_recipients
38
+ validation_recipients = [ActiveRecord::Validations::ClassMethods]
39
+ if Object.const_defined?('ActiveModel')
40
+ validation_recipients << ActiveModel::Validations::HelperMethods
41
+ end
42
+ if Object.const_defined?('ValidatesEmailFormatOf')
43
+ validation_recipients << ValidatesEmailFormatOf::Validations
44
+ end
45
+ validation_recipients
46
+ end
47
+ end
48
+ end
@@ -2,48 +2,31 @@ require 'delegate'
2
2
 
3
3
  module SampleModels
4
4
  class Model < Delegator
5
- attr_reader :validation_collections
5
+ attr_reader :ar_class
6
6
 
7
- def initialize(model_class)
8
- @model_class = model_class
9
- @validation_collections = Hash.new { |h, field|
10
- h[field] = ValidationCollection.new(self, field)
11
- }
7
+ def initialize(ar_class)
8
+ @ar_class = ar_class
9
+ @validations = Hash.new { |h,k| h[k] = [] }
12
10
  end
13
11
 
14
12
  def __getobj__
15
- @model_class
13
+ @ar_class
16
14
  end
17
15
 
18
- def associations(klass)
19
- @model_class.reflect_on_all_associations.select { |a|
20
- begin
21
- a.klass == klass
22
- rescue NameError
23
- false
24
- end
25
- }
16
+ def associations
17
+ @ar_class.reflect_on_all_associations.map { |a| Association.new(a) }
26
18
  end
27
19
 
28
- def belongs_to_associations
29
- @model_class.reflect_on_all_associations.select { |assoc|
30
- assoc.macro == :belongs_to
31
- }
20
+ def belongs_to_association(name)
21
+ belongs_to_associations.detect { |a| a.name.to_s == name.to_s }
32
22
  end
33
23
 
34
- def construct_finder_sql(*args)
35
- if @model_class.method(:scoped).arity == -1 &&
36
- @model_class.scoped(nil).respond_to?(:apply_finder_options)
37
- @model_class.scoped(nil).apply_finder_options(*args).arel.to_sql
38
- else
39
- @model_class.send(:construct_finder_sql, *args)
40
- end
24
+ def belongs_to_associations
25
+ associations.select { |a| a.belongs_to? }
41
26
  end
42
27
 
43
28
  def has_many_associations
44
- @model_class.reflect_on_all_associations.select { |assoc|
45
- assoc.macro == :has_many
46
- }
29
+ associations.select { |a| a.has_many? }
47
30
  end
48
31
 
49
32
  def record_validation(*args)
@@ -51,145 +34,71 @@ module SampleModels
51
34
  config = args.extract_options!
52
35
  fields = args
53
36
  fields.each do |field|
54
- @validation_collections[field].add(type, config)
37
+ @validations[field.to_s] << Validation.new(type, config)
55
38
  end
56
39
  end
57
40
 
58
- def validates_presence_of?(attr)
59
- @validation_collections[attr].includes_presence?
41
+ def unique?(field, value)
42
+ @ar_class.count(:conditions => {field => value}) == 0
60
43
  end
61
44
 
62
- def unique?(field, value)
63
- !@model_class.first(:conditions => {field => value})
45
+ def validated_attr_accessors
46
+ @validations.keys.select { |key|
47
+ columns.none? { |column| column.name.to_s == key.to_s }
48
+ }
64
49
  end
65
50
 
66
- class ValidationCollection
67
- def initialize(model, field)
68
- @model, @field = model, field
69
- @value_streams = []
70
- end
71
-
72
- def add(validation_method, config)
73
- stream_class_name = validation_method.to_s.camelize + 'ValueStream'
74
- if ValidationCollection.const_defined?(stream_class_name)
75
- stream_class = ValidationCollection.const_get(stream_class_name)
76
- if stream_class == ValidatesUniquenessOfValueStream
77
- @validates_uniqueness_config = config
78
- else
79
- input = @value_streams.last
80
- @value_streams << stream_class.new(@model, @field, config, input)
81
- end
82
- end
51
+ def validations(name)
52
+ @validations[name.to_s]
53
+ end
54
+
55
+ class Association < Delegator
56
+ def initialize(assoc)
57
+ @assoc = assoc
83
58
  end
84
59
 
85
- def includes_presence?
86
- @value_streams.any? { |vs| vs.is_a?(ValidatesPresenceOfValueStream) }
60
+ def __getobj__
61
+ @assoc
87
62
  end
88
63
 
89
- def includes_uniqueness?
90
- @value_streams.any? { |vs| vs.is_a?(ValidatesUniquenessOfValueStream) }
64
+ def belongs_to?
65
+ @assoc.macro == :belongs_to
91
66
  end
92
67
 
93
- def satisfying_value
94
- if @validates_uniqueness_config
95
- input = @value_streams.last
96
- @value_streams << ValidatesUniquenessOfValueStream.new(
97
- @model, @field, @validates_uniqueness_config, input
98
- )
99
- @validates_uniqueness_config = nil
68
+ def foreign_key
69
+ if @assoc.respond_to?(:foreign_key)
70
+ @assoc.foreign_key
71
+ else
72
+ @assoc.primary_key_name
100
73
  end
101
- @value_streams.last.satisfying_value if @value_streams.last
102
74
  end
103
75
 
104
- class ValueStream
105
- attr_reader :input
106
-
107
- def initialize(model, field, config, input)
108
- @model, @field, @config, @input = model, field, config, input
109
- @sequence_number = 0
110
- end
111
-
112
- def column
113
- @model.columns.detect { |c| c.name == @field.to_s }
114
- end
115
-
116
- def increment
117
- @sequence_number += 1
118
- input.increment if input
119
- end
76
+ def has_many?
77
+ @assoc.macro == :has_many
120
78
  end
121
79
 
122
- class ValidatesEmailFormatOfValueStream < ValueStream
123
- def satisfying_value
124
- "john.doe#{@sequence_number}@example.com"
125
- end
80
+ def polymorphic?
81
+ @assoc.options[:polymorphic]
126
82
  end
83
+ end
84
+
85
+ class Validation
86
+ attr_reader :config, :type
127
87
 
128
- class ValidatesInclusionOfValueStream < ValueStream
129
- def satisfying_value
130
- @config[:in].first
131
- end
88
+ def initialize(type, config = {})
89
+ @type, @config = type, config
132
90
  end
133
91
 
134
- class ValidatesPresenceOfValueStream < ValueStream
135
- def association
136
- @model.belongs_to_associations.detect { |a|
137
- a.association_foreign_key.to_sym == @field.to_sym
138
- }
139
- end
140
-
141
- def increment
142
- if association
143
- association.klass.create_sample
144
- else
145
- super
146
- end
147
- end
148
-
149
- def satisfying_associated_value
150
- value = association.klass.last || association.klass.sample
151
- value = value.id if value
152
- value
153
- end
92
+ def email_format?
93
+ @type == :validates_email_format_of
94
+ end
154
95
 
155
- def satisfying_value
156
- prev_value = input.satisfying_value if input
157
- if association
158
- satisfying_associated_value
159
- else
160
- if prev_value.present?
161
- prev_value
162
- elsif column && column.type == :date
163
- Date.today + @sequence_number
164
- elsif column && column.type == :datetime
165
- Time.utc(1970, 1, 1) + @sequence_number.days
166
- elsif column && column.type == :integer
167
- @sequence_number
168
- else
169
- "#{@field} #{@sequence_number}"
170
- end
171
- end
172
- end
96
+ def inclusion?
97
+ @type == :validates_inclusion_of
173
98
  end
174
99
 
175
- class ValidatesUniquenessOfValueStream < ValueStream
176
- def satisfying_value
177
- value = input.satisfying_value if input
178
- unless @config[:allow_nil] && value.nil?
179
- unless @config[:allow_blank] && value.blank?
180
- if !@model.unique?(@field, value)
181
- my_input = input || ValidatesPresenceOfValueStream.new(
182
- @model, @field, nil, @input
183
- )
184
- until @model.unique?(@field, value)
185
- my_input.increment
186
- value = my_input.satisfying_value
187
- end
188
- end
189
- end
190
- end
191
- value
192
- end
100
+ def presence?
101
+ @type == :validates_presence_of
193
102
  end
194
103
  end
195
104
  end