freeform 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.project +12 -0
- data/.rspec +2 -0
- data/README.md +209 -4
- data/freeform.gemspec +1 -0
- data/lib/freeform/form/form_input_key.rb +17 -0
- data/lib/freeform/form/nested.rb +110 -0
- data/lib/freeform/form/persistence.rb +57 -0
- data/lib/freeform/form/property.rb +90 -0
- data/lib/freeform/form.rb +24 -0
- data/lib/freeform/version.rb +1 -1
- data/lib/freeform.rb +10 -1
- data/spec/form/form_input_key_spec.rb +19 -0
- data/spec/form/nested_spec.rb +77 -0
- data/spec/form/persistence_spec.rb +237 -0
- data/spec/form/property_spec.rb +245 -0
- data/spec/spec_helper.rb +8 -0
- metadata +35 -2
data/.project
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<projectDescription>
|
3
|
+
<name>freeform</name>
|
4
|
+
<comment></comment>
|
5
|
+
<projects>
|
6
|
+
</projects>
|
7
|
+
<buildSpec>
|
8
|
+
</buildSpec>
|
9
|
+
<natures>
|
10
|
+
<nature>com.aptana.projects.webnature</nature>
|
11
|
+
</natures>
|
12
|
+
</projectDescription>
|
data/.rspec
ADDED
data/README.md
CHANGED
@@ -1,6 +1,12 @@
|
|
1
|
-
#
|
1
|
+
# FreeForm
|
2
2
|
|
3
|
-
|
3
|
+
FreeForm is a gem designed to give you total control over form objects, allowing you to map form objects to domain objects in any way that you see fit.
|
4
|
+
|
5
|
+
It is still a very-pre-beta work in progess, but hopefully it can showcase the power of form objects.
|
6
|
+
|
7
|
+
FreeForm is designed primarily with Rails in mind, but it should work on any Ruby framework.
|
8
|
+
|
9
|
+
FreeForm is compatible with (as far as I know) most form gems, including simpleform, formbuilder, and Ryan Bate's nested_form gem.
|
4
10
|
|
5
11
|
## Installation
|
6
12
|
|
@@ -16,9 +22,208 @@ Or install it yourself as:
|
|
16
22
|
|
17
23
|
$ gem install freeform
|
18
24
|
|
19
|
-
##
|
25
|
+
## How It Works
|
26
|
+
|
27
|
+
FreeForm can 1-*n* models, exposing whatever attributes you wish from each model, and delegating those assignments back to the models themselves. This means that one form can be used just as easily to support parent/child models, multiple unrelated models, etc. Your database relationships can change, and your forms won't have to.
|
28
|
+
|
29
|
+
**Example**
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
class RegistrationForm < FreeForm::Form
|
33
|
+
form_models :user, :address
|
34
|
+
|
35
|
+
property :username, :on => :user
|
36
|
+
property :email, :on => :user
|
37
|
+
property :password, :on => :user
|
38
|
+
|
39
|
+
property :street, :on => :address
|
40
|
+
property :city, :on => :address
|
41
|
+
property :state, :on => :address
|
42
|
+
property :zip_code, :on => :address
|
43
|
+
end
|
44
|
+
|
45
|
+
class User < ActiveRecord::Base
|
46
|
+
has_one :address
|
47
|
+
...
|
48
|
+
end
|
49
|
+
|
50
|
+
class Address < ActiveRecord::Base
|
51
|
+
belongs_to :user
|
52
|
+
...
|
53
|
+
end
|
54
|
+
|
55
|
+
user = User.new
|
56
|
+
RegistrationForm.new(:user => user, :address => user.build_address)
|
57
|
+
```
|
58
|
+
**Oh No!**
|
59
|
+
Our domain model has changed, and we needs users to have multiple addresses! We'll change our model...but our form remains the same.
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
class User < ActiveRecord::Base
|
63
|
+
has_many :addresses
|
64
|
+
...
|
65
|
+
end
|
66
|
+
|
67
|
+
class Address < ActiveRecord::Base
|
68
|
+
belongs_to :user
|
69
|
+
...
|
70
|
+
end
|
71
|
+
|
72
|
+
user = User.new
|
73
|
+
RegistrationForm.new(:user => user, :address => user.addresses.build)
|
74
|
+
```
|
75
|
+
|
76
|
+
## Defining Forms
|
77
|
+
|
78
|
+
FreeForm doesn't assume a lot, so you need to tell it:
|
79
|
+
* The names of the models it's going to be mapping (specified as `form_model` or `form_models`)
|
80
|
+
* The properties of the form, and which model they map to (specified as `property` or `properties`). Properties that don't map to a model are considered to be just form attributes.
|
81
|
+
* How to validate, if at all (see below)
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
class RegistrationForm < FreeForm::Form
|
85
|
+
form_models :user, :address
|
86
|
+
|
87
|
+
property :username, :on => :user
|
88
|
+
property :email, :on => :user
|
89
|
+
property :password, :on => :user
|
90
|
+
|
91
|
+
property :street, :on => :address
|
92
|
+
property :city, :on => :address
|
93
|
+
property :state, :on => :address
|
94
|
+
property :zip_code, :on => :address
|
95
|
+
end
|
96
|
+
|
97
|
+
class User < ActiveRecord::Base
|
98
|
+
has_one :address
|
99
|
+
...
|
100
|
+
end
|
101
|
+
|
102
|
+
class Address < ActiveRecord::Base
|
103
|
+
belongs_to :user
|
104
|
+
...
|
105
|
+
end
|
106
|
+
|
107
|
+
user = User.new
|
108
|
+
RegistrationForm.new(:user => user, :address => user.build_address)
|
109
|
+
```
|
110
|
+
## Form Validations
|
111
|
+
|
112
|
+
FreeForm handles validations wherever you define them. If you want to check model validations, simply specify that option in your form definition
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
class UserForm < FreeForm::Form
|
116
|
+
form_models :user
|
117
|
+
validate_models # This will check to see that the :user model itself is valid
|
118
|
+
|
119
|
+
property :username, :on => :user
|
120
|
+
property :email, :on => :user
|
121
|
+
property :current_password
|
122
|
+
|
123
|
+
# But you can also validate in the form itself!
|
124
|
+
validates :email, :presence => true
|
125
|
+
validate :valid_current_password
|
126
|
+
|
127
|
+
def valid_current_password
|
128
|
+
user.password == current_password
|
129
|
+
end
|
130
|
+
end
|
131
|
+
```
|
132
|
+
Personally, I use validations in both places. My domain models have their own validations, which I use for things that are universally true of that model (e.g. email is correctly formatted). Some forms have validations though that are specific to that form, and they live in the form itself (see above example with `current_password`)
|
133
|
+
|
134
|
+
## Nesting Forms
|
135
|
+
|
136
|
+
Sometimes, you need to be able to support a collection of unknown size (e.g. a user with many phone numbers). Since FreeForm makes no assumptions about your domain models, we nest forms themselves.
|
137
|
+
|
138
|
+
```ruby
|
139
|
+
class UserForm < FreeForm::Form
|
140
|
+
form_models :user
|
141
|
+
|
142
|
+
property :username, :on => :user
|
143
|
+
property :email, :on => :user
|
144
|
+
|
145
|
+
nested_form :phone_numbers do
|
146
|
+
form_models :phone
|
147
|
+
|
148
|
+
property :area_code, :on => :phone
|
149
|
+
property :number, :on => :phone
|
150
|
+
end
|
151
|
+
end
|
152
|
+
```
|
153
|
+
**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.
|
154
|
+
|
155
|
+
When using a nested form, the form starts with **no** nested forms pre-built. FreeForm provides a method called `build_#{nested_form_model}` (e.g. `build_phone_numbers`) that you can use to build a nested form. You must provide the initializer:
|
156
|
+
```ruby
|
157
|
+
form = UserForm.new(:user => User.new)
|
158
|
+
form.build_phone_numbers(:phone => Phone.new)
|
159
|
+
# The singularized version is aliased as well.
|
160
|
+
form.build_phone_number(:phone => Phone.new)
|
161
|
+
```
|
162
|
+
|
163
|
+
You can specify the default initializers for that form with the accessor `#{nested_form_name}_form_initializer`.
|
164
|
+
```ruby
|
165
|
+
form = UserForm.new(
|
166
|
+
:user => User.new,
|
167
|
+
:phone_numbers_form_initializer => {:phone => Phone.new} )
|
168
|
+
|
169
|
+
form.build_phone_numbers
|
170
|
+
form.build_phone_number
|
171
|
+
```
|
172
|
+
This is a necessary parameter if you're using the `nested_form` gem, as new nested forms are initialized automatically.
|
173
|
+
|
174
|
+
## Initialize With Care!
|
175
|
+
|
176
|
+
FreeForm's flexibility comes at a bit of a cost - it makes no assumptions about relationships between initialized models or nested forms. So initializing the form correctly is important.
|
177
|
+
|
178
|
+
** Example **
|
179
|
+
```ruby
|
180
|
+
current_user # => #<User:0x100124b88>
|
181
|
+
current_user.phone_numbers # => [#<Phone:0x100194867>, #<Phone:0x100100cd4>]
|
182
|
+
|
183
|
+
class UserForm < FreeForm::Form
|
184
|
+
form_models :user
|
185
|
+
|
186
|
+
property :username, :on => :user
|
187
|
+
property :email, :on => :user
|
188
|
+
|
189
|
+
nested_form :phone_numbers do
|
190
|
+
form_models :phone
|
191
|
+
|
192
|
+
property :area_code, :on => :phone
|
193
|
+
property :number, :on => :phone
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
form = UserForm.new(:user => current_user)
|
198
|
+
```
|
199
|
+
|
200
|
+
Will the current_user's phone numbers automatically appear as nested forms? **No.**
|
201
|
+
If you want them there, put them there, like this:
|
202
|
+
|
203
|
+
```ruby
|
204
|
+
current_user.phone_numbers.each do |phone_number|
|
205
|
+
form.build_phone_number(:phone => phone_number)
|
206
|
+
end
|
207
|
+
```
|
208
|
+
|
209
|
+
## Extras!
|
210
|
+
|
211
|
+
I'm open to trying to build out little helper features wherever possible. Right now, FreeForm comes with one handy option called `form_input_key`. Setting this determines the parameter key that your forms are rendered with in Rails.
|
212
|
+
|
213
|
+
*Why use this?*
|
214
|
+
Well, I like to keep my form keys fairly concise, but in a bigger application I often end up namespacing my forms. And changing namespaces sometimes breaks Cucumber specs, which might be hardcoded to find a particular ID. No more!
|
215
|
+
|
216
|
+
```ruby
|
217
|
+
class MyScope::UserForm < FreeForm::Form
|
218
|
+
form_input_key :user # Sets the parameter key for HTML rendering.
|
219
|
+
form_models :user
|
20
220
|
|
21
|
-
|
221
|
+
property :email, :on => :user
|
222
|
+
end
|
223
|
+
```
|
224
|
+
Would render with HTML input fields like
|
225
|
+
`<input id="user_email" ... name="user[email]"></input>` instead of
|
226
|
+
`<input id="my_scope_user_form_email" ... name="my_scope_user_form[email]"></input>`
|
22
227
|
|
23
228
|
## Contributing
|
24
229
|
|
data/freeform.gemspec
CHANGED
@@ -18,6 +18,7 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
+
spec.add_dependency "activemodel"
|
21
22
|
spec.add_development_dependency "bundler", "~> 1.3"
|
22
23
|
spec.add_development_dependency "rake"
|
23
24
|
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module FreeForm
|
2
|
+
module FormInputKey
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(ClassMethods)
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def form_input_key(name)
|
9
|
+
constant = name.to_s.camelize
|
10
|
+
Object.const_set(constant, Class.new) unless Object.const_defined?(constant)
|
11
|
+
define_singleton_method(:model_name) do
|
12
|
+
ActiveModel::Name.new(constant.constantize)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'freeform/form/property'
|
2
|
+
|
3
|
+
module FreeForm
|
4
|
+
module Nested
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
# Nested Forms
|
11
|
+
#------------------------------------------------------------------------
|
12
|
+
attr_accessor :nested_forms
|
13
|
+
|
14
|
+
def nested_form(attribute, options={}, &block)
|
15
|
+
# Define an attr_accessor for the parent class to hold this attribute
|
16
|
+
declared_model(attribute)
|
17
|
+
|
18
|
+
# Define the new class, and set it up with a new name
|
19
|
+
nested_form_class = Class.new(FreeForm::Form) do
|
20
|
+
include FreeForm::Property
|
21
|
+
self.instance_eval(&block)
|
22
|
+
end
|
23
|
+
self.const_set("#{attribute.to_s.camelize}Form", nested_form_class)
|
24
|
+
|
25
|
+
@nested_forms ||= {}
|
26
|
+
@nested_forms.merge!({:"#{attribute}" => nested_form_class})
|
27
|
+
|
28
|
+
# Defined other methods
|
29
|
+
define_nested_model_methods(attribute, nested_form_class)
|
30
|
+
|
31
|
+
# Setup Handling for nested attributes
|
32
|
+
define_nested_attribute_methods(attribute, nested_form_class)
|
33
|
+
end
|
34
|
+
alias_method :has_many, :nested_form
|
35
|
+
alias_method :has_one, :nested_form
|
36
|
+
|
37
|
+
# Supporting Methods
|
38
|
+
#------------------------------------------------------------------------
|
39
|
+
def reflect_on_association(key, *args)
|
40
|
+
reflection = OpenStruct.new
|
41
|
+
reflection.klass = self.nested_forms[key]
|
42
|
+
reflection
|
43
|
+
end
|
44
|
+
|
45
|
+
protected
|
46
|
+
# Defining Helper Methods For Models
|
47
|
+
#------------------------------------------------------------------------
|
48
|
+
def define_nested_model_methods(attribute, form_class)
|
49
|
+
singularized_attribute = attribute.to_s.singularize.to_s
|
50
|
+
|
51
|
+
# Example: form.addresses will return all nested address forms
|
52
|
+
define_method(:"#{attribute}") do
|
53
|
+
@nested_attributes ||= []
|
54
|
+
end
|
55
|
+
|
56
|
+
# Example: form.address_form_initializer (aliased to form.addresses_form_initializer)
|
57
|
+
attr_accessor :"#{attribute}_form_initializer"
|
58
|
+
alias_method :"#{singularized_attribute}_form_initializer", :"#{attribute}_form_initializer"
|
59
|
+
alias_method :"#{singularized_attribute}_form_initializer=", :"#{attribute}_form_initializer="
|
60
|
+
|
61
|
+
# Example: form.build_addresses (optional custom initializer)
|
62
|
+
define_method(:"build_#{attribute}") do |initializer=nil|
|
63
|
+
# Get correct class
|
64
|
+
form_class = self.class.nested_forms[:"#{attribute}"]
|
65
|
+
|
66
|
+
# Set default intializer if none provided
|
67
|
+
initializer ||= send("#{attribute}_form_initializer")
|
68
|
+
|
69
|
+
# Build new model
|
70
|
+
form_model = form_class.new(initializer)
|
71
|
+
@nested_attributes ||= []
|
72
|
+
@nested_attributes << form_model
|
73
|
+
form_model
|
74
|
+
end
|
75
|
+
alias_method :"build_#{singularized_attribute}", :"build_#{attribute}"
|
76
|
+
end
|
77
|
+
|
78
|
+
# Defining Helper Methods For Nested Attributes
|
79
|
+
#------------------------------------------------------------------------
|
80
|
+
def define_nested_attribute_methods(attribute, nested_form_class)
|
81
|
+
# Equivalent of "address_attributes=" for "address" attribute
|
82
|
+
define_method(:"#{attribute}_attributes=") do |params|
|
83
|
+
build_models_from_param_count(attribute, params)
|
84
|
+
|
85
|
+
self.send(:"#{attribute}").zip(params).each do |model, params_array|
|
86
|
+
identifier = params_array[0]
|
87
|
+
attributes = params_array[1]
|
88
|
+
#TODO: Check the key against id?
|
89
|
+
model.fill(attributes)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Instance Methods
|
96
|
+
#--------------------------------------------------------------------------
|
97
|
+
protected
|
98
|
+
def build_models_from_param_count(attribute, params)
|
99
|
+
# Get the difference between sets of params passed and current form objects
|
100
|
+
num_param_models = params.length
|
101
|
+
num_built_models = self.send(:"#{attribute}").nil? ? 0 : self.send(:"#{attribute}").length
|
102
|
+
additional_models_needed = num_param_models - num_built_models
|
103
|
+
|
104
|
+
# Make up the difference by building new nested form models
|
105
|
+
additional_models_needed.times do
|
106
|
+
send("build_#{attribute.to_s}")
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module FreeForm
|
2
|
+
module Persistence
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(ClassMethods)
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def validate_models
|
9
|
+
validate :model_validity
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def save
|
14
|
+
return false unless valid?
|
15
|
+
self.class.models.each do |form_model|
|
16
|
+
if send(form_model).is_a?(Array)
|
17
|
+
send(form_model).each { |model| return model.save }
|
18
|
+
else
|
19
|
+
return false unless send(form_model).save
|
20
|
+
end
|
21
|
+
end
|
22
|
+
return true
|
23
|
+
end
|
24
|
+
|
25
|
+
def save!
|
26
|
+
raise StandardError, "form invalid." unless valid?
|
27
|
+
self.class.models.each do |form_model|
|
28
|
+
if send(form_model).is_a?(Array)
|
29
|
+
send(form_model).each { |model| model.save! }
|
30
|
+
else
|
31
|
+
send(form_model).save!
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
def model_validity
|
38
|
+
model_validity = true
|
39
|
+
self.class.models.each do |form_model|
|
40
|
+
if send(form_model).is_a?(Array)
|
41
|
+
send(form_model).each { |model| model_validity = validate_and_append_errors(model) && model_validity }
|
42
|
+
else
|
43
|
+
model_validity = validate_and_append_errors(send(form_model)) && model_validity
|
44
|
+
end
|
45
|
+
end
|
46
|
+
return model_validity
|
47
|
+
end
|
48
|
+
|
49
|
+
def validate_and_append_errors(model)
|
50
|
+
unless model.valid?
|
51
|
+
model.errors.each { |error, message| self.errors.add(error, message) }
|
52
|
+
return false
|
53
|
+
end
|
54
|
+
return true
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module FreeForm
|
2
|
+
module Property
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(ClassMethods)
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
include Forwardable
|
9
|
+
|
10
|
+
# Models
|
11
|
+
#------------------------------------------------------------------------
|
12
|
+
def models
|
13
|
+
@models ||= []
|
14
|
+
end
|
15
|
+
|
16
|
+
def declared_model(name, opts={})
|
17
|
+
@models ||= []
|
18
|
+
@models << name
|
19
|
+
attr_accessor name
|
20
|
+
end
|
21
|
+
alias_method :form_model, :declared_model
|
22
|
+
|
23
|
+
def declared_models(*names)
|
24
|
+
names.each do |name|
|
25
|
+
declared_model(name)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
alias_method :form_models, :declared_models
|
29
|
+
|
30
|
+
# Properties
|
31
|
+
#------------------------------------------------------------------------
|
32
|
+
def ignored_blank_params
|
33
|
+
@ignored_blank_params ||= []
|
34
|
+
end
|
35
|
+
|
36
|
+
def property(attribute, options={})
|
37
|
+
if options[:on]
|
38
|
+
def_delegator options[:on], attribute
|
39
|
+
def_delegator options[:on], "#{attribute}=".to_sym
|
40
|
+
else
|
41
|
+
attr_accessor attribute
|
42
|
+
end
|
43
|
+
@ignored_blank_params ||= []
|
44
|
+
@ignored_blank_params << attribute if options[:ignore_blank]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def assign_params(params)
|
49
|
+
filter_dates(params)
|
50
|
+
params.each_pair do |attribute, value|
|
51
|
+
self.send :"#{attribute}=", value unless ignore?(attribute, value)
|
52
|
+
end
|
53
|
+
self
|
54
|
+
end
|
55
|
+
alias_method :assign_attributes, :assign_params
|
56
|
+
alias_method :populate, :assign_params
|
57
|
+
alias_method :fill, :assign_params
|
58
|
+
|
59
|
+
def ignore?(attribute, value)
|
60
|
+
ignored_if_blank = self.class.ignored_blank_params
|
61
|
+
return (ignored_if_blank.include?(attribute.to_sym) && value.blank?)
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
#TODO: This should be its own model
|
66
|
+
def filter_dates(params)
|
67
|
+
date_attributes = {}
|
68
|
+
params.each do |attribute, value|
|
69
|
+
if attribute.to_s.include?("(1i)") || attribute.to_s.include?("(2i)") || attribute.to_s.include?("(3i)")
|
70
|
+
date_attribute = attribute.to_s.gsub(/(\(.*\))/, "")
|
71
|
+
date_attributes[date_attribute.to_sym] = params_to_date(
|
72
|
+
params.delete("#{date_attribute}(1i)"),
|
73
|
+
params.delete("#{date_attribute}(2i)"),
|
74
|
+
params.delete("#{date_attribute}(3i)")
|
75
|
+
)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
params.merge!(date_attributes)
|
79
|
+
end
|
80
|
+
|
81
|
+
def params_to_date(year, month, day)
|
82
|
+
day ||= 1
|
83
|
+
begin
|
84
|
+
return Date.new(year.to_i, month.to_i, day.to_i)
|
85
|
+
rescue => e
|
86
|
+
return nil
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'ostruct'
|
3
|
+
require 'active_model'
|
4
|
+
|
5
|
+
module FreeForm
|
6
|
+
class Form
|
7
|
+
extend Forwardable
|
8
|
+
extend ActiveModel::Naming
|
9
|
+
extend ActiveModel::Callbacks
|
10
|
+
include ActiveModel::Conversion
|
11
|
+
include ActiveModel::Validations
|
12
|
+
|
13
|
+
# Instance Methods
|
14
|
+
#----------------------------------------------------------------------------
|
15
|
+
# Required for ActiveModel
|
16
|
+
def persisted?
|
17
|
+
false
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(h={})
|
21
|
+
h.each {|k,v| send("#{k}=",v)}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/freeform/version.rb
CHANGED
data/lib/freeform.rb
CHANGED
@@ -1,5 +1,14 @@
|
|
1
|
-
require
|
1
|
+
require 'freeform/version'
|
2
|
+
require 'freeform/form'
|
3
|
+
#require 'reform/form/composition'
|
4
|
+
#require 'reform/form/active_model'
|
2
5
|
|
6
|
+
if defined?(Rails) # DISCUSS: is everyone ok with this?
|
7
|
+
# require 'reform/rails'
|
8
|
+
end
|
9
|
+
|
10
|
+
=begin
|
3
11
|
module Freeform
|
4
12
|
# Your code goes here...
|
5
13
|
end
|
14
|
+
=end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'freeform/form/form_input_key'
|
3
|
+
|
4
|
+
describe FreeForm::FormInputKey do
|
5
|
+
describe "form input key", :form_input_key => true do
|
6
|
+
let(:form_class) do
|
7
|
+
Class.new(Module) do
|
8
|
+
include FreeForm::FormInputKey
|
9
|
+
form_input_key :test_key
|
10
|
+
|
11
|
+
attr_accessor :my_accessor
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
it "sets form_input_key" do
|
16
|
+
form_class.model_name.should eq("TestKey")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'freeform/form/property'
|
3
|
+
require 'freeform/form/nested'
|
4
|
+
|
5
|
+
describe FreeForm::Nested do
|
6
|
+
describe "class methods", :class_methods => true do
|
7
|
+
describe "has_many", :has_many => true do
|
8
|
+
let(:form_class) do
|
9
|
+
klass = Class.new(Module) do
|
10
|
+
include FreeForm::Property
|
11
|
+
include FreeForm::Nested
|
12
|
+
|
13
|
+
has_many :mailing_addresses do
|
14
|
+
declared_model :address
|
15
|
+
|
16
|
+
property :street, :on => :address
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(h={})
|
20
|
+
h.each {|k,v| send("#{k}=",v)}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
# This wrapper just avoids CONST warnings
|
24
|
+
v, $VERBOSE = $VERBOSE, nil
|
25
|
+
Module.const_set("DummyForm", klass)
|
26
|
+
$VERBOSE = v
|
27
|
+
klass
|
28
|
+
end
|
29
|
+
|
30
|
+
let(:form) do
|
31
|
+
form_class.new( :mailing_address_form_initializer => { :address => OpenStruct.new } )
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "form class" do
|
35
|
+
it "sets nested_form in models" do
|
36
|
+
form_class.models.should eq([:mailing_addresses])
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "building nested models" do
|
41
|
+
it "initializes with no nested models prebuilt" do
|
42
|
+
form.mailing_addresses.should eq([])
|
43
|
+
end
|
44
|
+
|
45
|
+
it "allows nested_forms to be built" do
|
46
|
+
form.build_mailing_addresses
|
47
|
+
form.mailing_addresses.should be_an(Array)
|
48
|
+
form.mailing_addresses.should_not be_empty
|
49
|
+
form.mailing_addresses.first.should be_a(Module::DummyForm::MailingAddressesForm)
|
50
|
+
end
|
51
|
+
|
52
|
+
it "allows nested_forms to be built with custom initializers" do
|
53
|
+
form.build_mailing_address(:address => OpenStruct.new(:street => "1600 Pennsylvania Ave."))
|
54
|
+
form.mailing_addresses.first.street.should eq("1600 Pennsylvania Ave.")
|
55
|
+
end
|
56
|
+
|
57
|
+
it "reflects on association" do
|
58
|
+
reflection = form_class.reflect_on_association(:mailing_addresses)
|
59
|
+
reflection.klass.should eq(Module::DummyForm::MailingAddressesForm)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "setting attributes" do
|
64
|
+
describe "nested attribute assignment" do
|
65
|
+
let(:attributes) do
|
66
|
+
{ :mailing_addresses_attributes => { "0" => { :street => "123 Main St." } } }
|
67
|
+
end
|
68
|
+
|
69
|
+
it "assigns nested attributes" do
|
70
|
+
form.fill(attributes)
|
71
|
+
form.mailing_addresses.first.street.should eq("123 Main St.")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,237 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'freeform/form/property'
|
3
|
+
require 'freeform/form/nested'
|
4
|
+
require 'freeform/form/persistence'
|
5
|
+
|
6
|
+
describe FreeForm::Persistence do
|
7
|
+
let(:user) do
|
8
|
+
model = Class.new(Module) do
|
9
|
+
include ActiveModel::Validations
|
10
|
+
def self.model_name; ActiveModel::Name.new(self, nil, "user") end
|
11
|
+
|
12
|
+
attr_accessor :username
|
13
|
+
validates :username, :presence => true
|
14
|
+
|
15
|
+
def save; return valid? end
|
16
|
+
alias_method :save!, :save
|
17
|
+
end.new
|
18
|
+
model
|
19
|
+
end
|
20
|
+
|
21
|
+
let(:address) do
|
22
|
+
model = Class.new(Module) do
|
23
|
+
include ActiveModel::Validations
|
24
|
+
def self.model_name; ActiveModel::Name.new(self, nil, "address") end
|
25
|
+
|
26
|
+
attr_accessor :street
|
27
|
+
attr_accessor :city
|
28
|
+
validates :street, :presence => true
|
29
|
+
|
30
|
+
def save; return valid? end
|
31
|
+
alias_method :save!, :save
|
32
|
+
end.new
|
33
|
+
model
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "validation", :validation => true do
|
37
|
+
context "without model validation" do
|
38
|
+
let(:form_class) do
|
39
|
+
klass = Class.new(Module) do
|
40
|
+
include ActiveModel::Validations
|
41
|
+
include FreeForm::Property
|
42
|
+
include FreeForm::Nested
|
43
|
+
include FreeForm::Persistence
|
44
|
+
|
45
|
+
form_model :user
|
46
|
+
property :username, :on => :user
|
47
|
+
validates :username, :presence => true
|
48
|
+
|
49
|
+
nested_form :addresses do
|
50
|
+
declared_model :address
|
51
|
+
|
52
|
+
property :street, :on => :address
|
53
|
+
end
|
54
|
+
|
55
|
+
def initialize(h={})
|
56
|
+
h.each {|k,v| send("#{k}=",v)}
|
57
|
+
end
|
58
|
+
end
|
59
|
+
# This wrapper just avoids CONST warnings
|
60
|
+
v, $VERBOSE = $VERBOSE, nil
|
61
|
+
Module.const_set("DummyForm", klass)
|
62
|
+
$VERBOSE = v
|
63
|
+
klass
|
64
|
+
end
|
65
|
+
|
66
|
+
let(:form) do
|
67
|
+
test_form = form_class.new( :user => OpenStruct.new, :address_form_initializer => { :address => OpenStruct.new } )
|
68
|
+
test_form.build_address
|
69
|
+
test_form
|
70
|
+
end
|
71
|
+
|
72
|
+
it "is not valid initially" do
|
73
|
+
form.should_not be_valid
|
74
|
+
end
|
75
|
+
|
76
|
+
it "has error on :username" do
|
77
|
+
form.valid?
|
78
|
+
form.errors[:username].should eq(["can't be blank"])
|
79
|
+
end
|
80
|
+
|
81
|
+
it "does not have errors on address" do
|
82
|
+
form.valid?
|
83
|
+
form.addresses.first.errors.should be_empty
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context "with model validation" do
|
88
|
+
let(:form_class) do
|
89
|
+
klass = Class.new(Module) do
|
90
|
+
include ActiveModel::Validations
|
91
|
+
include FreeForm::Property
|
92
|
+
include FreeForm::Nested
|
93
|
+
include FreeForm::Persistence
|
94
|
+
validate_models
|
95
|
+
|
96
|
+
form_model :user
|
97
|
+
property :username, :on => :user
|
98
|
+
property :form_property
|
99
|
+
validates :form_property, :presence => true
|
100
|
+
|
101
|
+
nested_form :addresses do
|
102
|
+
include ActiveModel::Validations
|
103
|
+
include FreeForm::Property
|
104
|
+
include FreeForm::Nested
|
105
|
+
include FreeForm::Persistence
|
106
|
+
validate_models
|
107
|
+
declared_model :address
|
108
|
+
|
109
|
+
property :street, :on => :address
|
110
|
+
property :city, :on => :address
|
111
|
+
end
|
112
|
+
|
113
|
+
def initialize(h={})
|
114
|
+
h.each {|k,v| send("#{k}=",v)}
|
115
|
+
end
|
116
|
+
end
|
117
|
+
# This wrapper just avoids CONST warnings
|
118
|
+
v, $VERBOSE = $VERBOSE, nil
|
119
|
+
Module.const_set("DummyForm", klass)
|
120
|
+
$VERBOSE = v
|
121
|
+
klass
|
122
|
+
end
|
123
|
+
|
124
|
+
let(:form) do
|
125
|
+
test_form = form_class.new( :user => user, :address_form_initializer => { :address => address } )
|
126
|
+
test_form.build_address
|
127
|
+
test_form
|
128
|
+
end
|
129
|
+
|
130
|
+
it "is not valid initially" do
|
131
|
+
form.should_not be_valid
|
132
|
+
end
|
133
|
+
|
134
|
+
it "has error on :username" do
|
135
|
+
form.valid?
|
136
|
+
form.errors[:username].should eq(["can't be blank"])
|
137
|
+
end
|
138
|
+
|
139
|
+
it "has error on :form_property" do
|
140
|
+
form.valid?
|
141
|
+
form.errors[:form_property].should eq(["can't be blank"])
|
142
|
+
end
|
143
|
+
|
144
|
+
it "has error on address street" do
|
145
|
+
form.valid?
|
146
|
+
form.addresses.first.errors[:street].should eq(["can't be blank"])
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
describe "saving", :saving => true do
|
152
|
+
let(:form_class) do
|
153
|
+
klass = Class.new(Module) do
|
154
|
+
include ActiveModel::Validations
|
155
|
+
include FreeForm::Property
|
156
|
+
include FreeForm::Nested
|
157
|
+
include FreeForm::Persistence
|
158
|
+
validate_models
|
159
|
+
|
160
|
+
form_model :user
|
161
|
+
property :username, :on => :user
|
162
|
+
property :form_property
|
163
|
+
validates :form_property, :presence => true
|
164
|
+
|
165
|
+
nested_form :addresses do
|
166
|
+
include ActiveModel::Validations
|
167
|
+
include FreeForm::Property
|
168
|
+
include FreeForm::Nested
|
169
|
+
include FreeForm::Persistence
|
170
|
+
validate_models
|
171
|
+
declared_model :address
|
172
|
+
|
173
|
+
property :street, :on => :address
|
174
|
+
property :city, :on => :address
|
175
|
+
end
|
176
|
+
|
177
|
+
def initialize(h={})
|
178
|
+
h.each {|k,v| send("#{k}=",v)}
|
179
|
+
end
|
180
|
+
end
|
181
|
+
# This wrapper just avoids CONST warnings
|
182
|
+
v, $VERBOSE = $VERBOSE, nil
|
183
|
+
Module.const_set("DummyForm", klass)
|
184
|
+
$VERBOSE = v
|
185
|
+
klass
|
186
|
+
end
|
187
|
+
|
188
|
+
let(:form) do
|
189
|
+
test_form = form_class.new( :user => user, :address_form_initializer => { :address => address } )
|
190
|
+
test_form.build_address
|
191
|
+
test_form
|
192
|
+
end
|
193
|
+
|
194
|
+
context "form is invalid" do
|
195
|
+
it "should be invalid" do
|
196
|
+
form.should_not be_valid
|
197
|
+
end
|
198
|
+
|
199
|
+
it "should return false on 'save'" do
|
200
|
+
form.save.should be_false
|
201
|
+
end
|
202
|
+
|
203
|
+
it "should raise error on 'save!'" do
|
204
|
+
expect{ form.save!.should be_false }.to raise_error
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
context "form is valid" do
|
209
|
+
before(:each) do
|
210
|
+
form.fill({
|
211
|
+
:username => "myusername",
|
212
|
+
:form_property => "present",
|
213
|
+
:addresses_attributes => { "0" => {
|
214
|
+
:street => "123 Main St.",
|
215
|
+
:city => "Springfield"
|
216
|
+
} }
|
217
|
+
})
|
218
|
+
end
|
219
|
+
|
220
|
+
it "should be valid" do
|
221
|
+
form.should be_valid
|
222
|
+
end
|
223
|
+
|
224
|
+
it "should return true on 'save', and call save on other models" do
|
225
|
+
user.should_receive(:save).and_return(true)
|
226
|
+
address.should_receive(:save).and_return(true)
|
227
|
+
form.save.should be_true
|
228
|
+
end
|
229
|
+
|
230
|
+
it "should return true on 'save!', and call save! on other models" do
|
231
|
+
user.should_receive(:save!).and_return(true)
|
232
|
+
address.should_receive(:save!).and_return(true)
|
233
|
+
form.save!.should be_true
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
@@ -0,0 +1,245 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'freeform/form/property'
|
3
|
+
|
4
|
+
describe FreeForm::Property do
|
5
|
+
describe "class methods", :class_methods => true do
|
6
|
+
describe "models", :models => true do
|
7
|
+
describe "declared model", :declared_model => true do
|
8
|
+
let(:form_class) do
|
9
|
+
Class.new(Module) do
|
10
|
+
include FreeForm::Property
|
11
|
+
declared_model :test_model_1
|
12
|
+
declared_model :test_model_2
|
13
|
+
|
14
|
+
def initialize(h={})
|
15
|
+
h.each {|k,v| send("#{k}=",v)}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
let(:form) do
|
21
|
+
form_class.new(
|
22
|
+
:test_model_1 => OpenStruct.new(:dummy => "yes"),
|
23
|
+
:test_model_2 => OpenStruct.new(:test => "clearly")
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "sets declared models as accessor" do
|
28
|
+
form.test_model_1.should eq(OpenStruct.new(:dummy => "yes"))
|
29
|
+
form.test_model_2.should eq(OpenStruct.new(:test => "clearly"))
|
30
|
+
end
|
31
|
+
|
32
|
+
it "has writable set accessors" do
|
33
|
+
form.test_model_1 = "Something New"
|
34
|
+
form.test_model_1.should eq("Something New")
|
35
|
+
end
|
36
|
+
|
37
|
+
it "sets @models in class to declared models" do
|
38
|
+
form_class.models.should eq([:test_model_1, :test_model_2])
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "form model", :form_model => true do
|
43
|
+
let(:form_class) do
|
44
|
+
Class.new(Module) do
|
45
|
+
include FreeForm::Property
|
46
|
+
form_model :test_model_1
|
47
|
+
form_model :test_model_2
|
48
|
+
|
49
|
+
def initialize(h={})
|
50
|
+
h.each {|k,v| send("#{k}=",v)}
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
let(:form) do
|
56
|
+
form_class.new(
|
57
|
+
:test_model_1 => OpenStruct.new(:dummy => "yes"),
|
58
|
+
:test_model_2 => OpenStruct.new(:test => "clearly")
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "sets declared models as accessor" do
|
63
|
+
form.test_model_1.should eq(OpenStruct.new(:dummy => "yes"))
|
64
|
+
form.test_model_2.should eq(OpenStruct.new(:test => "clearly"))
|
65
|
+
end
|
66
|
+
|
67
|
+
it "has writable set accessors" do
|
68
|
+
form.test_model_1 = "Something New"
|
69
|
+
form.test_model_1.should eq("Something New")
|
70
|
+
end
|
71
|
+
|
72
|
+
it "sets @models in class to declared models" do
|
73
|
+
form_class.models.should eq([:test_model_1, :test_model_2])
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe "declared models", :declared_models => true do
|
78
|
+
let(:form_class) do
|
79
|
+
Class.new(Module) do
|
80
|
+
include FreeForm::Property
|
81
|
+
declared_models :test_model_1, :test_model_2
|
82
|
+
|
83
|
+
def initialize(h={})
|
84
|
+
h.each {|k,v| send("#{k}=",v)}
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
let(:form) do
|
90
|
+
form_class.new(
|
91
|
+
:test_model_1 => OpenStruct.new(:dummy => "yes"),
|
92
|
+
:test_model_2 => OpenStruct.new(:test => "clearly")
|
93
|
+
)
|
94
|
+
end
|
95
|
+
|
96
|
+
it "sets declared models as accessor" do
|
97
|
+
form.test_model_1.should eq(OpenStruct.new(:dummy => "yes"))
|
98
|
+
form.test_model_2.should eq(OpenStruct.new(:test => "clearly"))
|
99
|
+
end
|
100
|
+
|
101
|
+
it "has writable set accessors" do
|
102
|
+
form.test_model_1 = "Something New"
|
103
|
+
form.test_model_1.should eq("Something New")
|
104
|
+
end
|
105
|
+
|
106
|
+
it "sets @models in class to declared models" do
|
107
|
+
form_class.models.should eq([:test_model_1, :test_model_2])
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
describe "form models", :form_models => true do
|
112
|
+
let(:form_class) do
|
113
|
+
Class.new(Module) do
|
114
|
+
include FreeForm::Property
|
115
|
+
form_models :test_model_1, :test_model_2
|
116
|
+
|
117
|
+
def initialize(h={})
|
118
|
+
h.each {|k,v| send("#{k}=",v)}
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
let(:form) do
|
124
|
+
form_class.new(
|
125
|
+
:test_model_1 => OpenStruct.new(:dummy => "yes"),
|
126
|
+
:test_model_2 => OpenStruct.new(:test => "clearly")
|
127
|
+
)
|
128
|
+
end
|
129
|
+
|
130
|
+
it "sets declared models as accessor" do
|
131
|
+
form.test_model_1.should eq(OpenStruct.new(:dummy => "yes"))
|
132
|
+
form.test_model_2.should eq(OpenStruct.new(:test => "clearly"))
|
133
|
+
end
|
134
|
+
|
135
|
+
it "has writable set accessors" do
|
136
|
+
form.test_model_1 = "Something New"
|
137
|
+
form.test_model_1.should eq("Something New")
|
138
|
+
end
|
139
|
+
|
140
|
+
it "sets @models in class to declared models" do
|
141
|
+
form_class.models.should eq([:test_model_1, :test_model_2])
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
describe "properties", :properties => true do
|
147
|
+
describe "property", :property => true do
|
148
|
+
let(:form_class) do
|
149
|
+
Class.new(Module) do
|
150
|
+
include FreeForm::Property
|
151
|
+
declared_model :test_model
|
152
|
+
|
153
|
+
property :attribute_1, :on => :test_model
|
154
|
+
property :attribute_2, :on => :test_model, :ignore_blank => true
|
155
|
+
|
156
|
+
def initialize(h={})
|
157
|
+
h.each {|k,v| send("#{k}=",v)}
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
let(:form) do
|
163
|
+
form_class.new(
|
164
|
+
:test_model => OpenStruct.new(:attribute_1 => "yes"),
|
165
|
+
)
|
166
|
+
end
|
167
|
+
|
168
|
+
it "delegates properties to models" do
|
169
|
+
form.attribute_1.should eq("yes")
|
170
|
+
form.attribute_2.should be_nil
|
171
|
+
end
|
172
|
+
|
173
|
+
it "allows properties to be written, and delegates the values" do
|
174
|
+
form.attribute_1 = "changed"
|
175
|
+
form.attribute_1.should eq("changed")
|
176
|
+
form.test_model.attribute_1.should eq("changed")
|
177
|
+
end
|
178
|
+
|
179
|
+
it "tracks ignored blank fields" do
|
180
|
+
form_class.ignored_blank_params.should eq([:attribute_2])
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
describe "instance methods", :instance_methods => true do
|
187
|
+
describe "assign_attributes", :assign_attributes => true do
|
188
|
+
let(:form_class) do
|
189
|
+
Class.new(Module) do
|
190
|
+
include FreeForm::Property
|
191
|
+
declared_model :first_model
|
192
|
+
declared_model :second_model
|
193
|
+
|
194
|
+
property :attribute_1, :on => :first_model
|
195
|
+
property :attribute_2, :on => :first_model, :ignore_blank => true
|
196
|
+
property :attribute_3, :on => :second_model, :ignore_blank => true
|
197
|
+
property :attribute_4, :on => :second_model
|
198
|
+
|
199
|
+
def initialize(h)
|
200
|
+
h.each {|k,v| send("#{k}=",v)}
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
let(:form) do
|
206
|
+
form_class.new(
|
207
|
+
:first_model => OpenStruct.new(:attribute_1 => "first", :attribute_2 => "second"),
|
208
|
+
:second_model => OpenStruct.new(:attribute_3 => "third", :attribute_4 => "fourth"),
|
209
|
+
)
|
210
|
+
end
|
211
|
+
|
212
|
+
it "delegates properties to models" do
|
213
|
+
form.assign_attributes({
|
214
|
+
:attribute_1 => "changed", :attribute_2 => 182.34,
|
215
|
+
:attribute_3 => 45, :attribute_4 => Date.new(2013, 10, 10) })
|
216
|
+
|
217
|
+
form.attribute_1.should eq("changed")
|
218
|
+
form.attribute_2.should eq(182.34)
|
219
|
+
form.attribute_3.should eq(45)
|
220
|
+
form.attribute_4.should eq(Date.new(2013, 10, 10))
|
221
|
+
end
|
222
|
+
|
223
|
+
it "ignores blank params when set" do
|
224
|
+
form.assign_attributes({
|
225
|
+
:attribute_1 => "", :attribute_2 => "",
|
226
|
+
:attribute_3 => nil, :attribute_4 => nil })
|
227
|
+
|
228
|
+
form.attribute_1.should eq("")
|
229
|
+
form.attribute_2.should eq("second")
|
230
|
+
form.attribute_3.should eq("third")
|
231
|
+
form.attribute_4.should eq(nil)
|
232
|
+
end
|
233
|
+
|
234
|
+
it "handles individiual date components" do
|
235
|
+
#TODO: should pass with symbols too!!
|
236
|
+
form.assign_attributes({
|
237
|
+
"attribute_1(3i)" => 5,
|
238
|
+
"attribute_1(2i)" => 6,
|
239
|
+
"attribute_1(1i)" => 2013 })
|
240
|
+
|
241
|
+
form.attribute_1.should eq(Date.new(2013, 6, 5))
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: freeform
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -11,6 +11,22 @@ bindir: bin
|
|
11
11
|
cert_chain: []
|
12
12
|
date: 2013-11-14 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activemodel
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
14
30
|
- !ruby/object:Gem::Dependency
|
15
31
|
name: bundler
|
16
32
|
requirement: !ruby/object:Gem::Requirement
|
@@ -67,13 +83,25 @@ extensions: []
|
|
67
83
|
extra_rdoc_files: []
|
68
84
|
files:
|
69
85
|
- .gitignore
|
86
|
+
- .project
|
87
|
+
- .rspec
|
70
88
|
- Gemfile
|
71
89
|
- LICENSE.txt
|
72
90
|
- README.md
|
73
91
|
- Rakefile
|
74
92
|
- freeform.gemspec
|
75
93
|
- lib/freeform.rb
|
94
|
+
- lib/freeform/form.rb
|
95
|
+
- lib/freeform/form/form_input_key.rb
|
96
|
+
- lib/freeform/form/nested.rb
|
97
|
+
- lib/freeform/form/persistence.rb
|
98
|
+
- lib/freeform/form/property.rb
|
76
99
|
- lib/freeform/version.rb
|
100
|
+
- spec/form/form_input_key_spec.rb
|
101
|
+
- spec/form/nested_spec.rb
|
102
|
+
- spec/form/persistence_spec.rb
|
103
|
+
- spec/form/property_spec.rb
|
104
|
+
- spec/spec_helper.rb
|
77
105
|
homepage: https://github.com/brycesenz/freeform
|
78
106
|
licenses:
|
79
107
|
- MIT
|
@@ -100,4 +128,9 @@ signing_key:
|
|
100
128
|
specification_version: 3
|
101
129
|
summary: FreeForm lets you compose forms however you like, explicitly guiding how
|
102
130
|
each field maps back to your domain models. No more accepting nested attributes!
|
103
|
-
test_files:
|
131
|
+
test_files:
|
132
|
+
- spec/form/form_input_key_spec.rb
|
133
|
+
- spec/form/nested_spec.rb
|
134
|
+
- spec/form/persistence_spec.rb
|
135
|
+
- spec/form/property_spec.rb
|
136
|
+
- spec/spec_helper.rb
|