reform 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGES.md ADDED
@@ -0,0 +1,3 @@
1
+ h3. 0.1.0
2
+
3
+ * Oh yeah.
data/README.md CHANGED
@@ -1,29 +1,170 @@
1
1
  # Reform
2
2
 
3
- TODO: Write a gem description
3
+ Decouple your models from forms. Reform gives you a form object with validations and nested setup of models. It is completely framework-agnostic and doesn't care about your database.
4
4
 
5
5
  ## Installation
6
6
 
7
- Add this line to your application's Gemfile:
7
+ Add this line to your Gemfile:
8
8
 
9
- gem 'reform'
9
+ ```ruby
10
+ gem 'reform'
11
+ ```
10
12
 
11
- And then execute:
13
+ ## Defining Forms
12
14
 
13
- $ bundle
15
+ Say you need a form for song requests on a radio station. Internally, this would imply associating `songs` table and the `artists` table. You don't wanna reflect that in your web form, do you?
14
16
 
15
- Or install it yourself as:
17
+ ```ruby
18
+ class SongRequestForm < Reform::Form
19
+ include DSL
16
20
 
17
- $ gem install reform
21
+ property :title, on: :song
22
+ property :name, on: :artist
18
23
 
19
- ## Usage
24
+ validates :name, :title, presence: true
25
+ end
26
+ ```
20
27
 
21
- TODO: Write usage instructions here
28
+ The `::property` method allows defining the fields of the form. Using `:on` delegates this field to a nested object in your form.
22
29
 
23
- ## Contributing
30
+ __Note__: There is a convenience method `::properties` that allows you to pass an array of fields at one time.
24
31
 
25
- 1. Fork it
26
- 2. Create your feature branch (`git checkout -b my-new-feature`)
27
- 3. Commit your changes (`git commit -am 'Add some feature'`)
28
- 4. Push to the branch (`git push origin my-new-feature`)
29
- 5. Create new Pull Request
32
+ ## Using Forms
33
+
34
+ In your controller you'd create a form instance and pass in the models you wanna work on.
35
+
36
+ ```ruby
37
+ def new
38
+ @form = SongRequestForm.new(song: Song.new, artist: Artist.new)
39
+ end
40
+ ```
41
+
42
+ You can also setup the form for editing existing items.
43
+
44
+ ```ruby
45
+ def edit
46
+ @form = SongRequestForm.new(song: Song.find(1), artist: Artist.find(2))
47
+ end
48
+ ```
49
+
50
+ ## Rendering Forms
51
+
52
+ Your `@form` is now ready to be rendered, either do it yourself or use something like `simple_form`.
53
+
54
+ ```haml
55
+ = simple_form_for @form do |f|
56
+
57
+ = f.input :name
58
+ = f.input :title
59
+ ```
60
+
61
+ ## Validating Forms
62
+
63
+ After a form submission, you wanna validate the input.
64
+
65
+ ```ruby
66
+ def create
67
+ @form = SongRequestForm.new(song: Song.new, artist: Artist.new)
68
+
69
+ #=> params: {song_request: {title: "Rio", name: "Duran Duran"}}
70
+
71
+ if @form.validate(params[:song_request])
72
+ ```
73
+
74
+ `Reform` uses the validations you provided in the form - and nothing else.
75
+
76
+
77
+ ## Saving Forms
78
+
79
+ We provide a bullet-proof way to save your form data: by letting _you_ do it!
80
+
81
+ ```ruby
82
+ if @form.validate(params[:song_request])
83
+
84
+ @form.save do |data, nested|
85
+ #=> data: <title: "Rio", name: "Duran Duran">
86
+ #
87
+ # nested: {song: {title: "Rio"},
88
+ # artist: {name: "Duran Duran"}}
89
+
90
+ SongRequest.new(nested[:song][:title])
91
+ end
92
+ ```
93
+
94
+ While `data` gives you an object exposing the form property readers, `nested` already reflects the nesting you defined in your form earlier.
95
+
96
+ To push the incoming data to the models directly, call `#save` without the block.
97
+
98
+ ```ruby
99
+ @form.save #=> populates song and artist with incoming data
100
+ # by calling @form.song.name= and @form.artist.title=.
101
+ ```
102
+
103
+ ## ActiveModel - Rails Integration
104
+
105
+ Reform offers ActiveModel support to easily make this accessible in Rails based projects. You simply `include Reform::Form::ActiveModel` in your form object and the Rails specific code will be handled for you.
106
+
107
+ ### Simple Integration
108
+ #### Form Class
109
+
110
+ You have to include a call to `model` to specifiy which is the main object of the form.
111
+
112
+ ```ruby
113
+ class UserProfileForm < Reform::Form
114
+ include DSL
115
+ include Reform::Form::ActiveModel
116
+
117
+ property :email, on: :user
118
+ properties [:gender, :age], on: :profile
119
+
120
+ model :user, on: :user
121
+
122
+ validates :email, :gender, presence: true
123
+ validates :age, numericality: true
124
+ end
125
+ ```
126
+
127
+ #### View Form
128
+
129
+ The form becomes __very__ dumb as it knows nothing about the backend assocations or data binding to the database layer. This simply takes input and passes it along to the controller as it should.
130
+
131
+ ```erb
132
+ <%= form_for @form do |f| %>
133
+ <%= f.email_field :email %>
134
+ <%= f.input :gender %>
135
+ <%= f.number_field :age %>
136
+ <%= f.submit %>
137
+ <% end %>
138
+ ```
139
+
140
+ #### Controller
141
+
142
+ In the controller you can easily create helpers to build these form objects for you. In the create and update actions Reform allows you total control of what to do with the data being passed via the form. How you interact with the data is entirely up to you.
143
+
144
+ ```ruby
145
+ class UsersController < ApplicationController
146
+
147
+ def create
148
+ @form = create_new_form
149
+ if @form.validate(params[:user])
150
+ @form.save do |data, map|
151
+ new_user = User.new(map[:user])
152
+ new_user.build_user_profile(map[:profile])
153
+ new_user.save!
154
+ end
155
+ end
156
+ end
157
+
158
+
159
+ private
160
+ def create_new_form
161
+ UserProfileForm.new(user: User.new, profile: UserProfile.new)
162
+ end
163
+ end
164
+ ```
165
+
166
+ __Note__: this can also be used for the update action as well.
167
+
168
+ ## Security
169
+
170
+ 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.
data/Rakefile CHANGED
@@ -1 +1,9 @@
1
1
  require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ task :default => [:test]
