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 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
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/README.md CHANGED
@@ -1,6 +1,12 @@
1
- # Freeform
1
+ # FreeForm
2
2
 
3
- TODO: Write a gem description
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
- ## Usage
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
- TODO: Write usage instructions here
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
@@ -1,3 +1,3 @@
1
1
  module Freeform
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
data/lib/freeform.rb CHANGED
@@ -1,5 +1,14 @@
1
- require "freeform/version"
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
@@ -0,0 +1,8 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'freeform'
4
+ require 'active_model'
5
+
6
+ RSpec.configure do |config|
7
+ # some (optional) config here
8
+ end
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.1
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