reform 0.1.2 → 0.2.0
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 +1 -0
- data/.travis.yml +0 -1
- data/CHANGES.md +13 -0
- data/Gemfile +0 -2
- data/README.md +276 -94
- data/Rakefile +6 -0
- data/TODO.md +10 -1
- data/database.sqlite3 +0 -0
- data/lib/reform/active_record.rb +2 -0
- data/lib/reform/composition.rb +55 -0
- data/lib/reform/form/active_model.rb +60 -15
- data/lib/reform/form/active_record.rb +3 -3
- data/lib/reform/form/composition.rb +69 -0
- data/lib/reform/form.rb +183 -80
- data/lib/reform/rails.rb +8 -1
- data/lib/reform/representer.rb +38 -0
- data/lib/reform/version.rb +1 -1
- data/lib/reform.rb +5 -2
- data/reform.gemspec +3 -2
- data/test/active_model_test.rb +83 -9
- data/test/coercion_test.rb +26 -0
- data/test/composition_test.rb +57 -0
- data/test/dummy/Rakefile +7 -0
- data/test/dummy/app/controllers/albums_controller.rb +18 -0
- data/test/dummy/app/controllers/application_controller.rb +4 -0
- data/test/dummy/app/controllers/musician_controller.rb +5 -0
- data/test/dummy/app/forms/album_form.rb +18 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/models/album.rb +4 -0
- data/test/dummy/app/models/song.rb +3 -0
- data/test/dummy/app/views/albums/new.html.erb +28 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/config/application.rb +20 -0
- data/test/dummy/config/boot.rb +10 -0
- data/test/dummy/config/database.yml +22 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +16 -0
- data/test/dummy/config/environments/production.rb +46 -0
- data/test/dummy/config/environments/test.rb +33 -0
- data/test/dummy/config/locales/en.yml +5 -0
- data/test/dummy/config/routes.rb +4 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/dummy/log/production.log +0 -0
- data/test/dummy/log/server.log +0 -0
- data/test/errors_test.rb +95 -0
- data/test/form_composition_test.rb +60 -0
- data/test/nested_form_test.rb +129 -0
- data/test/rails/integration_test.rb +54 -0
- data/test/reform_test.rb +80 -114
- data/test/test_helper.rb +14 -1
- metadata +86 -11
- data/lib/reform/form/dsl.rb +0 -38
- data/test/dsl_test.rb +0 -43
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/CHANGES.md
CHANGED
@@ -1,3 +1,16 @@
|
|
1
|
+
h3. 0.2.0
|
2
|
+
|
3
|
+
* Added nested property and collection for `has_one` and `has_many` relationships. . Note that this currently works only 1-level deep.
|
4
|
+
* Renamed `Reform::Form::DSL` to `Reform::Form::Composition` and deprecated `DSL`.
|
5
|
+
* `require 'reform'` now automatically requires Rails stuff in a Rails environment. Mainly, this is the FormBuilder compatibility layer that is injected into `Form`. If you don't want that, only require 'reform/form'.
|
6
|
+
* Composition now totally optional
|
7
|
+
* `Form.new` now accepts one argument, only: the model/composition. If you want to create your own representer, inject it by overriding `Form#mapper`. Note that this won't create property accessors for you.
|
8
|
+
* `Form::ActiveModel` no longer creates accessors to your represented models, e.g. having `property :title, on: :song` doesn't allow `form.song` anymore. This is because the actual model and the form's state might differ, so please use `form.title` directly.
|
9
|
+
|
10
|
+
h3. 0.1.3
|
11
|
+
|
12
|
+
* Altered `reform/rails` to conditionally load `ActiveRecord` code and created `reform/active_record`.
|
13
|
+
|
1
14
|
h3. 0.1.2
|
2
15
|
|
3
16
|
* `Form#to_model` is now delegated to model.
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -2,6 +2,9 @@
|
|
2
2
|
|
3
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
|
+
Although reform can be used in any Ruby framework, it comes with [Rails support](#rails-integration), works with [simple_form and other form gems](#formbuilder-support), allows nesting forms to implement [has_one](#nesting-forms-1-1-relations) and [has_many](#nesting-forms-1-n-relations) relationships, can [compose a form](#compositions) from multiple objects and gives you [coercion](#coercion).
|
6
|
+
|
7
|
+
|
5
8
|
## Installation
|
6
9
|
|
7
10
|
Add this line to your Gemfile:
|
@@ -12,47 +15,47 @@ gem 'reform'
|
|
12
15
|
|
13
16
|
## Defining Forms
|
14
17
|
|
15
|
-
|
18
|
+
You're working at a famous record label and your job is archiving all the songs, albums and artists. You start with a form to populate your `songs` table.
|
16
19
|
|
17
20
|
```ruby
|
18
|
-
class
|
19
|
-
|
20
|
-
|
21
|
-
property :title, on: :song
|
22
|
-
property :name, on: :artist
|
21
|
+
class SongForm < Reform::Form
|
22
|
+
property :title
|
23
|
+
property :length
|
23
24
|
|
24
|
-
validates :
|
25
|
+
validates :title, presence: true
|
26
|
+
validates :length, numericality: true
|
25
27
|
end
|
26
28
|
```
|
27
29
|
|
28
|
-
|
30
|
+
To add fields to the form use the `::property` method. Also, validations no longer go into the model but sit in the form.
|
29
31
|
|
30
|
-
__Note__: There is a convenience method `::properties` that allows you to pass an array of fields at one time.
|
31
32
|
|
32
33
|
## Using Forms
|
33
34
|
|
34
35
|
In your controller you'd create a form instance and pass in the models you wanna work on.
|
35
36
|
|
36
37
|
```ruby
|
37
|
-
|
38
|
-
|
39
|
-
|
38
|
+
class SongsController
|
39
|
+
def new
|
40
|
+
@form = SongForm.new(Song.new)
|
41
|
+
end
|
40
42
|
```
|
41
43
|
|
42
44
|
You can also setup the form for editing existing items.
|
43
45
|
|
44
46
|
```ruby
|
45
|
-
|
46
|
-
|
47
|
-
|
47
|
+
class SongsController
|
48
|
+
def edit
|
49
|
+
@form = SongForm.new(Song.find(1))
|
50
|
+
end
|
48
51
|
```
|
49
52
|
|
50
53
|
## Rendering Forms
|
51
54
|
|
52
|
-
Your `@form` is now ready to be rendered, either do it yourself or use something like `simple_form`.
|
55
|
+
Your `@form` is now ready to be rendered, either do it yourself or use something like Rails' `#form_for`, `simple_form` or `formtastic`.
|
53
56
|
|
54
57
|
```haml
|
55
|
-
=
|
58
|
+
= form_for @form do |f|
|
56
59
|
|
57
60
|
= f.input :name
|
58
61
|
= f.input :title
|
@@ -63,15 +66,16 @@ Your `@form` is now ready to be rendered, either do it yourself or use something
|
|
63
66
|
After a form submission, you wanna validate the input.
|
64
67
|
|
65
68
|
```ruby
|
66
|
-
|
67
|
-
|
69
|
+
class SongsController
|
70
|
+
def create
|
71
|
+
@form = SongForm.new(Song.new)
|
68
72
|
|
69
|
-
|
73
|
+
#=> params: {song: {title: "Rio", length: "366"}}
|
70
74
|
|
71
|
-
|
75
|
+
if @form.validate(params[:song])
|
72
76
|
```
|
73
77
|
|
74
|
-
|
78
|
+
Reform uses the validations you provided in the form - and nothing else.
|
75
79
|
|
76
80
|
|
77
81
|
## Saving Forms
|
@@ -79,143 +83,320 @@ def create
|
|
79
83
|
We provide a bullet-proof way to save your form data: by letting _you_ do it!
|
80
84
|
|
81
85
|
```ruby
|
82
|
-
if @form.validate(params[:
|
86
|
+
if @form.validate(params[:song])
|
83
87
|
|
84
88
|
@form.save do |data, nested|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
+
data.title #=> "Rio"
|
90
|
+
data.length #=> "366"
|
91
|
+
|
92
|
+
nested #=> {title: "Rio"}
|
89
93
|
|
90
|
-
|
94
|
+
Song.create(nested)
|
91
95
|
end
|
92
96
|
```
|
93
97
|
|
94
|
-
While `data` gives you an object exposing the form property readers, `nested`
|
98
|
+
While `data` gives you an object exposing the form property readers, `nested` is a hash reflecting the nesting structure of your form. Note how you can use arbitrary code to create/update models - in this example, we used `Song::create`.
|
95
99
|
|
96
100
|
To push the incoming data to the models directly, call `#save` without the block.
|
97
101
|
|
98
102
|
```ruby
|
99
|
-
@form.save #=> populates song
|
100
|
-
# by calling @form.song.
|
103
|
+
@form.save #=> populates song with incoming data
|
104
|
+
# by calling @form.song.title= and @form.song.length=.
|
101
105
|
```
|
102
106
|
|
103
|
-
## Coercion
|
104
107
|
|
105
|
-
|
108
|
+
## Nesting Forms: 1-1 Relations
|
106
109
|
|
107
|
-
|
110
|
+
Songs have artists to compose them. Let's say your `Song` model would implement that as follows.
|
108
111
|
|
109
112
|
```ruby
|
110
|
-
|
113
|
+
class Song < ActiveRecord::Base
|
114
|
+
has_one :artist
|
115
|
+
end
|
116
|
+
```
|
117
|
+
|
118
|
+
The edit form should allow changing data for artist and song.
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
class SongForm < Reform::Form
|
122
|
+
property :title
|
123
|
+
property :length
|
111
124
|
|
112
|
-
|
113
|
-
|
114
|
-
include Reform::Form::Coercion
|
125
|
+
property :artist do
|
126
|
+
property :name
|
115
127
|
|
116
|
-
|
128
|
+
validates :name, presence: true
|
129
|
+
end
|
130
|
+
|
131
|
+
#validates :title, ...
|
117
132
|
end
|
133
|
+
```
|
134
|
+
|
135
|
+
See how simple nesting forms is? By passing a block to `::property` you can define another form nested into your main form.
|
136
|
+
|
137
|
+
|
138
|
+
### has_one: Setup
|
139
|
+
|
140
|
+
This setup's only requirement is having a working `Song#artist` reader.
|
141
|
+
|
142
|
+
```ruby
|
143
|
+
class SongsController
|
144
|
+
def edit
|
145
|
+
song = Song.find(1)
|
146
|
+
song.artist #=> <0x999#Artist title="Duran Duran">
|
147
|
+
|
148
|
+
@form = SongForm.new(song)
|
149
|
+
end
|
150
|
+
```
|
151
|
+
|
152
|
+
### has_one: Rendering
|
153
|
+
|
154
|
+
When rendering this form you could use the form's accessors manually.
|
155
|
+
|
156
|
+
```haml
|
157
|
+
= text_field :title, @form.title
|
158
|
+
= text_field "artist[name]", @form.artist.name
|
159
|
+
```
|
118
160
|
|
161
|
+
Or use something like `#fields_for` in a Rails environment.
|
162
|
+
|
163
|
+
```haml
|
164
|
+
= form_for @form |f|
|
165
|
+
= f.text_field :title
|
166
|
+
= f.text_field :length
|
167
|
+
|
168
|
+
= fields_for :artist do |a|
|
169
|
+
= a.text_field :name
|
170
|
+
```
|
171
|
+
|
172
|
+
### has_one: Processing
|
173
|
+
|
174
|
+
The block form of `#save` would give you the following data.
|
175
|
+
|
176
|
+
```ruby
|
119
177
|
@form.save do |data, nested|
|
120
|
-
data.
|
178
|
+
data.title #=> "Hungry Like The Wolf"
|
179
|
+
data.artist.name #=> "Duran Duran"
|
180
|
+
|
181
|
+
nested #=> {title: "Hungry Like The Wolf",
|
182
|
+
# artist: {name: "Duran Duran"}}
|
183
|
+
end
|
121
184
|
```
|
122
185
|
|
123
|
-
|
186
|
+
Supposed you use reform's automatic save without a block, the following assignments would be made.
|
187
|
+
|
188
|
+
```ruby
|
189
|
+
form.song.title = "Hungry Like The Wolf"
|
190
|
+
form.song.artist.name = "Duran Duran"
|
191
|
+
```
|
192
|
+
|
193
|
+
## Nesting Forms: 1-n Relations
|
194
|
+
|
195
|
+
Reform also gives you nested collections.
|
196
|
+
|
197
|
+
Let's have Albums with songs!
|
198
|
+
|
199
|
+
```ruby
|
200
|
+
class Album < ActiveRecord::Base
|
201
|
+
has_many :songs
|
202
|
+
end
|
203
|
+
```
|
204
|
+
|
205
|
+
The form might look like this.
|
206
|
+
|
207
|
+
```ruby
|
208
|
+
class AlbumForm < Reform::Form
|
209
|
+
property :title
|
210
|
+
|
211
|
+
collection :songs do
|
212
|
+
property :title
|
213
|
+
|
214
|
+
validates :title, presence: true
|
215
|
+
end
|
216
|
+
end
|
217
|
+
```
|
218
|
+
|
219
|
+
This basically works like a nested `property` that iterates over a collection of songs.
|
220
|
+
|
221
|
+
### has_many: Rendering
|
222
|
+
|
223
|
+
Reform will expose the collection using the `#songs` method.
|
224
|
+
|
225
|
+
```haml
|
226
|
+
= text_field :title, @form.title
|
227
|
+
= text_field "songs[0][title]", @form.songs[0].title
|
228
|
+
```
|
124
229
|
|
125
|
-
|
230
|
+
However, `#fields_for` works just fine, again.
|
126
231
|
|
127
|
-
|
232
|
+
```haml
|
233
|
+
= form_for @form |f|
|
234
|
+
= f.text_field :title
|
235
|
+
|
236
|
+
= fields_for :songs do |s|
|
237
|
+
= s.text_field :title
|
238
|
+
```
|
128
239
|
|
129
|
-
###
|
130
|
-
#### Form Class
|
240
|
+
### has_many: Processing
|
131
241
|
|
132
|
-
|
242
|
+
The block form of `#save` will expose the data structures already discussed.
|
133
243
|
|
134
244
|
```ruby
|
135
|
-
|
245
|
+
@form.save do |data, nested|
|
246
|
+
data.title #=> "Rio"
|
247
|
+
data.songs.first.title #=> "Hungry Like The Wolf"
|
248
|
+
|
249
|
+
nested #=> {title: "Rio"
|
250
|
+
songs: [{title: "Hungry Like The Wolf"},
|
251
|
+
{title: "Last Chance On The Stairways"}]
|
252
|
+
end
|
253
|
+
```
|
136
254
|
|
137
|
-
class UserProfileForm < Reform::Form
|
138
|
-
include DSL
|
139
|
-
include Reform::Form::ActiveRecord
|
140
255
|
|
141
|
-
|
142
|
-
properties [:gender, :age], on: :profile
|
256
|
+
## Compositions
|
143
257
|
|
144
|
-
|
258
|
+
Sometimes you might want to embrace two (or more) unrelated objects with a single form. While you could write a simple delegating composition yourself, reform comes with it built-in.
|
145
259
|
|
146
|
-
|
147
|
-
|
148
|
-
|
260
|
+
Say we were to edit a song and the label data the record was released from. Internally, this would imply working on the `songs` table and the `labels` table.
|
261
|
+
|
262
|
+
```ruby
|
263
|
+
class SongWithLabelForm < Reform::Form
|
264
|
+
include Composition
|
265
|
+
|
266
|
+
property :title, on: :song
|
267
|
+
property :city, on: :label
|
268
|
+
|
269
|
+
validates :title, :city, presence: true
|
149
270
|
end
|
150
271
|
```
|
151
272
|
|
152
|
-
|
273
|
+
Note that reform needs to know about the owner objects of properties. You can do so by using the `on:` option.
|
274
|
+
|
275
|
+
### Composition: Setup
|
276
|
+
|
277
|
+
The constructor slightly differs.
|
153
278
|
|
154
279
|
```ruby
|
155
|
-
|
280
|
+
@form = SongWithLabelForm.new(song: Song.new, label: Label.new)
|
156
281
|
```
|
157
282
|
|
283
|
+
### Composition: Rendering
|
284
|
+
|
285
|
+
After you configured your composition in the form, reform hides the fact that you're actually showing two different objects.
|
158
286
|
|
159
|
-
|
287
|
+
```haml
|
288
|
+
= form_for @form do |f|
|
160
289
|
|
161
|
-
|
290
|
+
Song: = f.input :title
|
162
291
|
|
163
|
-
|
164
|
-
<%= form_for @form do |f| %>
|
165
|
-
<%= f.email_field :email %>
|
166
|
-
<%= f.input :gender %>
|
167
|
-
<%= f.number_field :age %>
|
168
|
-
<%= f.submit %>
|
169
|
-
<% end %>
|
292
|
+
Label in: = f.input :city
|
170
293
|
```
|
171
294
|
|
172
|
-
|
295
|
+
### Composition: Processing
|
296
|
+
|
297
|
+
When using `#save' without a block reform will use writer methods on the different objects to push validated data to the properties.
|
173
298
|
|
174
|
-
|
299
|
+
Here's how the block parameters look like.
|
175
300
|
|
176
301
|
```ruby
|
177
|
-
|
302
|
+
@form.save do |data, nested|
|
303
|
+
data.title #=> "Rio"
|
304
|
+
data.city #=> "London"
|
178
305
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
new_user.save!
|
186
|
-
end
|
187
|
-
end
|
188
|
-
end
|
306
|
+
nested #=> {
|
307
|
+
song: {title: "Rio"}
|
308
|
+
label: {city: "London"}
|
309
|
+
}
|
310
|
+
end
|
311
|
+
```
|
189
312
|
|
313
|
+
## Coercion
|
190
314
|
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
315
|
+
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.
|
316
|
+
|
317
|
+
Be sure to add `virtus` to your Gemfile.
|
318
|
+
|
319
|
+
```ruby
|
320
|
+
require 'reform/form/coercion'
|
321
|
+
|
322
|
+
class SongForm < Reform::Form
|
323
|
+
include Coercion
|
324
|
+
|
325
|
+
property :written_at, type: DateTime
|
195
326
|
end
|
327
|
+
|
328
|
+
@form.save do |data, nested|
|
329
|
+
data.written_at #=> <DateTime XXX>
|
196
330
|
```
|
197
331
|
|
198
|
-
__Note__: this can also be used for the update action as well.
|
199
332
|
|
200
|
-
##
|
333
|
+
## Agnosticism: Mapping Data
|
334
|
+
|
335
|
+
Reform doesn't really know whether it's working with a PORO, an `ActiveRecord` instance or a `Sequel` row.
|
336
|
+
|
337
|
+
When rendering the form, reform calls readers on the decorated model to retrieve the field data (`Song#title`, `Song#length`).
|
338
|
+
|
339
|
+
When saving a submitted form, the same happens using writers. Reform simply calls `Song#title=(value)`. No knowledge is required about the underlying database layer.
|
340
|
+
|
341
|
+
Nesting forms only requires readers for the nested properties as `Album#songs`.
|
342
|
+
|
343
|
+
|
344
|
+
## Rails Integration
|
201
345
|
|
202
|
-
|
346
|
+
Check out [@gogogarret](https://twitter.com/GoGoGarrett/)'s [sample Rails app](https://github.com/gogogarrett/reform_example) using Reform.
|
347
|
+
|
348
|
+
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
|
+
|
350
|
+
|
351
|
+
## ActiveModel compliance
|
352
|
+
|
353
|
+
Forms in Reform can easily be made ActiveModel-compliant.
|
354
|
+
|
355
|
+
Note that this step is _not_ necessary in a Rails environment.
|
203
356
|
|
204
357
|
```ruby
|
205
|
-
class
|
206
|
-
|
358
|
+
class SongForm < Reform::Form
|
359
|
+
include Reform::Form::ActiveModel
|
360
|
+
end
|
361
|
+
```
|
207
362
|
|
208
|
-
|
363
|
+
If you're not happy with the `model_name` result, configure it manually.
|
209
364
|
|
210
|
-
|
211
|
-
|
212
|
-
|
365
|
+
```ruby
|
366
|
+
class CoverSongForm < Reform::Form
|
367
|
+
include Reform::Form::ActiveModel
|
368
|
+
|
369
|
+
model :hit
|
213
370
|
end
|
214
371
|
```
|
215
372
|
|
373
|
+
This is especially helpful when your framework tries to render `cover_song_path` although you wanna go with `song_path`.
|
374
|
+
|
375
|
+
|
376
|
+
## FormBuilder Support
|
377
|
+
|
378
|
+
To make your forms work with all the form gems like `simple_form` or Rails `form_for` you need to include another module.
|
379
|
+
|
380
|
+
Again, this step is implicit in Rails and you don't need to do it manually.
|
381
|
+
|
382
|
+
```ruby
|
383
|
+
class SongForm < Reform::Form
|
384
|
+
include Reform::Form::ActiveModel
|
385
|
+
include Reform::Form::ActiveModel::FormBuilderMethods
|
386
|
+
end
|
387
|
+
```
|
388
|
+
|
389
|
+
|
216
390
|
## Security
|
217
391
|
|
218
|
-
By explicitely defining the form layout using `::property` there is no more need for protecting from unwanted input. `strong_parameter` or `
|
392
|
+
By explicitely defining the form layout using `::property` there is no more need for protecting from unwanted input. `strong_parameter` or `
|
393
|
+
attr_accessible` become obsolete. Reform will simply ignore undefined incoming parameters.
|
394
|
+
|
395
|
+
|
396
|
+
## Support
|
397
|
+
|
398
|
+
If you run into any trouble chat with us on irc.freenode.org#trailblazer.
|
399
|
+
|
219
400
|
|
220
401
|
## Maintainers
|
221
402
|
|
@@ -223,6 +404,7 @@ By explicitely defining the form layout using `::property` there is no more need
|
|
223
404
|
|
224
405
|
[Garrett Heinlen](https://github.com/gogogarrett)
|
225
406
|
|
407
|
+
|
226
408
|
### Attributions!!!
|
227
409
|
|
228
410
|
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/Rakefile
CHANGED
data/TODO.md
CHANGED
@@ -1,4 +1,13 @@
|
|
1
1
|
* `validates :title, :presence => true`
|
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
|
-
* document Form#to_hash and Form#to_nested_hash (e.g. with OpenStruct composition to make it a very simple form)
|
4
|
+
* document Form#to_hash and Form#to_nested_hash (e.g. with OpenStruct composition to make it a very simple form)
|
5
|
+
|
6
|
+
* allow :as to rename nested forms
|
7
|
+
* make #nested_forms easier
|
8
|
+
|
9
|
+
vererben in inline representern (module zum einmixen, attrs löschen)
|
10
|
+
|
11
|
+
# TODO: remove the concept of Errors#messages and just iterate over Errors.
|
12
|
+
# each form contains its local field errors in Errors
|
13
|
+
# form.messages should then go through them and compile a "summary" instead of adding them to the parents #errors in #validate.
|
data/database.sqlite3
CHANGED
Binary file
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Reform
|
2
|
+
# Keeps composition of models and knows how to transform a plain hash into a nested hash.
|
3
|
+
class Composition
|
4
|
+
class << self
|
5
|
+
def map(options)
|
6
|
+
@attr2obj = {} # {song: ["title", "track"], artist: ["name"]}
|
7
|
+
|
8
|
+
options.each do |mdl, meths|
|
9
|
+
create_accessors(mdl, meths)
|
10
|
+
attr_reader mdl
|
11
|
+
|
12
|
+
meths.each { |m| @attr2obj[m.to_s] = mdl }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Specific to representable.
|
17
|
+
def map_from(representer)
|
18
|
+
options = {}
|
19
|
+
representer.representable_attrs.each do |cfg|
|
20
|
+
options[cfg.options[:on]] ||= []
|
21
|
+
options[cfg.options[:on]] << cfg.name
|
22
|
+
end
|
23
|
+
|
24
|
+
map options
|
25
|
+
end
|
26
|
+
|
27
|
+
def model_for_property(name)
|
28
|
+
@attr2obj.fetch(name.to_s)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
def create_accessors(model, methods)
|
33
|
+
accessors = methods.collect { |m| [m, "#{m}="] }.flatten
|
34
|
+
delegate *accessors << {:to => :"#{model}"}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# TODO: make class method?
|
39
|
+
def nested_hash_for(attrs)
|
40
|
+
{}.tap do |hsh|
|
41
|
+
attrs.each do |name, val|
|
42
|
+
obj = self.class.model_for_property(name)
|
43
|
+
hsh[obj] ||= {}
|
44
|
+
hsh[obj][name.to_sym] = val
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def initialize(models)
|
50
|
+
models.each do |name, obj|
|
51
|
+
instance_variable_set(:"@#{name}", obj)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|