5
+ Rake::TestTask.new(:test) do |test|
6
+ test.libs << 'test'
7
+ test.test_files = FileList['test/*_test.rb']
8
+ test.verbose = true
9
+ end
@@ -0,0 +1,28 @@
1
+ module Reform::Form::ActiveModel
2
+ def self.included(base)
3
+ base.class_eval do
4
+ extend ClassMethods
5
+ end
6
+ end
7
+
8
+ module ClassMethods
9
+ def model(*args)
10
+ @model_options = args # FIXME: make inheritable!
11
+ main_model = args.last[:on]
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
15
+
16
+ alias_method args.first, main_model # #hit => model.song.
17
+ end
18
+
19
+ def property(name, options={})
20
+ delegate options[:on], :to => :model
21
+ super
22
+ end
23
+
24
+ def model_name
25
+ ActiveModel::Name.new(self, nil, @model_options.first.to_s.camelize)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,38 @@
1
+ class Reform::Form
2
+ module DSL
3
+ def self.included(base)
4
+ base.class_eval do
5
+ extend ClassMethods
6
+ end
7
+ end
8
+
9
+ module ClassMethods
10
+ def property(name, *args)
11
+ representer_class.property(name, *args)
12
+ end
13
+
14
+ def properties(names, *args)
15
+ names.each do |name|
16
+ property(name, *args)
17
+ end
18
+ end
19
+
20
+ #private
21
+ def representer_class
22
+ @representer_class ||= Class.new(Reform::Representer)
23
+ end
24
+
25
+ def model_class
26
+ rpr = representer_class
27
+ @model_class ||= Class.new(Reform::Composition) do
28
+ map_from rpr
29
+ end
30
+ end
31
+ end
32
+
33
+ def initialize(models)
34
+ composition = self.class.model_class.new(models)
35
+ super(self.class.representer_class, composition)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,132 @@
1
+ require 'delegate'
2
+
3
+ module Reform
4
+ class Form < SimpleDelegator
5
+ # reasons for delegation:
6
+ # presentation: this object is used in the presentation layer by #form_for.
7
+ # problem: #form_for uses respond_to?(:email_before_type_cast) which goes to an internal hash in the actual record.
8
+ # validation: this object also contains the validation rules itself, should be separated.
9
+ # TODO: figure out #to_key issues.
10
+
11
+ def initialize(mapper, composition)
12
+ @mapper = mapper
13
+ @model = composition
14
+ representer = @mapper.new(composition)
15
+
16
+ super Fields.new(representer.fields, representer.to_hash) # decorate composition and transform to hash.
17
+ end
18
+
19
+ def validate(params)
20
+ # here it would be cool to have a validator object containing the validation rules representer-like and then pass it the formed model.
21
+ update_with(params)
22
+
23
+ valid? # this validates on <Fields> using AM::Validations, currently.
24
+ end
25
+
26
+ def save
27
+ # DISCUSS: we should never hit @mapper here (which writes to the models) when a block is passed.
28
+ return yield self, to_nested_hash if block_given?
29
+
30
+ @mapper.new(model).from_hash(to_hash) # DISCUSS: move to Composition?
31
+ end
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
+ # Use representer to return current key-value form hash.
41
+ def to_hash
42
+ mapper.new(self).to_hash
43
+ end
44
+
45
+ def to_nested_hash
46
+ model.nested_hash_for(to_hash) # use composition to compute nested hash.
47
+ end
48
+
49
+ # FIXME: make AM optional.
50
+ require 'active_model'
51
+ include ActiveModel::Validations
52
+ end
53
+
54
+ # Keeps values of the form fields. What's in here is to be displayed in the browser!
55
+ # we need this intermediate object to display both "original values" and new input from the form after submitting.
56
+ class Fields < OpenStruct
57
+ def initialize(properties, values={})
58
+ fields = properties.inject({}) { |hsh, attr| hsh.merge!(attr => nil) }
59
+ super(fields.merge!(values)) # TODO: stringify value keys!
60
+ end
61
+ end
62
+
63
+ # Keeps composition of models and knows how to transform a plain hash into a nested hash.
64
+ class Composition
65
+ class << self
66
+ def map(options)
67
+ @attr2obj = {} # {song: ["title", "track"], artist: ["name"]}
68
+
69
+ options.each do |mdl, meths|
70
+ create_accessors(mdl, meths)
71
+ attr_reader mdl # FIXME: unless already defined!!
72
+
73
+ meths.each { |m| @attr2obj[m.to_s] = mdl }
74
+ end
75
+ end
76
+
77
+ # Specific to representable.
78
+ def map_from(representer)
79
+ options = {}
80
+ representer.representable_attrs.each do |cfg|
81
+ options[cfg.options[:on]] ||= []
82
+ options[cfg.options[:on]] << cfg.name
83
+ end
84
+
85
+ map options
86
+ end
87
+
88
+ def model_for_property(name)
89
+ @attr2obj.fetch(name.to_s)
90
+ end
91
+
92
+ private
93
+ def create_accessors(model, methods)
94
+ accessors = methods.collect { |m| [m, "#{m}="] }.flatten
95
+ delegate *accessors, to: "@#{model}"
96
+ end
97
+ end
98
+
99
+ # TODO: make class method?
100
+ def nested_hash_for(attrs)
101
+ {}.tap do |hsh|
102
+ attrs.each do |name, val|
103
+ obj = self.class.model_for_property(name)
104
+ hsh[obj] ||= {}
105
+ hsh[obj][name.to_sym] = val
106
+ end
107
+ end
108
+ end
109
+
110
+ def initialize(models)
111
+ models.each do |name, obj|
112
+ instance_variable_set(:"@#{name}", obj)
113
+ end
114
+ end
115
+ end
116
+
117
+ require 'representable/hash'
118
+ class Representer < Representable::Decorator
119
+ include Representable::Hash
120
+
121
+ def self.properties(names, *args)
122
+ names.each do |name|
123
+ property(name, *args)
124
+ end
125
+ end
126
+
127
+ # Returns hash of all property names.
128
+ def fields
129
+ representable_attrs.collect { |cfg| cfg.name }
130
+ end
131
+ end
132
+ end
@@ -1,3 +1,3 @@
1
1
  module Reform
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
data/lib/reform.rb CHANGED
@@ -3,3 +3,7 @@ require "reform/version"
3
3
  module Reform
