reform 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b232b1d3bcc6dea9226b28873a60bba7a56772df
4
+ data.tar.gz: 4b987843362c2cc4ff3349304f0672182437a828
5
+ SHA512:
6
+ metadata.gz: 1c7eefc137c219075f02c5ff89035fadb1868b7b693ac828b100f63621f55158f0dc05b6a497efc935193f670438a42675487ebacdd5dfacf9d0c8a990a53911
7
+ data.tar.gz: 70f13e49731d80f0645f4467fb5521cce5b34ee9fe7490b23f0b2d6f79f66630ecec4902a2d17d2758a1b861804ad437ce205f3fcba693a084274e89f6662139
@@ -3,5 +3,7 @@ rvm:
3
3
  - 2.0.0
4
4
  - 1.9.3
5
5
  gemfile:
6
- - Gemfile
6
+ - gemfiles/Gemfile.rails-4.0
7
+ - gemfiles/Gemfile.rails-3.2
8
+ - gemfiles/Gemfile.rails-3.1
7
9
  - gemfiles/Gemfile.rails-3.0
data/CHANGES.md CHANGED
@@ -1,3 +1,14 @@
1
+ h3. 0.2.1
2
+
3
+ * `ActiveRecord::i18n_scope` now returns `activerecord`.
4
+ * `Form#save` now calls save on the model in `ActiveRecord` context.
5
+ * `Form#model` is public now.
6
+ * Introduce `:empty` to have empty fields that are accessible for validation and processing, only.
7
+ * Introduce `:virtual` for read-only fields the are like `:empty` but initially read from the decorated model.
8
+ * Fix uniqueness validation with `Composition` form.
9
+ * Move `setup` and `save` logic into respective representer classes. This might break your code in case you overwrite private reform classes.
10
+
11
+
1
12
  h3. 0.2.0
2
13
 
3
14
  * Added nested property and collection for `has_one` and `has_many` relationships. . Note that this currently works only 1-level deep.
data/README.md CHANGED
@@ -104,6 +104,11 @@ To push the incoming data to the models directly, call `#save` without the block
104
104
  # by calling @form.song.title= and @form.song.length=.
105
105
  ```
106
106
 
107
+ Think of `@form.save` as a sync operation where the submitted data is written to your models using public setters.
108
+
109
+ Note that this does _not_ call `save` on your models per default: this only happens in an ActiveRecord environment or when `Form::ActiveRecord` is mixed in (learn more [here](https://github.com/apotonick/reform#activerecord-compatibility)).
110
+
111
+
107
112
 
108
113
  ## Nesting Forms: 1-1 Relations
109
114
 
@@ -165,7 +170,7 @@ Or use something like `#fields_for` in a Rails environment.
165
170
  = f.text_field :title
166
171
  = f.text_field :length
167
172
 
168
- = fields_for :artist do |a|
173
+ = f.fields_for :artist do |a|
169
174
  = a.text_field :name
170
175
  ```
171
176
 
@@ -233,7 +238,7 @@ However, `#fields_for` works just fine, again.
233
238
  = form_for @form |f|
234
239
  = f.text_field :title
235
240
 
236
- = fields_for :songs do |s|
241
+ = f.fields_for :songs do |s|
237
242
  = s.text_field :title
238
243
  ```
239
244
 
@@ -247,8 +252,8 @@ The block form of `#save` will expose the data structures already discussed.
247
252
  data.songs.first.title #=> "Hungry Like The Wolf"
248
253
 
249
254
  nested #=> {title: "Rio"
250
- songs: [{title: "Hungry Like The Wolf"},
251
- {title: "Last Chance On The Stairways"}]
255
+ # songs: [{title: "Hungry Like The Wolf"},
256
+ # {title: "Last Chance On The Stairways"}]
252
257
  end
253
258
  ```
254
259
 
@@ -304,9 +309,9 @@ Here's how the block parameters look like.
304
309
  data.city #=> "London"
305
310
 
306
311
  nested #=> {
307
- song: {title: "Rio"}
308
- label: {city: "London"}
309
- }
312
+ # song: {title: "Rio"}
313
+ # label: {city: "London"}
314
+ # }
310
315
  end
311
316
  ```
312
317
 
@@ -329,6 +334,58 @@ end
329
334
  data.written_at #=> <DateTime XXX>
