freeform 1.0.11 → 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.
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