4
4
  # Your code goes here...
5
5
  end
6
+
7
+ require 'reform/form'
8
+ require 'reform/form/dsl'
9
+ require 'reform/form/active_model'
data/reform.gemspec CHANGED
@@ -6,8 +6,8 @@ require 'reform/version'
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = "reform"
8
8
  spec.version = Reform::VERSION
9
- spec.authors = ["Nick Sutterer"]
10
- spec.email = ["apotonick@gmail.com"]
9
+ spec.authors = ["Nick Sutterer", "Garrett Heinlen"]
10
+ spec.email = ["apotonick@gmail.com", "heinleng@gmail.com"]
11
11
  spec.description = %q{Freeing your AR models from form logic.}
12
12
  spec.summary = %q{Freeing your AR models from form logic.}
13
13
  spec.homepage = ""
@@ -18,6 +18,9 @@ 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_development_dependency "bundler", "~> 1.3"
21
+ spec.add_dependency "representable"
22
+ spec.add_development_dependency "bundler", "~> 1.3"
22
23
  spec.add_development_dependency "rake"
24
+ spec.add_development_dependency "minitest"
25
+ spec.add_development_dependency "activemodel"
23
26
  end
data/test/dsl_test.rb ADDED
@@ -0,0 +1,72 @@
1
+ require 'test_helper'
2
+
3
+ class DslTest < MiniTest::Spec
4
+ class SongForm < Reform::Form
5
+ include DSL
6
+
7
+ property :title, :on => :song
8
+ properties [:name, :genre], :on => :artist
9
+
10
+ validates :name, :title, :genre, presence: true
11
+ end
12
+
13
+ let (:form) { SongForm.new(:song => OpenStruct.new(:title => "Rio"), :artist => OpenStruct.new()) }
14
+
15
+ it "works by creating Representer and Composition for you" do
16
+ form.validate("title" => "Greyhound", "name" => "Frenzal Rhomb").must_equal false
17
+ end
18
+ end
19
+
20
+ class ActiveModelTest < MiniTest::Spec
21
+ class HitForm < Reform::Form
22
+ include DSL
23
+ include Reform::Form::ActiveModel
24
+
25
+ property :title, :on => :song
26
+ properties [:name, :genre], :on => :artist
27
+
28
+ model :hit, :on => :song
29
+ end
30
+
31
+ let (:rio) { OpenStruct.new(:title => "Rio") }
32
+ let (:duran) { OpenStruct.new }
33
+ let (:form) { HitForm.new(:song => rio, :artist => duran) }
34
+
35
+ it "creates model readers" do
36
+ form.hit.must_equal rio
37
+ end
38
+
39
+ it "creates composition readers" do
40
+ form.song.must_equal rio
41
+ form.artist.must_equal duran
42
+ end
43
+
44
+ it "provides ::model_name" do
45
+ form.class.model_name.must_equal "Hit"
46
+ end
47
+
48
+ it "provides #persisted?" do
49
+ HitForm.new(:song => OpenStruct.new.instance_eval { def persisted?; "yo!"; end; self }, :artist => OpenStruct.new).persisted?.must_equal "yo!"
50
+ end
51
+
52
+ it "provides #to_key" do
53
+ HitForm.new(:song => OpenStruct.new.instance_eval { def to_key; "yo!"; end; self }, :artist => OpenStruct.new).to_key.must_equal "yo!"
54
+ end
55
+
56
+ it "provides #to_param" do
57
+ HitForm.new(:song => OpenStruct.new.instance_eval { def to_param; "yo!"; end; self }, :artist => OpenStruct.new).to_param.must_equal "yo!"
58
+ end
59
+
60
+ it "works with any order of ::model and ::property" do
61
+ class AnotherForm < Reform::Form
62
+ include DSL
63
+ include Reform::Form::ActiveModel
64
+
65
+ model :song, :on => :song
66
+ property :title, :on => :song
67
+ end
68
+
69
+
70
+ AnotherForm.new(:song => rio).song.must_equal rio
71
+ end
72
+ end
@@ -0,0 +1,203 @@
1
+ require 'test_helper'
2
+
3
+ class RepresenterTest < MiniTest::Spec
4
+ class SongRepresenter < Reform::Representer
5
+ properties [:title, :year]
6
+ end
7
+
8
+ let (:rpr) { SongRepresenter.new(OpenStruct.new(:title => "Disconnect, Disconnect", :year => 1990)) }
9
+
10
+ # TODO: introduce representer_for helper.
11
+ describe "::properties" do
12
+ it "accepts array of property names" do
13
+ rpr.to_hash.must_equal({"title"=>"Disconnect, Disconnect", "year" => 1990} )
14
+ end
15
+ end
16
+
17
+ describe "#fields" do
18
+ it "returns all properties as strings" do
19
+ rpr.fields.must_equal(["title", "year"])
20
+ end
21
+ end
22
+ end
23
+
24
+ class FieldsTest < MiniTest::Spec
25
+ describe "#new" do
26
+ it "accepts list of properties" do
27
+ fields = Reform::Fields.new([:name, :title])
28
+ fields.name.must_equal nil
29
+ fields.title.must_equal nil
30
+ end
31
+
32
+ it "accepts list of properties and values" do
33
+ fields = Reform::Fields.new(["name", "title"], "title" => "The Body")
34
+ fields.name.must_equal nil
35
+ fields.title.must_equal "The Body"
36
+ end
37
+
38
+ it "processes value syms" do
39
+ fields = Reform::Fields.new(["name", "title"], :title => "The Body")
40
+ fields.name.must_equal nil
41
+ fields.title.must_equal "The Body"
42
+ end
43
+ end
44
+ end
45
+
46
+ class ReformTest < MiniTest::Spec
47
+ let (:duran) { OpenStruct.new(:name => "Duran Duran") }
48
+ let (:rio) { OpenStruct.new(:title => "Rio") }
49
+
50
+ let (:form) { SongForm.new(SongAndArtistMap, comp) }
51
+
52
+ class SongAndArtistMap < Reform::Representer
53
+ property :name, on: :artist
54
+ property :title, on: :song
55
+ end
56
+
57
+ class SongForm < Reform::Form
58
+ end
59
+
60
+ describe "Composition" do
61
+ class SongAndArtist < Reform::Composition
62
+ map({:artist => [:name], :song => [:title]}) #SongAndArtistMap.representable_attrs
63
+ end
64
+
65
+ let (:comp) { SongAndArtist.new(:artist => @artist=OpenStruct.new, :song => rio) }
66
+
67
+ it "delegates to models as defined" do
68
+ comp.name.must_equal nil
69
+ comp.title.must_equal "Rio"
70
+ end
71
+
72
+ it "raises when non-mapped property" do
73
+ assert_raises NoMethodError do
74
+ comp.raise_an_exception
75
+ end
76
+ end
77
+
78
+ it "creates readers to models" do
79
+ comp.song.object_id.must_equal rio.object_id
80
+ comp.artist.object_id.must_equal @artist.object_id
81
+ end
82
+
83
+ describe "::map_from" do
84
+ it "creates the same mapping" do
85
+ comp =
86
+ Class.new(Reform::Composition) do
87
+ map_from SongAndArtistMap
88
+ end.
89
+ new(:artist => duran, :song => rio)
90
+
91
+ comp.name.must_equal "Duran Duran"
92
+ comp.title.must_equal "Rio"
93
+ end
94
+ end
95
+
96
+ describe "#nested_hash_for" do
97
+ it "returns nested hash" do
98
+ comp.nested_hash_for(:name => "Jimi Hendrix", :title => "Fire").must_equal({:artist=>{:name=>"Jimi Hendrix"}, :song=>{:title=>"Fire"}})
99
+ end
100
+
101
+ it "works with strings" do
102
+ comp.nested_hash_for("name" => "Jimi Hendrix", "title" => "Fire").must_equal({:artist=>{:name=>"Jimi Hendrix"}, :song=>{:title=>"Fire"}})
103
+ end
104
+
105
+ it "works with strings in map" do
106
+ Class.new(Reform::Composition) do
107
+ map(:artist => ["name"])
108
+ end.new([nil]).nested_hash_for(:name => "Jimi Hendrix").must_equal({:artist=>{:name=>"Jimi Hendrix"}})
109
+ end
110
+ end
111
+ end
112
+
113
+ describe "(new) form with empty models" do
114
+ let (:comp) { SongAndArtist.new(:artist => OpenStruct.new, :song => OpenStruct.new) }
115
+
116
+ it "returns empty fields" do
117
+ form.title.must_equal nil
118
+ form.name.must_equal nil
119
+ end
120
+
121
+ describe "and submitted values" do
122
+ it "returns filled-out fields" do
123
+ form.validate("name" => "Duran Duran")
124
+
125
+ form.title.must_equal nil
126
+ form.name.must_equal "Duran Duran"
127
+ end
128
+ end
129
+ end
130
+
131
+ describe "(edit) form with existing models" do
132
+ let (:comp) { SongAndArtist.new(:artist => duran, :song => rio) }
133
+
134
+ it "returns filled-out fields" do
135
+ form.name.must_equal "Duran Duran"
136
+ form.title.must_equal "Rio"
137
+ end
138
+ end
139
+
140
+ describe "#validate" do
141
+ let (:comp) { SongAndArtist.new(:artist => OpenStruct.new, :song => OpenStruct.new) }
142
+
143
+ it "ignores unmapped fields in input" do
144
+ form.validate("name" => "Duran Duran", :genre => "80s")
145
+ assert_raises NoMethodError do
146
+ form.genre
147
+ end
148
+ end
149
+
150
+ it "returns true when valid" do
151
+ form.validate("name" => "Duran Duran").must_equal true
152
+ end
153
+
154
+ # TODO: test errors. test valid.
155
+ it "returns false when invalid" do
156
+ class ValidatingForm < Reform::Form
157
+ validates :name, :presence => true
158
+ end
159
+
160
+ ValidatingForm.new(SongAndArtistMap, comp).validate({}).must_equal false
161
+ end
162
+ end
163
+
164
+
165
+ describe "#save" do
166
+ let (:comp) { SongAndArtist.new(:artist => OpenStruct.new, :song => OpenStruct.new) }
167
+ let (:form) { SongForm.new(SongAndArtistMap, comp) }
168
+
169
+ before { form.validate("name" => "Diesel Boy") }
170
+
171
+ it "pushes data to models" do
172
+ form.save
173
+
174
+ comp.artist.name.must_equal "Diesel Boy"
175
+ comp.song.title.must_equal nil
176
+ end
177
+
178
+ describe "#save with block" do
179
+ it "provides data block argument" do
180
+ hash = {}
181
+
182
+ form.save do |data, map|
183
+ hash[:name] = data.name
184
+ hash[:title] = data.title
185
+ end
186
+
187
+ hash.must_equal({:name=>"Diesel Boy", :title=>nil})
188
+ end
189
+
190
+ it "provides nested symbolized hash as second block argument" do
191
+ hash = {}
192
+
193
+ form.save do |data, map|
194
+ hash = map
195
+ end
196
+
197
+ hash.must_equal({:artist=>{:name=>"Diesel Boy"}})
198
+ end
199
+ end
200
+ end
201
+ end
202
+
203
+ # TODO: test errors
@@ -0,0 +1,4 @@
1
+ require 'test/unit'
2
+ require 'minitest/spec'
3
+ require 'ostruct'
4
+ require 'reform'
metadata CHANGED
@@ -1,16 +1,33 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reform
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
8
8
  - Nick Sutterer
