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,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