reform 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -4,6 +4,7 @@
4
4
  .config
5
5
  .yardoc
6
6
  Gemfile.lock
7
+ gemfiles/*.lock
7
8
  InstalledFiles
8
9
  _yardoc
9
10
  coverage
data/.travis.yml CHANGED
@@ -3,5 +3,6 @@ rvm:
3
3
  - 2.0.0
4
4
  - 1.9.3
5
5
  - 1.8.7
6
- - rbx-19mode
7
- - jruby-19mode
6
+ gemfile:
7
+ - Gemfile
8
+ - gemfiles/Gemfile.rails-3.0
data/CHANGES.md CHANGED
@@ -1,3 +1,8 @@
1
+ h3. 0.1.2
2
+
3
+ * `Form#to_model` is now delegated to model.
4
+ * Coercion with virtus works.
5
+
1
6
  h3. 0.1.1
2
7
 
3
8
  * Added `reform/rails` that requires everything you need (even in other frameworks :).
data/Gemfile CHANGED
@@ -2,3 +2,6 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in reform.gemspec
4
4
  gemspec
5
+
6
+ gem 'rake', "10.1.0.beta3"
7
+ #gem 'representable', path: "../representable"
data/README.md CHANGED
@@ -100,6 +100,26 @@ To push the incoming data to the models directly, call `#save` without the block
100
100
  # by calling @form.song.name= and @form.artist.title=.
101
101
  ```
102
102
 
103
+ ## Coercion
104
+
105
+ Often you want incoming form data to be converted to a type, like timestamps. Reform uses [virtus](https://github.com/solnic/virtus) for coercion, the DSL is seamlessly integrated into Reform with the `:type` option.
106
+
107
+ Be sure to add `virtus` to your Gemfile.
108
+
109
+ ```ruby
110
+ require 'reform/form/coercion'
111
+
112
+ class SongRequestForm < Reform::Form
113
+ include DSL
114
+ include Reform::Form::Coercion
115
+
116
+ property :written_at, on: :song, type: DateTime
117
+ end
118
+
119
+ @form.save do |data, nested|
120
+ data.written_at #=> <DateTime XXX>
121
+ ```
122
+
103
123
  ## Rails Integration
104
124
 
105
125
  [A sample Rails app using Reform.](https://github.com/gogogarrett/reform_example)
@@ -121,7 +141,7 @@ class UserProfileForm < Reform::Form
121
141
  property :email, on: :user
122
142
  properties [:gender, :age], on: :profile
123
143
 
124
- model :user, on: :user
144
+ model :user
125
145
 
126
146
  validates :email, :gender, presence: true
127
147
  validates :age, numericality: true
@@ -129,6 +149,12 @@ class UserProfileForm < Reform::Form
129
149
  end
130
150
  ```
131
151
 
152
+ Basically, `model :user` tells Reform to use the `:user` object in the composition as the form main object while using `"user"` as the form name (needed for URL computation). If you want to change the form name let Reform know.
153
+
154
+ ```ruby
155
+ model :singer, :on => :user # form name is "singer" whereas main object is `:user` in composition.
156
+ ```
157
+
132
158
 
133
159
  #### View Form
134
160
 
@@ -190,3 +216,13 @@ end
190
216
  ## Security
191
217
 
192
218
  By explicitely defining the form layout using `::property` there is no more need for protecting from unwanted input. `strong_parameter` or `attr_accessible` become obsolete. Reform will simply ignore undefined incoming parameters.
219
+
220
+ ## Maintainers
221
+
222
+ [Nick Sutterer](https://github.com/apotonick)
223
+
224
+ [Garrett Heinlen](https://github.com/gogogarrett)
225
+
226
+ ### Attributions!!!
227
+
228
+ Great thanks to [Blake Education](https://github.com/blake-education) for giving us the freedom and time to develop this project while working on their project.
data/TODO.md CHANGED
@@ -1,2 +1,4 @@
1
1
  * `validates :title, :presence => true`
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?
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
+
4
+ * document Form#to_hash and Form#to_nested_hash (e.g. with OpenStruct composition to make it a very simple form)
@@ -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.0.11'
7
+ gem 'activerecord', '~> 3.0.11'
data/lib/reform/form.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'delegate'
2
+ require 'ostruct'
2
3
 
3
4
  module Reform
4
5
  class Form < SimpleDelegator
@@ -8,12 +9,11 @@ module Reform
8
9
  # validation: this object also contains the validation rules itself, should be separated.
9
10
  # TODO: figure out #to_key issues.
10
11
 
11
- def initialize(mapper, composition)
12
- @mapper = mapper
12
+ def initialize(mapper_class, composition)
13
+ @mapper = mapper_class
13
14
  @model = composition
14
- representer = @mapper.new(composition)
15
15
 
16
- super Fields.new(representer.fields, representer.to_hash) # decorate composition and transform to hash.
16
+ super(setup_fields(mapper_class, composition)) # delegate all methods to Fields instance.
17
17
  end
18
18
 
19
19
  def validate(params)
@@ -30,13 +30,6 @@ module Reform
30
30
  @mapper.new(model).from_hash(to_hash) # DISCUSS: move to Composition?
31
31
  end
32
32
 
33
- private
34
- attr_accessor :mapper, :model
35
-
36
- def update_with(params)
37
- mapper.new(self).from_hash(params) # sets form properties found in params on self.
38
- end
39
-
40
33
  # Use representer to return current key-value form hash.
41
34
  def to_hash
42
35
  mapper.new(self).to_hash
@@ -46,6 +39,24 @@ module Reform
46
39
  model.nested_hash_for(to_hash) # use composition to compute nested hash.
47
40
  end
48
41
 
42
+ private
43
+ attr_accessor :mapper, :model
44
+
45
+ def setup_fields(mapper_class, composition)
46
+ # decorate composition and transform to hash.
47
+ representer = mapper_class.new(composition)
48
+
49
+ create_fields(representer.fields, representer.to_hash)
50
+ end
51
+
52
+ def create_fields(field_names, fields)
53
+ Fields.new(field_names, fields)
54
+ end
55
+
56
+ def update_with(params)
57
+ mapper.new(self).from_hash(params) # sets form properties found in params on self.
58
+ end
59
+
49
60
  # FIXME: make AM optional.
50
61
  require 'active_model'
51
62
  include ActiveModel::Validations
@@ -6,14 +6,14 @@ module Reform::Form::ActiveModel
6
6
  end
7
7
 
8
8
  module ClassMethods
9
- def model(*args)
10
- @model_options = args # FIXME: make inheritable!
11
- main_model = args.last[:on]
9
+ def model(main_model, options={})
10
+ @model_options = [main_model, options] # FIXME: make inheritable!
11
+ composition_model = options[:on] || main_model
12
12
 
13
- delegate main_model, :to => :model # #song => model.song
14
- delegate :persisted?, :to_key, :to_param, :to => main_model # #to_key => song.to_key
13
+ delegate composition_model, :to => :model # #song => model.song
14
+ delegate :persisted?, :to_key, :to_param, :to_model, :to => composition_model # #to_key => song.to_key
15
15
 
16
- alias_method args.first, main_model # #hit => model.song.
16
+ alias_method main_model, composition_model # #hit => model.song.
17
17
  end
18
18
 
19
19
  def property(name, options={})
@@ -22,7 +22,10 @@ module Reform::Form::ActiveModel
22
22
  end
23
23
 
24
24
  def model_name
25
- ::ActiveModel::Name.new(self, nil, @model_options.first.to_s.camelize)
25
+ name = @model_options.first.to_s.camelize
26
+
27
+ return ::ActiveModel::Name.new(OpenStruct.new(:name => name)) if ::ActiveModel::VERSION::MAJOR == 3 and ::ActiveModel::VERSION::MINOR == 0
28
+ ::ActiveModel::Name.new(self, nil, name)
26
29
  end
27
30
  end
28
31
  end
@@ -0,0 +1,18 @@
1
+ require 'representable/decorator/coercion'
2
+
3
+ class Reform::Form
4
+ module Coercion
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ end
8
+
9
+ module ClassMethods
10
+ def representer_class
11
+ super.class_eval do
12
+ include Representable::Decorator::Coercion unless self < Representable::Decorator::Coercion # DISCUSS: include it once. why do we have to check this?
13
+ self
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -1,3 +1,3 @@
1
1
  module Reform
2
- VERSION = "0.1.1"
2
+ VERSION = "0.1.2"
3
3
  end
data/reform.gemspec CHANGED
@@ -18,11 +18,12 @@ 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 "representable"
21
+ spec.add_dependency "representable", ">= 1.5.3"
22
22
  spec.add_dependency "activemodel"
23
23
  spec.add_development_dependency "bundler", "~> 1.3"
24
24
  spec.add_development_dependency "rake"
25
25
  spec.add_development_dependency "minitest"
26
26
  spec.add_development_dependency "activerecord"
27
27
  spec.add_development_dependency "sqlite3"
28
+ spec.add_development_dependency "virtus"
28
29
  end
@@ -15,10 +15,24 @@ class ActiveModelTest < MiniTest::Spec
15
15
  let (:duran) { OpenStruct.new }
16
16
  let (:form) { HitForm.new(:song => rio, :artist => duran) }
17
17
 
18
- it "creates model readers" do
19
- form.hit.must_equal rio
18
+ describe "main form reader #hit" do
19
+ it "delegates to :on model" do
20
+ form.hit.must_equal rio
21
+ end
22
+
23
+ it "doesn't delegate when :on missing" do
24
+ class HitForm < Reform::Form
25
+ include DSL
26
+ include Reform::Form::ActiveModel
27
+
28
+ property :title, :on => :song
29
+
30
+ model :song
31
+ end.new(:song => rio, :artist => duran).song.must_equal rio
32
+ end
20
33
  end
21
34
 
35
+
22
36
  it "creates composition readers" do
23
37
  form.song.must_equal rio
24
38
  form.artist.must_equal duran
@@ -40,6 +54,10 @@ class ActiveModelTest < MiniTest::Spec
40
54
  HitForm.new(:song => OpenStruct.new.instance_eval { def to_param; "yo!"; end; self }, :artist => OpenStruct.new).to_param.must_equal "yo!"
41
55
  end
42
56
 
57
+ it "provides #to_model" do
58
+ HitForm.new(:song => OpenStruct.new.instance_eval { def to_model; "yo!"; end; self }, :artist => OpenStruct.new).to_model.must_equal "yo!"
59
+ end
60
+
43
61
  it "works with any order of ::model and ::property" do
44
62
  class AnotherForm < Reform::Form
45
63
  include DSL
data/test/dsl_test.rb CHANGED
@@ -15,4 +15,29 @@ class DslTest < MiniTest::Spec
15
15
  it "works by creating Representer and Composition for you" do
16
16
  form.validate("title" => "Greyhound", "name" => "Frenzal Rhomb").must_equal false
17
17
  end
18
+
19
+ require 'reform/form/coercion'
20
+ it "allows coercion" do
21
+ form = Class.new(Reform::Form) do
22
+ include Reform::Form::DSL
23
+ include Reform::Form::Coercion
24
+
25
+ property :written_at, :type => DateTime, :on => :song
26
+ end.new(:song => OpenStruct.new(:written_at => "31/03/1981"))
27
+
28
+ form.written_at.must_be_kind_of DateTime
29
+ form.written_at.must_equal DateTime.parse("Tue, 31 Mar 1981 00:00:00 +0000")
30
+ end
31
+
32
+ it "allows coercion in validate" do
33
+ form = Class.new(Reform::Form) do
34
+ include Reform::Form::DSL
35
+ include Reform::Form::Coercion
36
+
37
+ property :id, :type => Integer, :on => :song
38
+ end.new(:song => OpenStruct.new())
39
+
40
+ form.validate("id" => "1")
41
+ form.to_hash.must_equal("id" => 1)
42
+ end
18
43
  end
data/test/reform_test.rb CHANGED
@@ -45,6 +45,7 @@ class FieldsTest < MiniTest::Spec
45
45
  end
46
46
 
47
47
  it "processes value syms" do
48
+ skip "we don't need to test this as representer.to_hash always returns strings"
48
49
  fields = Reform::Fields.new(["name", "title"], :title => "The Body")
49
50
  fields.name.must_equal nil
50
51
  fields.title.must_equal "The Body"
@@ -53,6 +54,12 @@ class FieldsTest < MiniTest::Spec
53
54
  end
54
55
 
55
56
  class ReformTest < MiniTest::Spec
57
+ def errors_for(form)
58
+ errors = form.errors
59
+ errors = errors.messages unless ::ActiveModel::VERSION::MAJOR == 3 and ::ActiveModel::VERSION::MINOR == 0
60
+ errors
61
+ end
62
+
56
63
  let (:duran) { OpenStruct.new(:name => "Duran Duran") }
57
64
  let (:rio) { OpenStruct.new(:title => "Rio") }
58
65
 
@@ -174,7 +181,7 @@ class ReformTest < MiniTest::Spec
174
181
 
175
182
  it "populates errors" do
176
183
  form.validate({})
177
- form.errors.messages.must_equal({:name=>["can't be blank"], :title=>["can't be blank"]})
184
+ errors_for(form).must_equal({:name=>["can't be blank"], :title=>["can't be blank"]})
178
185
  end
179
186
  end
180
187
 
@@ -189,7 +196,7 @@ class ReformTest < MiniTest::Spec
189
196
  end.new(SongAndArtistMap, comp)
190
197
 
191
198
  form.validate({}).must_equal false
192
- form.errors.messages.must_equal({:name=>["Please give me a name"]})
199
+ errors_for(form).must_equal({:name=>["Please give me a name"]})
193
200
  end
194
201
  end
195
202
 
@@ -212,7 +219,7 @@ class ReformTest < MiniTest::Spec
212
219
  it "is invalid and shows error when taken" do
213
220
  form = ActiveRecordForm.new(SongAndArtistMap, comp)
214
221
  form.validate({"name" => "Racer X"}).must_equal false
215
- form.errors.messages.must_equal({:name=>["has already been taken"], :title => ["can't be blank"]})
222
+ errors_for(form).must_equal({:name=>["has already been taken"], :title => ["can't be blank"]})
216
223
  end
217
224
 
218
225
  require 'reform/rails'
@@ -264,5 +271,3 @@ class ReformTest < MiniTest::Spec
264
271
  end
265
272
  end
266
273
  end
267
-
268
- # TODO: test errors
data/test/test_helper.rb CHANGED
@@ -1,4 +1,2 @@
1
- require 'test/unit'
2
- require 'minitest/spec'
3
- require 'ostruct'
4
- require 'reform'
1
+ require 'reform'
2
+ require 'minitest/autorun'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reform
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2013-05-13 00:00:00.000000000 Z
13
+ date: 2013-06-14 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: representable
@@ -19,7 +19,7 @@ dependencies:
19
19
  requirements:
20
20
  - - ! '>='
21
21
  - !ruby/object:Gem::Version
22
- version: '0'
22
+ version: 1.5.3
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -27,7 +27,7 @@ dependencies:
27
27
  requirements:
28
28
  - - ! '>='
29
29
  - !ruby/object:Gem::Version
30
- version: '0'
30
+ version: 1.5.3
31
31
  - !ruby/object:Gem::Dependency
32
32
  name: activemodel
33
33
  requirement: !ruby/object:Gem::Requirement
@@ -124,6 +124,22 @@ dependencies:
124
124
  - - ! '>='
125
125
  - !ruby/object:Gem::Version
126
126
  version: '0'
127
+ - !ruby/object:Gem::Dependency
128
+ name: virtus
129
+ requirement: !ruby/object:Gem::Requirement
130
+ none: false
131
+ requirements:
132
+ - - ! '>='
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ type: :development
136
+ prerelease: false
137
+ version_requirements: !ruby/object:Gem::Requirement
138
+ none: false
139
+ requirements:
140
+ - - ! '>='
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
127
143
  description: Freeing your AR models from form logic.
128
144
  email:
129
145
  - apotonick@gmail.com
@@ -141,10 +157,12 @@ files:
141
157
  - Rakefile
142
158
  - TODO.md
143
159
  - database.sqlite3
160
+ - gemfiles/Gemfile.rails-3.0
144
161
  - lib/reform.rb
145
162
  - lib/reform/form.rb
146
163
  - lib/reform/form/active_model.rb
147
164
  - lib/reform/form/active_record.rb
165
+ - lib/reform/form/coercion.rb
148
166
  - lib/reform/form/dsl.rb
149
167
  - lib/reform/rails.rb
150
168
  - lib/reform/version.rb