9
+ - Garrett Heinlen
9
10
  autorequire:
10
11
  bindir: bin
11
12
  cert_chain: []
12
- date: 2013-04-30 00:00:00.000000000 Z
13
+ date: 2013-05-09 00:00:00.000000000 Z
13
14
  dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: representable
17
+ requirement: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ! '>='
29
+ - !ruby/object:Gem::Version
30
+ version: '0'
14
31
  - !ruby/object:Gem::Dependency
15
32
  name: bundler
16
33
  requirement: !ruby/object:Gem::Requirement
@@ -43,21 +60,61 @@ dependencies:
43
60
  - - ! '>='
44
61
  - !ruby/object:Gem::Version
45
62
  version: '0'
63
+ - !ruby/object:Gem::Dependency
64
+ name: minitest
65
+ requirement: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ type: :development
72
+ prerelease: false
73
+ version_requirements: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ - !ruby/object:Gem::Dependency
80
+ name: activemodel
81
+ requirement: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ! '>='
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ type: :development
88
+ prerelease: false
89
+ version_requirements: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ! '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
46
95
  description: Freeing your AR models from form logic.
47
96
  email:
48
97
  - apotonick@gmail.com
98
+ - heinleng@gmail.com
49
99
  executables: []
50
100
  extensions: []
51
101
  extra_rdoc_files: []
52
102
  files:
53
103
  - .gitignore
104
+ - CHANGES.md
54
105
  - Gemfile
55
106
  - LICENSE.txt
56
107
  - README.md
57
108
  - Rakefile
58
109
  - lib/reform.rb
110
+ - lib/reform/form.rb
111
+ - lib/reform/form/active_model.rb
112
+ - lib/reform/form/dsl.rb
59
113
  - lib/reform/version.rb
60
114
  - reform.gemspec
115
+ - test/dsl_test.rb
116
+ - test/reform_test.rb
117
+ - test/test_helper.rb
61
118
  homepage: ''
62
119
  licenses:
63
120
  - MIT
@@ -83,4 +140,7 @@ rubygems_version: 1.8.25
83
140
  signing_key:
84
141
  specification_version: 3
85
142
  summary: Freeing your AR models from form logic.
86
- test_files: []
143
+ test_files:
144
+ - test/dsl_test.rb
145
+ - test/reform_test.rb
146
+ - test/test_helper.rb