freeform 1.0.11 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- YjA0MmU0YThkYWRkNjhmZjc4N2Y2NmUyOTE3MWE4MWZjZTVkZDc0NA==
4
+ ZmM3NmYwN2Q0MzBjODk2MTRlNzA3NjU0YWRjMWI1YTY3MjMxNjI1Yw==
5
5
  data.tar.gz: !binary |-
6
- ZTRmMGY1Yjc4YzkxNDUwNWEwNTZlNjVmYWNlODdhM2M2M2I0NTA3MQ==
6
+ YjVmOTEzMDZmNTNiNmQ2MGIwZTllMGRjMmI5NWU4ODFiYWFhMzhjMw==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- NTM4ZjNlYmM5MGYyNjQ0OGYyN2VhZTIxNmY2Y2Y0Y2IyNzY0ODI2MTQ2NTQy
10
- MWIyZjMxNjRmODc4ZGE4NTIyYTBhOTM2NGNkNTFmNmQ5ZWM5OGFlOTNiMTI5
11
- MTg0YTRkYTI1YzhkN2UzOTY2OTE0M2RkZTAwMzMyZjg0ZmFhYWM=
9
+ YjU2ZDBlNTA2ZjNkZDdjZjgyMzY4NTg0NWRjZjM0ZTI0NjUwNWI5ZjZmZWMy
10
+ ZjUxNWFhZWQ2ZjZmODMyYzdmYjI4ZjJkNGIzNmEyYjhiNzNkMzMyY2E3NDMx
11
+ ZDgyOWI2M2ZlNjZiMTQxNDBkYzdjZWVmOTg0NzE3YzlhMDVlMWY=
12
12
  data.tar.gz: !binary |-
13
- NTgzODg1NTFjM2YyNzVkNmRlMjYzZjAzODhlY2VmYmQ3Y2FiZDJmNGRhZmJk
14
- NzEyMWIwMDFjNzUxZjMyMDY5ZjMzZDkwY2MxZDE0MTQyYTk2OTBhOTU4ZDkx
15
- N2E2ZTkzZWZhNWJhNDliYmU2YzhhMzYyMDdmMDA2MTFjMzUyMWM=
13
+ NTZhNjZjYmEzZWQxOWI4YTZmMjlhMjg5MTQ4OGJiYWY1MzQyZmE3NDVlYjA0
14
+ M2I4MGM2NDhkZGRkODdiYjZjMTU0YWRhMzRhNGQ5OTEyNDU4OTZkNTI0ZDJm
15
+ OTNiMTc0ZmU1YzZhMTE2ODBiZTIxOTQ4NTk2NWY2ZTA5YjA2NGQ=
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- freeform (1.0.9)
4
+ freeform (2.0.0rc1)
5
5
  activemodel
6
6
  formtastic
7
7
  rails (>= 3.2.0)
@@ -46,7 +46,7 @@ GEM
46
46
  rack-test (>= 0.5.4)
47
47
  selenium-webdriver (~> 2.0)
48
48
  xpath (~> 0.1.4)
49
- childprocess (0.5.2)
49
+ childprocess (0.5.3)
50
50
  ffi (~> 1.0, >= 1.0.11)
51
51
  diff-lcs (1.2.5)
52
52
  erubis (2.7.0)
@@ -110,7 +110,7 @@ GEM
110
110
  rspec-core (~> 2.14.0)
111
111
  rspec-expectations (~> 2.14.0)
112
112
  rspec-mocks (~> 2.14.0)
113
- rubyzip (1.1.2)
113
+ rubyzip (1.1.3)
114
114
  selenium-webdriver (2.41.0)
115
115
  childprocess (>= 0.5.0)
116
116
  multi_json (~> 1.0)
data/README.md CHANGED
@@ -10,11 +10,13 @@ FreeForm is designed primarily with Rails in mind, but it should work on any Rub
10
10
 
11
11
  **FreeForm will not work with Ryan Bate's nested_form gem, but provides its own identical behavior**
12
12
 
13
+ Please use/migrate to version 2.x of this gem. Version 1.x will no longer be supported, and version 2.x provides a better DSL, additional features, and cleaner source code.
14
+
13
15
  ## Installation
14
16
 
15
17
  Add this line to your application's Gemfile:
16
18
 
17
- gem 'freeform', '>= 1.0.0'
19
+ gem 'freeform', '>= 2.0.0'
18
20
 
19
21
  And then execute:
20
22
 
@@ -198,19 +200,18 @@ class UserForm < FreeForm::Form
198
200
  property :username, :on => :user