330
335
  ```
331
336
 
337
+ ## Virtual Attributes
338
+
339
+ Virtual fields come in handy when there's no direct mapping to a model attribute or when you plan on displaying but not processing a value.
340
+
341
+
342
+ ### Empty Fields
343
+
344
+ Often, fields like `password_confirmation` shouldn't be retrieved from the model. Reform comes with the `:empty` option for that.
345
+
346
+ ```ruby
347
+ class PasswordForm < Reform::Form
348
+ property :password
349
+ property :password_confirmation, :empty => true
350
+ ```
351
+
352
+ Here, the model won't be queried for a `password_confirmation` field when creating and rendering the form. Likewise, when saving the form, the input value is not written to the decorated model. It is only readable in validations and when saving the form.
353
+
354
+ ```ruby
355
+ form.validate("password" => "123", "password_confirmation" => "321")
356
+
357
+ form.password_confirmation #=> "321"
358
+ ```
359
+
360
+ The nested hash in the block-`#save` provides the same value.
361
+
362
+ ```ruby
363
+ form.save do |f, nested|
364
+ nested[:password_confirmation] #=> "321"
365
+ ```
366
+
367
+ ### Read-Only Fields
368
+
369
+ Almost identical, the `:virtual` option makes fields read-only. Say you wanna show a value, but not process it after submission, this option is your friend.
370
+
371
+ ```ruby
372
+ class ProfileForm < Reform::Form
373
+ property :country, :virtual => true
374
+ ```
375
+
376
+ This time reform will query the model for the value by calling `model.country`.
377
+
378
+ You want to use this to display an initial value or to further process this field with JavaScript. However, after submission, the field is no longer considered: it won't be written to the model when saving.
379
+
380
+ It is still readable in the nested hash and through the form itself.
381
+
382
+ ```ruby
383
+ form.save do |f, nested|
384
+ nested[:country] #=> "Australia"
385
+
386
+ f.country #=> "Australia"
387
+ ```
388
+
332
389
 
333
390
  ## Agnosticism: Mapping Data
334
391
 
