reform 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|