199
201
  property :email, :on => :user
200
202
 
201
- has_many :phone_numbers, :class => PhoneNumberForm, :default_initializer => :phone_initializer
203
+ has_many :phone_numbers do
204
+ form_models :phone
205
+
206
+ property :area_code, :on => :phone
207
+ property :number, :on => :phone
208
+ end
202
209
 
203
210
  def phone_initializer
204
211
  { :phone => user.phone_numbers.build }
205
212
  end
206
213
  end
207
214
 
208
- class PhoneNumberForm < FreeForm::Form
209
- form_models :phone
210
-
211
- property :area_code, :on => :phone
212
- property :number, :on => :phone
213
- end
214
215
  ```
215
216
  **Note:** The method `nested_form` is also aliased as `has_many` and `has_one`, if you prefer the expressiveness of that syntax. The functionality is the same in any case.
216
217
 
@@ -235,18 +236,16 @@ class UserForm < FreeForm::Form
235
236
  property :username, :on => :user
236
237
  property :email, :on => :user
237
238
 
238
- has_many :phone_numbers, :class => PhoneNumberForm, :default_initializer => :phone_initializer
239
+ has_many :phone_numbers do
240
+ form_models :phone
239
241
 
240
- def phone_initializer
241
- { :phone => user.phone_numbers.build }
242
+ property :area_code, :on => :phone
243
+ property :number, :on => :phone
242
244
  end
243
- end
244
-
245
- class PhoneNumberForm < FreeForm::Form
246
- form_models :phone
247
245
 
248
- property :area_code, :on => :phone
249
- property :number, :on => :phone
246
+ def phone_initializer
247
+ { :phone => user.phone_numbers.build }
248
+ end
250
249
  end
251
250
 
252
251
  form = UserForm.new(:user => current_user)
@@ -0,0 +1,34 @@
1
+ module FreeForm
2
+ class DateParamsFilter
3
+ def initialize(*args)
4
+ end
5
+
6
+ def call(params)
7
+ date_attributes = {}
8
+
9
+ params.each do |attribute, value|
10
+ if value.is_a?(Hash)
11
+ call(value) # TODO: #validate should only handle local form params.
12
+ elsif matches = attribute.match(/^(\w+)\(.i\)$/)
13
+ date_attribute = matches[1]
14
+ date_attributes[date_attribute] = params_to_date(
15
+ params.delete("#{date_attribute}(1i)"),
16
+ params.delete("#{date_attribute}(2i)"),
17
+ params.delete("#{date_attribute}(3i)")
18
+ )
19
+ end
20
+ end
21
+ params.merge!(date_attributes)
22
+ end
23
+
24
+ private
25
+ def params_to_date(year, month, day)
26
+ day ||= 1 # FIXME: is that really what we want? test.
27
+ begin # TODO: test fails.
28
+ return Date.new(year.to_i, month.to_i, day.to_i)
29
+ rescue ArgumentError => e
30
+ return nil
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,56 @@
1
+ require 'freeform/form/date_params_filter'
2
+
3
+ module FreeForm
4
+ module Model
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ end
8
+
9
+ module ClassMethods
10
+ include Forwardable
11
+
12
+ def models
13
+ @models ||= []
14
+ end
15
+
16
+ def child_models
17
+ @child_models ||= []
18
+ end
19
+
20
+ def declared_models(*names)
21
+ names.each do |name|
22
+ declared_model(name)
23
+ end
24
+ end
25
+ alias_method :form_models, :declared_models
26
+
27
+ def declared_model(name, opts={})
28
+ attr_accessor name
29
+ @models = (@models || []) << name
30
+ end
31
+ alias_method :form_model, :declared_model
32
+
33
+ def child_model(name, opts={}, &block)
34
+ declared_model(name)
35
+ @child_models = (@child_models || []) << name
36
+
37
+ # Defines an initializer method, such as 'initialize_address'
38
+ # that can be called on form initialization to build the model
39
+ define_method("initialize_#{name}") do
40
+ instance_variable_set("@#{name}", instance_eval(&block))
41
+ end
42
+ end
43
+ end
44
+
45
+ def models
46
+ Array.new.tap do |a|
47
+ self.class.models.each do |form_model|
48
+ a << send(form_model)
49
+ end
50
+ a.flatten!
51
+ end
52
+ end
53
+
54
+ private
55
+ end
56
+ end
@@ -9,64 +9,88 @@ module FreeForm
9
9
  #------------------------------------------------------------------------
10
10
  attr_accessor :nested_forms
11
11
 
12
- def nested_form(attribute, options={})
13
- nested_form_class = options[:class]
14
- @nested_forms ||= {}
15
- @nested_forms.merge!({:"#{attribute}" => nested_form_class})
12
+ def nested_form(attribute, options={}, &block)
13
+ # Initialize the instance variables on the parent class
14
+ initialize_instance_variables(attribute)
16
15
 
17
- # Define an attr_accessor for the parent class to hold this attribute
18
- declared_model(attribute)
16
+ # Define new nested class
17
+ klass = define_nested_class(attribute, options, &block)
19
18
 
20
- # Defined other methods
21
- define_nested_model_methods(attribute, nested_form_class, options)
22
-
23
- # Setup Handling for nested attributes
24
- define_nested_attribute_methods(attribute, nested_form_class)
19
+ # Define methods for working with nested models
20
+ define_nested_model_methods(attribute, klass)
25
21
  end
26
22
  alias_method :has_many, :nested_form
27
23
  alias_method :has_one, :nested_form
28
24
 
29
- protected
30
- # Defining Helper Methods For Models
31
- #------------------------------------------------------------------------
32
- def define_nested_model_methods(attribute, form_class, options={})
33
- singularized_attribute = attribute.to_s.singularize.to_s
25
+ protected
26
+ def initialize_instance_variables(attribute)
27
+ declared_model attribute
34
28
 
35
- # Example: form.addresses will return all nested address forms
36
29
  define_method(:"#{attribute}") do
37
30
  value = instance_variable_get("@nested_#{attribute}")
38
31
  value ||= []
39
32
  instance_variable_set("@nested_#{attribute}", value)
40
33
  end
34
+ end
41
35
 
42
- # Example: form.build_addresses (optional custom initializer)
43
- define_method(:"build_#{attribute}") do |initializer=nil|
44
- # Builder object
45
- parent_object = self
36
+ def register_nested_form(klass)
37
+ @nested_forms ||= []
38
+ @nested_forms << klass
39
+ end
46
40
 
47
- # Get correct class
48
- form_class = self.class.nested_forms[:"#{attribute}"]
41
+ def parent_class
42
+ self
43
+ end
49
44
 
50
- # Default Initializer
51
- if options[:default_initializer]
52
- initializer ||= instance_eval(&options[:default_initializer])
53
- end
54
- initializer ||= {}
45
+ def attribute_to_form_class(attribute)
46
+ singularized_attribute(attribute).camelize
47
+ end
55
48
 
56
- # Build new model
57
- form_model = form_class.new(initializer)
58
- value = instance_variable_get("@nested_#{attribute}")
59
- value ||= []
60
- value << form_model
61
- instance_variable_set("@nested_#{attribute}", value)
49
+ def define_nested_class(attribute, options={}, &block)
50
+ # Define new nested class
51
+ klass = Class.new(FreeForm::Form) do
52
+ end
62
53
 
63
- form_model
54
+ # Add whatever passed in methods, properties, etc. to the class
55
+ if block_given?
56
+ klass.class_eval(&block)
64
57
  end
65
- alias_method :"build_#{singularized_attribute}", :"build_#{attribute}"
58
+
59
+ # Set the class name
60
+ form_class = attribute_to_form_class(attribute)
61
+ parent_class.const_set(form_class, klass)
62
+
63
+ # Register the class as being nested under the current class
64
+ register_nested_form(klass)
65
+ klass
66
66
  end
67
67
 
68
- # Defining Helper Methods For Nested Attributes
69
- #------------------------------------------------------------------------
68
+ def define_nested_model_methods(attribute, form_class, options={})
69
+ singular = singularized_attribute(attribute)
70
+
71
+ # Example: form.build_addresses (optional custom initializer)
72
+ define_method(:"build_#{attribute}") do |initializer=nil|
73
+ initializer ||= self.send("#{singular}_initializer")
74
+
75
+ form_class.new(initializer).tap do |obj|
76
+ # Add new object to parent model collection (e.g. the new address is in parent.addresses)
77
+ current_models = self.send(attribute)
78
+ current_models ||= []
79
+ current_models << obj
80
+ self.send("#{attribute}=", current_models)
81
+ end
82
+ end
83
+ alias_method :"build_#{singular}", :"build_#{attribute}"
84
+
85
+ # Define methods for working with nested models
86
+ define_nested_attribute_methods(attribute, form_class)
87
+ end
88
+
89
+ def singularized_attribute(attribute)
90
+ attribute.to_s.singularize
91
+ end
92
+
93
+ #TODO: Clean up this method!
70
94
  def define_nested_attribute_methods(attribute, nested_form_class)
71
95
  # Equivalent of "address_attributes=" for "address" attribute
72
96
  define_method(:"#{attribute}_attributes=") do |params|
@@ -79,12 +103,13 @@ module FreeForm
79
103
  model.fill(attributes)
80
104
  end
81
105
  end
82
- end
106
+ end
83
107
  end
84
108
 
85
109
  # Instance Methods
86
110
  #--------------------------------------------------------------------------
87
111
  protected
112
+ #TODO: Clean up this method!
88
113
  def build_models_from_param_count(attribute, params)
89
114
  # Get the difference between sets of params passed and current form objects
90
115
  num_param_models = params.length
@@ -1,3 +1,5 @@
1
+ require 'freeform/form/date_params_filter'
2
+
1
3
  module FreeForm
2
4
  module Property
3
5
  def self.included(base)
@@ -7,43 +9,6 @@ module FreeForm
7
9
  module ClassMethods
8
10
  include Forwardable
9
11
 
10
- # Models
11
- #------------------------------------------------------------------------
12
- def models
13
- @models ||= []
14
- end
15
-
16
- def child_models
17
- @child_models ||= []
18
- end
19
-
20
- def declared_model(name, opts={})
21
- @models ||= []
22
- @models << name
23
- attr_accessor name
24
- end
25
- alias_method :form_model, :declared_model
26
-
27
- def declared_models(*names)
28
- names.each do |name|
29
- declared_model(name)
30
- end
31
- end
32
- alias_method :form_models, :declared_models
33
-
34
- def child_model(name, opts={}, &block)
35
- @models ||= []
36
- @models << name
37
- @child_models ||= []
38
- @child_models << name
39
- attr_accessor name
40
- define_method("initialize_#{name}") do
41
- instance_variable_set("@#{name}", instance_eval(&block))
42
- end
43
- end
44
-
45
- # Properties
46
- #------------------------------------------------------------------------
47
12
  def property_mappings
48
13
  # Take the form of {:property => {:model => model, :field => field, :ignore_blank => false}}
49
14
  @property_mappings ||= Hash.new
@@ -89,57 +54,48 @@ module FreeForm
89
54
  end
90
55
  end
91
56
 
92
- attr_accessor :parent_form
93
-
94
- class DateParamsFilter
95
- def call(params)
96
- date_attributes = {}
97
-
98
- params.each do |attribute, value|
99
- if value.is_a?(Hash)
100
- call(value) # TODO: #validate should only handle local form params.
101
- elsif matches = attribute.match(/^(\w+)\(.i\)$/)
102
- date_attribute = matches[1]
103
- date_attributes[date_attribute] = params_to_date(
104
- params.delete("#{date_attribute}(1i)"),
105
- params.delete("#{date_attribute}(2i)"),
106
- params.delete("#{date_attribute}(3i)")
107
- )
108
- end
109
- end
110
- params.merge!(date_attributes)
111
- end
112
-
113
- private
114
- def params_to_date(year, month, day)
115
- day ||= 1 # FIXME: is that really what we want? test.
116
- begin # TODO: test fails.
117
- return Date.new(year.to_i, month.to_i, day.to_i)
118
- rescue ArgumentError => e
119
- return nil
120
- end
121
- end
122
- end
123
-
124
57
  def assign_params(params)
125
- DateParamsFilter.new.call(params)
126
- params.each_pair do |attribute, value|
127
- assign_attribute(attribute, value)
58
+ self.tap do |s|
59
+ FreeForm::DateParamsFilter.new.call(params)
60
+ before_assign_params(params)
61
+ params.each_pair do |attribute, value|
62
+ assign_attribute(attribute, value)
63
+ end
64
+ after_assign_params(params)
128
65
  end
129
- self
130
66
  end
131
67
  alias_method :assign_attributes, :assign_params
132
68
  alias_method :populate, :assign_params
133
69
  alias_method :fill, :assign_params
134
70
 
71
+ def before_assign_params(params)
72
+ end
73
+ alias_method :before_assign_attributes, :before_assign_params
74
+ alias_method :before_populate, :before_assign_params
75
+ alias_method :before_fill, :before_assign_params
76
+
77
+ def after_assign_params(params)
78
+ end
79
+ alias_method :after_assign_attributes, :after_assign_params
80
+ alias_method :after_populate, :after_assign_params
81
+ alias_method :after_fill, :after_assign_params
82
+
83
+ def model_property_mappings
84
+ self.class.property_mappings
85
+ end
86
+
135
87
  private
136
88
  def assign_attribute(attribute, value)
137
89
  self.send :"#{attribute}=", value unless ignore?(attribute, value)
138
90
  end
139
91
 
140
92
  def ignore?(attribute, value)
141
- mapping = self.class.property_mappings[attribute.to_sym]
142
- return (mapping[:ignore_blank] && value.blank?) unless mapping.nil?
93
+ mapping = model_property_mappings[attribute.to_sym]
94
+ if mapping.present?
95
+ mapping[:ignore_blank] && value.blank?
96
+ else
97
+ false
98
+ end
143
99
  end
144
100
  end
145
101
  end
@@ -6,59 +6,73 @@ module FreeForm
6
6
  end
7
7
 
8
8
  module ClassMethods
9
+ def validate_nested_forms
10
+ validate :validate_nested_forms
11
+ end
12
+
9
13
  def validate_models
10
- validate :model_validity
14
+ validate :validate_component_models
11
15
  end
12
16
  end
13
17
 
14
18
  protected
15
- def model_validity
16
- self.class.models.each do |form_model|
17
- if send(form_model).is_a?(Array)
18
- # If it's an array, we're dealing with nested forms
19
- errors.add(:base, "has invalid nested forms") unless validate_nested_forms(send(form_model))
20
- else
21
- # Otherwise, validate the form object
22
- validate_form_model(form_model)
23
- end
24
- end
19
+ def validate_nested_forms
20
+ models.each { |m| validate_nested_form(m) if m.is_a?(FreeForm::Form) }
25
21
  end
26
22
 
27
- private
28
- def validate_nested_forms(form_array)
29
- form_validity = true
30
- form_array.each do |model|
31
- destroyed = model.respond_to?(:marked_for_destruction?) ? model.marked_for_destruction? : false
32
- unless model.valid? || destroyed
33
- form_validity = false
34
- end
35
- end
36
- form_validity
23
+ def validate_component_models
24
+ models.each { |m| validate_model(m) unless m.is_a?(FreeForm::Form) }
37
25
  end
38
26
 
39
- def validate_form_model(form_model)
40
- model = send(form_model)
41
- append_errors(form_model)
42
- return model.valid?
27
+ private
28
+ def validate_nested_form(form)
29
+ errors.add(:base, "has invalid nested forms") unless marked_for_destruction_or_valid?(form)
43
30
  end
44
31
 
45
- def append_errors(form_model)
46
- model = send(form_model)
32
+ def validate_model(model)
47
33
  model.valid?
34
+ add_model_errors_to_form(model)
35
+ end
36
+
37
+ def marked_for_destruction_or_valid?(form)
38
+ form_marked_for_destruction?(form) || form.valid?
39
+ end
40
+
41
+ def form_marked_for_destruction?(form)
42
+ form.respond_to?(:marked_for_destruction?) ? form.marked_for_destruction? : false
43
+ end
44
+
45
+ def add_model_errors_to_form(model)
48
46
  model.errors.each do |error, message|
49
- if find_form_field_from_model_field(form_model, error)
50
- self.errors.add(find_form_field_from_model_field(form_model, error), message)
51
- end
47
+ add_error_to_form(model, error, message)
48
+ end
49
+ end
50
+
51
+ def add_error_to_form(model, error, message)
52
+ field = find_form_field_from_model_field(model, error)
53
+ if field
54
+ errors.add(field, message)
52
55
  end
53
56
  end
54
57
 
55
- def find_form_field_from_model_field(model, field)
56
- self.class.property_mappings.each_pair do |property, attributes|
57
- if (attributes[:model] == model.to_sym) && (attributes[:field] == field.to_sym)
58
+ def find_form_field_from_model_field(model, error_field)
59
+ model_property_mappings.each_pair do |property, attributes|
60
+ model_sym = model_symbol_from_model(model)
61
+ field_sym = error_field.to_sym
62
+
63
+ if (attributes[:model] == model_sym) && (attributes[:field] == field_sym)
58
64
  return property
59
65
  end
60
66
  end
61
- return false
67
+ nil
68
+ end
69
+
70
+ def model_symbol_from_model(model)
71
+ models_as_symbols.find { |sym| return sym if send(sym) == model }
72
+ end
73
+
74
+ def models_as_symbols
75
+ self.class.models.map { |x| x.to_sym }
62
76
  end
63
77
  end
64
78
  end