@@ -348,7 +405,15 @@ Check out [@gogogarret](https://twitter.com/GoGoGarrett/)'s [sample Rails app](h
348
405
  Rails and Reform work out-of-the-box. If you're using Rails but for some reason wanna use the pure reform, `require reform/form`, only.
349
406
 
350
407
 
351
- ## ActiveModel compliance
408
+ ## ActiveRecord Compatibility
409
+
410
+ Reform provides the following `ActiveRecord` specific features. They're mixed in automatically in a Rails/AR setup.
411
+
412
+ * Uniqueness validations. Use `validates_uniqueness_of` in your form.
413
+ * Calling `Form#save` will explicitely call `save` on your model (added in 0.2.1) which will usually trigger a database insertion or update.
414
+
415
+
416
+ ## ActiveModel Compliance
352
417
 
353
418
  Forms in Reform can easily be made ActiveModel-compliant.
354
419
 
@@ -366,7 +431,7 @@ If you're not happy with the `model_name` result, configure it manually.
366
431
  class CoverSongForm < Reform::Form
367
432
  include Reform::Form::ActiveModel
368
433
 
369
- model :hit
434
+ model :song
370
435
  end
371
436
  ```
372
437
 
data/TODO.md CHANGED
@@ -2,6 +2,7 @@
2
2
  with @model.title == "Little Green Car" and validate({}) the form is still valid (as we "have" a valid title). is that what we want?
3
3
 
4
4
  * document Form#to_hash and Form#to_nested_hash (e.g. with OpenStruct composition to make it a very simple form)
5
+ * document getter: and representer_exec:
5
6
 
6
7
  * allow :as to rename nested forms
7
8
  * make #nested_forms easier
Binary file
@@ -0,0 +1,7 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in reform.gemspec
4
+ gemspec :path => '../'
5
+
6
+ gem 'railties', '~> 3.1.0'
7
+ gem 'activerecord', '~> 3.1.0'
@@ -0,0 +1,7 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in reform.gemspec
4
+ gemspec :path => '../'
5
+
6
+ gem 'railties', '~> 3.2.0'
7
+ gem 'activerecord', '~> 3.2.0'
@@ -0,0 +1,7 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in reform.gemspec
4
+ gemspec :path => '../'
5
+
6
+ gem 'railties', '~> 4.0.0'
7
+ gem 'activerecord', '~> 4.0.0'
@@ -17,6 +17,8 @@ module Reform
17
17
  extend Forwardable
18
18
 
19
19
  def property(name, options={}, &block)
20
+ process_options(name, options, &block)
21
+
20
22
  definition = representer_class.property(name, options, &block)
21
23
  setup_form_definition(definition) if block_given?
22
24
  create_accessor(name)
@@ -47,6 +49,9 @@ module Reform
47
49
  def create_accessor(name)
48
50
  delegate [name, "#{name}="] => :fields
49
51
  end
52
+
53
+ def process_options(name, options) # DISCUSS: do we need that hook?
54
+ end
50
55
  end
51
56
  extend PropertyMethods
52
57
 
@@ -89,12 +94,15 @@ module Reform
89
94
  end
90
95
 
91
96
  # Use representer to return current key-value form hash.
92
- def to_hash(*)
93
- mapper.new(self).to_hash
97
+ def to_hash(*args)
98
+ mapper.new(self).to_hash(*args)
94
99
  end
95
100
 
101
+ require "active_support/hash_with_indifferent_access" # DISCUSS: replace?
96
102
  def to_nested_hash
97
- symbolize_keys(to_hash)
103
+ map = mapper.new(self)
104
+
105
+ ActiveSupport::HashWithIndifferentAccess.new(map.to_hash)
98
106
  end
99
107
 
100
108
  def from_hash(params, *args)
@@ -105,76 +113,112 @@ module Reform
105
113
  @errors ||= Errors.new(self)
106
114
  end
107
115
 
108
- private
109
- attr_accessor :model, :fields
116
+ attr_accessor :model
110
117
 
111
- def symbolize_keys(hash)
112
- hash.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
113
- end
118
+ private
119
+ attr_accessor :fields
114
120
 
115
121
  def mapper
116
122
  self.class.representer_class
117
123
  end
118
124
 
119
125
  def setup_fields(model)
120
- representer = Class.new(mapper).new(model)
121
-
122
- setup_nested_forms(representer)
126
+ representer = mapper.new(model).extend(Setup::Representer)
123
127
 
124
128
  create_fields(representer.fields, representer.to_hash)
125
129
  end
126
130
 
127
- def setup_nested_forms(representer)
128
- # TODO: we should simply give a FormBuilder instance to representer.to_hash that does this kind of mapping:
129
- # after this, Fields contains scalars and Form instances and Forms with form instances.
130
- representer.nested_forms do |attr, model|
131
- form_class = attr.options[:form]
132
-
133
- attr.options.merge!(
134
- :getter => lambda do |*|
135
- nested_model = send(attr.getter) # decorated.hit # TODO: use bin.get
131
+ #representer.to_hash override: { write: lambda { |doc, value| } }
136
132
 
137
- if attr.options[:form_collection]
138
- Forms.new(nested_model.collect { |mdl| form_class.new(mdl)})
139
- else
140
- form_class.new(nested_model)
141
- end
142
- end,
143
- :instance => false, # that's how we make it non-typed?.
144
- )
145
- end
133
+ # DISCUSS: this would be cool in representable:
134
+ # to_hash(hit: lambda { |value| form_class.new(..) })
146
135
 
147
- #representer.to_hash override: { write: lambda { |doc, value| } }
136
+ # steps:
137
+ # - bin.get
138
+ # - map that: Forms.new( orig ) <-- override only this in representable (how?)
139
+ # - mapped.to_hash
148
140
 
149
- # DISCUSS: this would be cool in representable:
150
- # to_hash(hit: lambda { |value| form_class.new(..) })
151
-
152
- # steps:
153
- # - bin.get
154
- # - map that: Forms.new( orig ) <-- override only this in representable (how?)
155
- # - mapped.to_hash
156
- end
157
141
 
158
142
  def create_fields(field_names, fields)
159
143
  Fields.new(field_names, fields)
160
144
  end
161
145
 
162
- def save_to_models
163
- representer = mapper.new(model)
164
146
 
165
- representer.nested_forms do |attr, model|
166
- attr.options.merge!(
167
- :decorator => attr.options[:form].representer_class
168
- )
147
+ require "reform/form/virtual_attributes"
148
+
149
+ # Mechanics for setting up initial Field values.
150
+ module Setup
151
+ module Representer
152
+ include Reform::Representer::WithOptions
153
+ include EmptyAttributesOptions
169
154
 
170
- if attr.options[:form_collection]
171
- attr.options.merge!(
172
- :collection => true
173
- )
155
+ def to_hash(*)
156
+ setup_nested_forms
157
+
158
+ super # TODO: allow something like super(:exclude => empty_fields)
159
+ end
160
+
161
+ private
162
+ def setup_nested_forms
163
+ nested_forms do |attr, model|
164
+ form_class = attr.options[:form]
165
+
166
+ attr.options.merge!(
167
+ :getter => lambda do |*|
168
+ nested_model = send(attr.getter) # decorated.hit # TODO: use bin.get
169
+
170
+ if attr.options[:form_collection]
171
+ Forms.new(nested_model.collect { |mdl| form_class.new(mdl)})
172
+ else
173
+ form_class.new(nested_model)
174
+ end
175
+ end,
176
+ :instance => false, # that's how we make it non-typed?.
177
+ )
178
+ end
179
+ end
180
+ end
181
+ end
182
+
183
+ # Mechanics for writing input to model.
184
+ module Sync
185
+ # Writes input to model.
186
+ module Representer
187
+ def from_hash(*)
188
+ nested_forms do |attr, model|
189
+ attr.options.merge!(
190
+ :decorator => attr.options[:form].representer_class
191
+ )
192
+
193
+ if attr.options[:form_collection]
194
+ attr.options.merge!(
195
+ :collection => true
196
+ )
197
+ end
198
+ end
199
+
200
+ super
174
201
  end
175
202
  end
176
203
 
177
- representer.from_hash(to_hash)
204
+ # Transforms form input into what actually gets written to model.
205
+ module InputRepresenter
206
+ include Reform::Representer::WithOptions
207
+ # TODO: make dynamic.
208
+ include EmptyAttributesOptions
209
+ include ReadonlyAttributesOptions
210
+ end
211
+ end
212
+
213
+
214
+ def save_to_models # TODO: rename to #sync_models
215
+ representer = mapper.new(model)
216
+
217
+ representer.extend(Sync::Representer)
218
+
219
+ input_representer = mapper.new(self).extend(Sync::InputRepresenter)
220
+
221
+ representer.from_hash(input_representer.to_hash)
178
222
  end
179
223
 
180
224
  # FIXME: make AM optional.
@@ -11,24 +11,40 @@ class Reform::Form
11
11
  def validates_uniqueness_of(attribute)
12
12
  validates_with UniquenessValidator, :attributes => [attribute]
13
13
  end
14
+ def i18n_scope
15
+ :activerecord
16
+ end
14
17
  end
15
18
 
16
19
  class UniquenessValidator < ::ActiveRecord::Validations::UniquenessValidator
17
20
  # when calling validates it should create the Vali instance already and set @klass there! # TODO: fix this in AM.
18
21
  def validate(form)
19
22
  property = attributes.first
20
- #model_name = form.send(:model).class.model_for_property(property)
21
23
 
22
24
  # here is the thing: why does AM::UniquenessValidator require a filled-out record to work properly? also, why do we need to set
23
25
  # the class? it would be way easier to pass #validate a hash of attributes and get back an errors hash.
24
26
  # the class for the finder could either be infered from the record or set in the validator instance itself in the call to ::validates.
25
- record = form.send(:model)
27
+ record = form.model_for_property(property)
26
28
  record.send("#{property}=", form.send(property))
29
+
27
30
  @klass = record.class # this is usually done in the super-sucky #setup method.
28
31
  super(record).tap do |res|
29
32
  form.errors.add(property, record.errors.first.last) if record.errors.present?
30
33
  end
31
34
  end
32
35
  end
36
+
37
+ def save(*)
38
+ super.tap do
39
+ model.save # DISCUSS: should we implement nested saving here?
40
+ end
41
+ end
42
+
43
+ def model_for_property(name)
44
+ return model unless is_a?(Reform::Form::Composition) # i am too lazy for proper inheritance. there should be a ActiveRecord::Composition that handles this.
45
+
46
+ model_name = mapper.representable_attrs[name].options[:on]
47
+ send(model_name)
48
+ end
33
49
  end
34
50
  end
@@ -0,0 +1,24 @@
1
+ module Reform
2
+ class Form
3
+ # TODO: this should be in Representer namespace.
4
+ module EmptyAttributesOptions
5
+ def options
6
+ empty_fields = representable_attrs.
7
+ find_all { |d| d.options[:empty] }.
8
+ collect { |d| d.name.to_sym }
9
+
10
+ super.exclude!(empty_fields)
11
+ end
12
+ end
13
+
14
+ module ReadonlyAttributesOptions
15
+ def options
16
+ readonly_fields = representable_attrs.
17
+ find_all { |d| d.options[:virtual] }.
18
+ collect { |d| d.name.to_sym }
19
+
20
+ super.exclude!(readonly_fields)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -3,6 +3,36 @@ require 'representable/decorator'
3
3
 
4
4
  module Reform
5
5
  class Representer < Representable::Decorator
6
+ # Invokes #to_hash and/or #from_hash with #options. This provides a hook for other
7
+ # modules to add options for the representational process.
8
+ module WithOptions
9
+ class Options < Hash
10
+ def include!(names)
11
+ self[:include] ||= []
12
+ self[:include] += names
13
+ self
14
+ end
15
+
16
+ def exclude!(names)
17
+ self[:exclude] ||= []
18
+ self[:exclude] += names
19
+ self
20
+ end
21
+ end
22
+
23
+ def options
24
+ Options.new
25
+ end
26
+
27
+ def to_hash(*)
28
+ super(options)
29
+ end
30
+
31
+ def from_hash(*)
32
+ super(options)
33
+ end
34
+ end
35
+
6
36
  include Representable::Hash
7
37
 
8
38
  # Returns hash of all property names.
@@ -1,3 +1,3 @@
1
1
  module Reform
2
- VERSION = "0.2.0"
2
+ VERSION = "0.2.1"
3
3
  end
@@ -0,0 +1,67 @@
1
+ require 'test_helper'
2
+
3
+ class ActiveRecordTest < MiniTest::Spec
4
+ let (:form) do
5
+ require 'reform/active_record'
6
+ Class.new(Reform::Form) do
7
+ include Reform::Form::ActiveRecord
8
+ model :artist
9
+
10
+ property :name
11
+ property :created_at
12
+
13
+ validates_uniqueness_of :name
14
+ validates :created_at, :presence => true # have another property to test if we mix up.
15
+ end.
16
+ new(Artist.new)
17
+ end
18
+
19
+ it { form.class.i18n_scope.must_equal :activerecord }
20
+
21
+ describe "UniquenessValidator" do
22
+ # ActiveRecord::Schema.define do
23
+ # create_table :artists do |table|
24
+ # table.column :name, :string
25
+ # table.timestamps
26
+ # end
27
+ # end
28
+ # Artist.new(:name => "Racer X").save
29
+
30
+ it "allows accessing the database" do
31
+ end
32
+
33
+ it "is valid when name is unique" do
34
+ form.validate({"name" => "Paul Gilbert", "created_at" => "November 6, 1966"}).must_equal true
35
+ end
36
+
37
+ it "is invalid and shows error when taken" do
38
+ Artist.create(:name => "Racer X")
39
+
40
+ form.validate({"name" => "Racer X"}).must_equal false
41
+ form.errors.messages.must_equal({:name=>["has already been taken"], :created_at => ["can't be blank"]})
42
+ end
43
+
44
+ it "works with Composition" do
45
+ form = Class.new(Reform::Form) do
46
+ include Reform::Form::ActiveRecord
47
+ include Reform::Form::Composition
48
+
49
+ property :name, :on => :artist
50
+ validates_uniqueness_of :name
51
+ end.new(:artist => Artist.new)
52
+
53
+ Artist.create(:name => "Bad Religion")
54
+ form.validate("name" => "Bad Religion").must_equal false
55
+ end
56
+ end
57
+
58
+ describe "#save" do
59
+ # TODO: test 1-n?
60
+ it "calls AR#save" do
61
+ Artist.delete_all
62
+ form.validate("name" => "Bad Religion")
63
+ form.save
64
+ Artist.where(:name => "Bad Religion").size.must_equal 1
65
+ end
66
+ end
67
+ end
@@ -82,14 +82,19 @@ class NestedFormTest < MiniTest::Spec
82
82
  frm.songs.first.must_be_kind_of Reform::Form
83
83
  end
84
84
 
85
- it "returns nested hash with symbol keys" do
85
+ it "returns nested hash with indifferent access" do
86
86
  nested = nil
87
87
 
88
88
  form.save do |hash, nested_hash|
89
89
  nested = nested_hash
90
90
  end
91
91
 
92
- nested.must_equal(:title=>"Second Heat", :hit=>{"title"=>"Sacrifice"}, :songs=>[{"title"=>"Scarified"}])
92
+ nested.must_equal("title"=>"Second Heat", "hit"=>{"title"=>"Sacrifice"}, "songs"=>[{"title"=>"Scarified"}])
93
+
94
+ nested[:title].must_equal "Second Heat"
95
+ nested["title"].must_equal "Second Heat"
96
+ nested[:hit][:title].must_equal "Sacrifice"
97
+ nested["hit"]["title"].must_equal "Sacrifice"
93
98
  end
94
99
 
95
100
  it "pushes data to models" do
@@ -15,6 +15,19 @@ class RepresenterTest < MiniTest::Spec
15
15
  end
16
16
  end
17
17
 
18
+ class WithOptionsTest < MiniTest::Spec
19
+ subject { Reform::Representer::WithOptions::Options.new }
20
+
21
+ it { subject.must_equal({}) }
22
+ it { subject.exclude!([:id, :title]).must_equal(:exclude => [:id, :title]) }
23
+ it do
24
+ subject.exclude!([:id, :title])
25
+ subject.exclude!([:id, :name])
26
+ subject.must_equal(:exclude => [:id, :title, :id, :name])
27
+ end
28
+ it { subject.include!([:id, :title]).must_equal(:include => [:id, :title]) }
29
+ end
30
+
18
31
  class FieldsTest < MiniTest::Spec
19
32
  describe "#new" do
20
33
  it "accepts list of properties" do
@@ -145,42 +158,6 @@ class ReformTest < ReformSpec
145
158
  end
146
159
  end
147
160
 
148
- describe "UniquenessValidator" do
149
- # ActiveRecord::Schema.define do
150
- # create_table :artists do |table|
151
- # table.column :name, :string
152
- # table.timestamps
153
- # end
154
- # end
155
- # Artist.new(:name => "Racer X").save
156
-
157
- let (:form) do
158
- require 'reform/active_record'
159
- Class.new(Reform::Form) do
160
- include Reform::Form::ActiveRecord
161
- model :artist
162
-
163
- property :name
164
- property :created_at
165
-
166
- validates_uniqueness_of :name
167
- validates :created_at, :presence => true # have another property to test if we mix up.
168
- end.
169
- new(Artist.new)
170
- end
171
-
172
- it "allows accessing the database" do
173
- end
174
-
175
- it "is valid when name is unique" do
176
- form.validate({"name" => "Paul Gilbert", "created_at" => "November 6, 1966"}).must_equal true
177
- end
178
-
179
- it "is invalid and shows error when taken" do
180
- form.validate({"name" => "Racer X"}).must_equal false
181
- form.errors.messages.must_equal({:name=>["has already been taken"], :created_at => ["can't be blank"]})
182
- end
183
- end
184
161
  end
185
162
 
186
163
  describe "#errors" do
@@ -227,13 +204,72 @@ class ReformTest < ReformSpec
227
204
  hash = map
228
205
  end
229
206
 
230
- hash.must_equal({:name=>"Diesel Boy"})
207
+ hash.must_equal({"name"=>"Diesel Boy"})
231
208
  end
232
209
  end
233
210
  end
234
211
 
235
212
 
236
213
  describe "#model" do
237
- it { form.send(:model).must_equal comp }
214
+ it { form.model.must_equal comp }
215
+ end
216
+ end
217
+
218
+ class EmptyAttributesTest < MiniTest::Spec
219
+ Credentials = Struct.new(:password)
220
+
221
+ class PasswordForm < Reform::Form
222
+ property :password
223
+ property :password_confirmation, :empty => true
238
224
  end
225
+
226
+ let (:cred) { Credentials.new }
227
+ let (:form) { PasswordForm.new(cred) }
228
+
229
+ it { form }
230
+
231
+ it {
232
+
233
+ form.validate("password" => "123", "password_confirmation" => "321")
234
+ form.password.must_equal "123"
235
+ form.password_confirmation.must_equal "321"
236
+
237
+ form.save
238
+ cred.password.must_equal "123"
239
+
240
+ hash = {}
241
+ form.save do |f, nested|
242
+ hash = nested
243
+ end
244
+
245
+ hash.must_equal("password"=> "123", "password_confirmation" => "321")
246
+ }
239
247
  end
248
+
249
+ class ReadonlyAttributesTest < MiniTest::Spec
250
+ Location = Struct.new(:country)
251
+
252
+ class LocationForm < Reform::Form
253
+ property :country, :virtual => true # read_only: true
254
+ end
255
+
256
+ let (:loc) { Location.new("Australia") }
257
+ let (:form) { LocationForm.new(loc) }
258
+
259
+ it { form.country.must_equal "Australia" }
260
+ it do
261
+ form.validate("country" => "Germany") # this usually won't change when submitting.
262
+ form.country.must_equal "Germany"
263
+
264
+
265
+ form.save
266
+ loc.country.must_equal "Australia" # the writer wasn't called.
267
+
268
+ hash = {}
269
+ form.save do |f, nested|
270
+ hash = nested
271
+ end
272
+
273
+ hash.must_equal("country"=> "Germany")
274
+ end
275
+ end
@@ -12,4 +12,6 @@ end
12
12
  ActiveRecord::Base.establish_connection(
13
13
  :adapter => "sqlite3",
14
14
  :database => "#{Dir.pwd}/database.sqlite3"
15
- )
15
+ )
16
+
17
+ #Artist.delete_all
metadata CHANGED
@@ -1,8 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reform
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
5
- prerelease:
4
+ version: 0.2.1
6
5
  platform: ruby
7
6
  authors:
8
7
  - Nick Sutterer
@@ -10,12 +9,11 @@ authors:
10
9
  autorequire:
11
10
  bindir: bin
12
11
  cert_chain: []
13
- date: 2013-09-15 00:00:00.000000000 Z
12
+ date: 2013-10-30 00:00:00.000000000 Z
14
13
  dependencies:
15
14
  - !ruby/object:Gem::Dependency
16
15
  name: representable
17
16
  requirement: !ruby/object:Gem::Requirement
18
- none: false
19
17
  requirements:
20
18
  - - ~>
21
19
  - !ruby/object:Gem::Version
@@ -23,7 +21,6 @@ dependencies:
23
21
  type: :runtime
24
22
  prerelease: false
25
23
  version_requirements: !ruby/object:Gem::Requirement
26
- none: false
27
24
  requirements:
28
25
  - - ~>
29
26
  - !ruby/object:Gem::Version
@@ -31,23 +28,20 @@ dependencies:
31
28
  - !ruby/object:Gem::Dependency
32
29
  name: activemodel
33
30
  requirement: !ruby/object:Gem::Requirement
34
- none: false
35
31
  requirements:
36
- - - ! '>='
32
+ - - '>='
37
33
  - !ruby/object:Gem::Version
38
34
  version: '0'
39
35
  type: :runtime
40
36
  prerelease: false
41
37
  version_requirements: !ruby/object:Gem::Requirement
42
- none: false
43
38
  requirements:
44
- - - ! '>='
39
+ - - '>='
45
40
  - !ruby/object:Gem::Version
46
41
  version: '0'
47
42
  - !ruby/object:Gem::Dependency
48
43
  name: bundler
49
44
  requirement: !ruby/object:Gem::Requirement
50
- none: false
51
45
  requirements:
52
46
  - - ~>
53
47
  - !ruby/object:Gem::Version
@@ -55,7 +49,6 @@ dependencies:
55
49
  type: :development
56
50
  prerelease: false
57
51
  version_requirements: !ruby/object:Gem::Requirement
58
- none: false
59
52
  requirements:
60
53
  - - ~>
61
54
  - !ruby/object:Gem::Version
@@ -63,97 +56,85 @@ dependencies:
63
56
  - !ruby/object:Gem::Dependency
64
57
  name: rake
65
58
  requirement: !ruby/object:Gem::Requirement
66
- none: false
67
59
  requirements:
68
- - - ! '>='
60
+ - - '>='
69
61
  - !ruby/object:Gem::Version
70
62
  version: 10.1.0
71
63
  type: :development
72
64
  prerelease: false
73
65
  version_requirements: !ruby/object:Gem::Requirement
74
- none: false
75
66
  requirements:
76
- - - ! '>='
67
+ - - '>='
77
68
  - !ruby/object:Gem::Version
78
69
  version: 10.1.0
79
70
  - !ruby/object:Gem::Dependency
80
71
  name: minitest
81
72
  requirement: !ruby/object:Gem::Requirement
82
- none: false
83
73
  requirements:
84
- - - ! '>='
74
+ - - '>='
85
75
  - !ruby/object:Gem::Version
86
76
  version: '0'
87
77
  type: :development
88
78
  prerelease: false
89
79
  version_requirements: !ruby/object:Gem::Requirement
90
- none: false
91
80
  requirements:
92
- - - ! '>='
81
+ - - '>='
93
82
  - !ruby/object:Gem::Version
94
83
  version: '0'
95
84
  - !ruby/object:Gem::Dependency
96
85
  name: activerecord
97
86
  requirement: !ruby/object:Gem::Requirement
98
- none: false
99
87
  requirements:
100
- - - ! '>='
88
+ - - '>='
101
89
  - !ruby/object:Gem::Version
102
90
  version: '0'
103
91
  type: :development
104
92
  prerelease: false
105
93
  version_requirements: !ruby/object:Gem::Requirement
106
- none: false
107
94
  requirements:
108
- - - ! '>='
95
+ - - '>='
109
96
  - !ruby/object:Gem::Version
110
97
  version: '0'
111
98
  - !ruby/object:Gem::Dependency
112
99
  name: sqlite3
113
100
  requirement: !ruby/object:Gem::Requirement
114
- none: false
115
101
  requirements:
116
- - - ! '>='
102
+ - - '>='
117
103
  - !ruby/object:Gem::Version
118
104
  version: '0'
119
105
  type: :development
120
106
  prerelease: false
121
107
  version_requirements: !ruby/object:Gem::Requirement
122
- none: false
123
108
  requirements:
124
- - - ! '>='
109
+ - - '>='
125
110
  - !ruby/object:Gem::Version
126
111
  version: '0'
127
112
  - !ruby/object:Gem::Dependency
128
113
  name: virtus
129
114
  requirement: !ruby/object:Gem::Requirement
130
- none: false
131
115
  requirements:
132
- - - ! '>='
116
+ - - '>='
133
117
  - !ruby/object:Gem::Version
134
118
  version: '0'
135
119
  type: :development
136
120
  prerelease: false
137
121
  version_requirements: !ruby/object:Gem::Requirement
138
- none: false
139
122
  requirements:
140
- - - ! '>='
123
+ - - '>='
141
124
  - !ruby/object:Gem::Version
142
125
  version: '0'
143
126
  - !ruby/object:Gem::Dependency
144
127
  name: rails
145
128
  requirement: !ruby/object:Gem::Requirement
146
- none: false
147
129
  requirements:
148
- - - ! '>='
130
+ - - '>='
149
131
  - !ruby/object:Gem::Version
150
132
  version: '0'
151
133
  type: :development
152
134
  prerelease: false
153
135
  version_requirements: !ruby/object:Gem::Requirement
154
- none: false
155
136
  requirements:
156
- - - ! '>='
137
+ - - '>='
157
138
  - !ruby/object:Gem::Version
158
139
  version: '0'
159
140
  description: Freeing your AR models from form logic.
@@ -174,6 +155,9 @@ files:
174
155
  - TODO.md
175
156
  - database.sqlite3
176
157
  - gemfiles/Gemfile.rails-3.0
158
+ - gemfiles/Gemfile.rails-3.1
159
+ - gemfiles/Gemfile.rails-3.2
160
+ - gemfiles/Gemfile.rails-4.0
177
161
  - lib/reform.rb
178
162
  - lib/reform/active_record.rb
179
163
  - lib/reform/composition.rb
@@ -182,11 +166,13 @@ files:
182
166
  - lib/reform/form/active_record.rb
183
167
  - lib/reform/form/coercion.rb
184
168
  - lib/reform/form/composition.rb
169
+ - lib/reform/form/virtual_attributes.rb
185
170
  - lib/reform/rails.rb
186
171
  - lib/reform/representer.rb
187
172
  - lib/reform/version.rb
188
173
  - reform.gemspec
189
174
  - test/active_model_test.rb
175
+ - test/active_record_test.rb
190
176
  - test/coercion_test.rb
191
177
  - test/composition_test.rb
192
178
  - test/dummy/Rakefile
@@ -221,31 +207,31 @@ files:
221
207
  homepage: ''
222
208
  licenses:
223
209
  - MIT
210
+ metadata: {}
224
211
  post_install_message:
225
212
  rdoc_options: []
226
213
  require_paths:
227
214
  - lib
228
215
  required_ruby_version: !ruby/object:Gem::Requirement
229
- none: false
230
216
  requirements:
231
- - - ! '>='
217
+ - - '>='
232
218
  - !ruby/object:Gem::Version
233
219
  version: '0'
234
220
  required_rubygems_version: !ruby/object:Gem::Requirement
235
- none: false
236
221
  requirements:
237
- - - ! '>='
222
+ - - '>='
238
223
  - !ruby/object:Gem::Version
239
224
  version: '0'
240
225
  requirements: []
241
226
  rubyforge_project:
242
- rubygems_version: 1.8.25
227
+ rubygems_version: 2.0.3
243
228
  signing_key:
244
- specification_version: 3
229
+ specification_version: 4
245
230
  summary: Decouples your models from form by giving you form objects with validation,
246
231
  presentation, workflows and security.
247
232
  test_files:
248
233
  - test/active_model_test.rb
234
+ - test/active_record_test.rb
249
235
  - test/coercion_test.rb
250
236
  - test/composition_test.rb
251
237
  - test/dummy/Rakefile