reform 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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