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
@@ -1,31 +1,76 @@
|
|
1
1
|
module Reform::Form::ActiveModel
|
2
|
+
module FormBuilderMethods # TODO: rename to FormBuilderCompat.
|
3
|
+
def self.included(base)
|
4
|
+
base.class_eval do
|
5
|
+
extend ClassMethods # ::model_name
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def property(name, options={})
|
11
|
+
add_nested_attribute_compat(name) if block_given? # TODO: fix that in Rails FB#1832 work.
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
# The Rails FormBuilder "detects" nested attributes (which is what we want) by checking existance of a setter method.
|
17
|
+
def add_nested_attribute_compat(name)
|
18
|
+
define_method("#{name}_attributes=") {} # this is why i hate respond_to? in Rails.
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Modify the incoming Rails params hash to be representable compliant.
|
23
|
+
def validate(params)
|
24
|
+
# DISCUSS: #validate should actually expect the complete params hash and then pick the right key as it knows the form name.
|
25
|
+
# however, this would cause confusion?
|
26
|
+
mapper.new(self).nested_forms do |attr, model| # FIXME: make this simpler.
|
27
|
+
if attr.options[:form_collection] # FIXME: why no array?
|
28
|
+
params[attr.name] = params["#{attr.name}_attributes"].values
|
29
|
+
else
|
30
|
+
params[attr.name] = params["#{attr.name}_attributes"]# DISCUSS: delete old key? override existing?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
super
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
|
2
39
|
def self.included(base)
|
3
40
|
base.class_eval do
|
4
41
|
extend ClassMethods
|
42
|
+
|
43
|
+
delegate [:persisted?, :to_key, :to_param, :id] => :model
|
44
|
+
|
45
|
+
def to_model # this is called somewhere in FormBuilder and ActionController.
|
46
|
+
self
|
47
|
+
end
|
5
48
|
end
|
6
49
|
end
|
7
50
|
|
8
51
|
module ClassMethods
|
52
|
+
# Set a model name for this form if the infered is wrong.
|
53
|
+
#
|
54
|
+
# class CoverSongForm < Reform::Form
|
55
|
+
# model :song
|
9
56
|
def model(main_model, options={})
|
10
|
-
@model_options
|
11
|
-
composition_model = options[:on] || main_model
|
12
|
-
|
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
|
-
|
16
|
-
alias_method main_model, composition_model # #hit => model.song.
|
17
|
-
end
|
18
|
-
|
19
|
-
def property(name, options={})
|
20
|
-
delegate options[:on], :to => :model
|
21
|
-
super
|
57
|
+
@model_options = [main_model, options] # FIXME: make inheritable!
|
22
58
|
end
|
23
59
|
|
24
60
|
def model_name
|
25
|
-
|
61
|
+
if @model_options
|
62
|
+
form_name = @model_options.first.to_s.camelize
|
63
|
+
else
|
64
|
+
form_name = name.sub(/Form$/, "")
|
65
|
+
end
|
66
|
+
|
67
|
+
active_model_name_for(form_name)
|
68
|
+
end
|
26
69
|
|
27
|
-
|
28
|
-
|
70
|
+
private
|
71
|
+
def active_model_name_for(string)
|
72
|
+
return ::ActiveModel::Name.new(OpenStruct.new(:name => string)) if Reform.rails3_0?
|
73
|
+
::ActiveModel::Name.new(self, nil, string)
|
29
74
|
end
|
30
75
|
end
|
31
76
|
end
|
@@ -2,7 +2,7 @@ class Reform::Form
|
|
2
2
|
module ActiveRecord
|
3
3
|
def self.included(base)
|
4
4
|
base.class_eval do
|
5
|
-
include ActiveModel
|
5
|
+
include Reform::Form::ActiveModel
|
6
6
|
extend ClassMethods
|
7
7
|
end
|
8
8
|
end
|
@@ -17,12 +17,12 @@ class Reform::Form
|
|
17
17
|
# when calling validates it should create the Vali instance already and set @klass there! # TODO: fix this in AM.
|
18
18
|
def validate(form)
|
19
19
|
property = attributes.first
|
20
|
-
model_name = form.send(:model).class.model_for_property(property)
|
20
|
+
#model_name = form.send(:model).class.model_for_property(property)
|
21
21
|
|
22
22
|
# here is the thing: why does AM::UniquenessValidator require a filled-out record to work properly? also, why do we need to set
|
23
23
|
# the class? it would be way easier to pass #validate a hash of attributes and get back an errors hash.
|
24
24
|
# the class for the finder could either be infered from the record or set in the validator instance itself in the call to ::validates.
|
25
|
-
record = form.send(
|
25
|
+
record = form.send(:model)
|
26
26
|
record.send("#{property}=", form.send(property))
|
27
27
|
@klass = record.class # this is usually done in the super-sucky #setup method.
|
28
28
|
super(record).tap do |res|
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require "reform/form/active_model"
|
2
|
+
|
3
|
+
class Reform::Form
|
4
|
+
# Automatically creates a Composition object for you when initializing the form.
|
5
|
+
module Composition
|
6
|
+
def self.included(base)
|
7
|
+
base.class_eval do
|
8
|
+
extend ClassMethods
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
include Reform::Form::ActiveModel::ClassMethods # ::model.
|
14
|
+
|
15
|
+
def model_class # DISCUSS: needed?
|
16
|
+
rpr = representer_class
|
17
|
+
@model_class ||= Class.new(Reform::Composition) do
|
18
|
+
map_from rpr
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def property(name, options={})
|
23
|
+
super
|
24
|
+
delegate options[:on] => :@model
|
25
|
+
end
|
26
|
+
|
27
|
+
# Same as ActiveModel::model but allows you to define the main model in the composition
|
28
|
+
# using +:on+.
|
29
|
+
#
|
30
|
+
# class CoverSongForm < Reform::Form
|
31
|
+
# model :song, on: :cover_song
|
32
|
+
def model(main_model, options={})
|
33
|
+
super
|
34
|
+
|
35
|
+
composition_model = options[:on] || main_model
|
36
|
+
|
37
|
+
delegate composition_model => :model # #song => model.song
|
38
|
+
|
39
|
+
# FIXME: this should just delegate to :model as in FB, and the comp would take care of it internally.
|
40
|
+
delegate [:persisted?, :to_key, :to_param] => composition_model # #to_key => song.to_key
|
41
|
+
|
42
|
+
alias_method main_model, composition_model # #hit => model.song.
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def initialize(models)
|
47
|
+
composition = self.class.model_class.new(models)
|
48
|
+
super(composition)
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_nested_hash
|
52
|
+
model.nested_hash_for(to_hash) # use composition to compute nested hash.
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
# TODO: remove me in 1.3.
|
58
|
+
module DSL
|
59
|
+
include Composition
|
60
|
+
|
61
|
+
def self.included(base)
|
62
|
+
warn "[DEPRECATION] Reform::Form: `DSL` is deprecated. Please use `Composition` instead."
|
63
|
+
|
64
|
+
base.class_eval do
|
65
|
+
extend Composition::ClassMethods
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/reform/form.rb
CHANGED
@@ -1,143 +1,246 @@
|
|
1
|
-
require '
|
1
|
+
require 'forwardable'
|
2
2
|
require 'ostruct'
|
3
3
|
|
4
|
+
require 'reform/composition'
|
5
|
+
require 'reform/representer'
|
6
|
+
|
4
7
|
module Reform
|
5
|
-
class Form
|
8
|
+
class Form
|
9
|
+
extend Forwardable
|
6
10
|
# reasons for delegation:
|
7
11
|
# presentation: this object is used in the presentation layer by #form_for.
|
8
12
|
# problem: #form_for uses respond_to?(:email_before_type_cast) which goes to an internal hash in the actual record.
|
9
13
|
# validation: this object also contains the validation rules itself, should be separated.
|
10
|
-
# TODO: figure out #to_key issues.
|
11
14
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
+
# Allows using property and friends in the Form itself. Forwarded to the internal representer_class.
|
16
|
+
module PropertyMethods
|
17
|
+
extend Forwardable
|
18
|
+
|
19
|
+
def property(name, options={}, &block)
|
20
|
+
definition = representer_class.property(name, options, &block)
|
21
|
+
setup_form_definition(definition) if block_given?
|
22
|
+
create_accessor(name)
|
23
|
+
end
|
24
|
+
|
25
|
+
def collection(name, options={}, &block)
|
26
|
+
options[:form_collection] = true
|
27
|
+
|
28
|
+
property(name, options, &block)
|
29
|
+
end
|
30
|
+
|
31
|
+
def properties(names, *args)
|
32
|
+
names.each { |name| property(name, *args) }
|
33
|
+
end
|
34
|
+
|
35
|
+
def setup_form_definition(definition)
|
36
|
+
definition.options[:form] = definition.options.delete(:extend)
|
37
|
+
|
38
|
+
definition.options[:parse_strategy] = :sync
|
39
|
+
definition.options[:instance] = true # just to make typed? work
|
40
|
+
end
|
41
|
+
|
42
|
+
def representer_class
|
43
|
+
@representer_class ||= Class.new(Reform::Representer)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
def create_accessor(name)
|
48
|
+
delegate [name, "#{name}="] => :fields
|
49
|
+
end
|
50
|
+
end
|
51
|
+
extend PropertyMethods
|
52
|
+
|
15
53
|
|
16
|
-
|
54
|
+
def initialize(model)
|
55
|
+
@model = model # we need this for #save.
|
56
|
+
@fields = setup_fields(model) # delegate all methods to Fields instance.
|
17
57
|
end
|
18
58
|
|
19
|
-
|
20
|
-
|
21
|
-
|
59
|
+
module ValidateMethods # TODO: introduce Base module.
|
60
|
+
def validate(params)
|
61
|
+
# here it would be cool to have a validator object containing the validation rules representer-like and then pass it the formed model.
|
62
|
+
from_hash(params)
|
63
|
+
|
64
|
+
res = valid? # this validates on <Fields> using AM::Validations, currently.
|
65
|
+
#inject(true) do |res, form| # FIXME: replace that!
|
66
|
+
mapper.new(@fields).nested_forms do |attr, form| #.collect { |attr, form| nested[attr.from] = form }
|
67
|
+
res = validate_for(form, res, attr.from)
|
68
|
+
end
|
69
|
+
|
70
|
+
res
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
def validate_for(form, res, prefix=nil)
|
75
|
+
return res if form.valid? # FIXME: we have to call validate here, otherwise this works only one level deep.
|
76
|
+
|
77
|
+
errors.merge!(form.errors, prefix)
|
78
|
+
false
|
79
|
+
end
|
22
80
|
|
23
|
-
valid? # this validates on <Fields> using AM::Validations, currently.
|
24
81
|
end
|
82
|
+
include ValidateMethods
|
25
83
|
|
26
84
|
def save
|
27
85
|
# DISCUSS: we should never hit @mapper here (which writes to the models) when a block is passed.
|
28
86
|
return yield self, to_nested_hash if block_given?
|
29
87
|
|
30
|
-
|
88
|
+
save_to_models
|
31
89
|
end
|
32
90
|
|
33
91
|
# Use representer to return current key-value form hash.
|
34
|
-
def to_hash
|
92
|
+
def to_hash(*)
|
35
93
|
mapper.new(self).to_hash
|
36
94
|
end
|
37
95
|
|
38
96
|
def to_nested_hash
|
39
|
-
|
97
|
+
symbolize_keys(to_hash)
|
98
|
+
end
|
99
|
+
|
100
|
+
def from_hash(params, *args)
|
101
|
+
mapper.new(self).from_hash(params) # sets form properties found in params on self.
|
102
|
+
end
|
103
|
+
|
104
|
+
def errors
|
105
|
+
@errors ||= Errors.new(self)
|
40
106
|
end
|
41
107
|
|
42
108
|
private
|
43
|
-
attr_accessor :
|
109
|
+
attr_accessor :model, :fields
|
110
|
+
|
111
|
+
def symbolize_keys(hash)
|
112
|
+
hash.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
|
113
|
+
end
|
114
|
+
|
115
|
+
def mapper
|
116
|
+
self.class.representer_class
|
117
|
+
end
|
44
118
|
|
45
|
-
def setup_fields(
|
46
|
-
|
47
|
-
|
119
|
+
def setup_fields(model)
|
120
|
+
representer = Class.new(mapper).new(model)
|
121
|
+
|
122
|
+
setup_nested_forms(representer)
|
48
123
|
|
49
124
|
create_fields(representer.fields, representer.to_hash)
|
50
125
|
end
|
51
126
|
|
127
|
+
def setup_nested_forms(representer)
|
128
|
+
# TODO: we should simply give a FormBuilder instance to representer.to_hash that does this kind of mapping:
|
129
|
+
# after this, Fields contains scalars and Form instances and Forms with form instances.
|
130
|
+
representer.nested_forms do |attr, model|
|
131
|
+
form_class = attr.options[:form]
|
132
|
+
|
133
|
+
attr.options.merge!(
|
134
|
+
:getter => lambda do |*|
|
135
|
+
nested_model = send(attr.getter) # decorated.hit # TODO: use bin.get
|
136
|
+
|
137
|
+
if attr.options[:form_collection]
|
138
|
+
Forms.new(nested_model.collect { |mdl| form_class.new(mdl)})
|
139
|
+
else
|
140
|
+
form_class.new(nested_model)
|
141
|
+
end
|
142
|
+
end,
|
143
|
+
:instance => false, # that's how we make it non-typed?.
|
144
|
+
)
|
145
|
+
end
|
146
|
+
|
147
|
+
#representer.to_hash override: { write: lambda { |doc, value| } }
|
148
|
+
|
149
|
+
# DISCUSS: this would be cool in representable:
|
150
|
+
# to_hash(hit: lambda { |value| form_class.new(..) })
|
151
|
+
|
152
|
+
# steps:
|
153
|
+
# - bin.get
|
154
|
+
# - map that: Forms.new( orig ) <-- override only this in representable (how?)
|
155
|
+
# - mapped.to_hash
|
156
|
+
end
|
157
|
+
|
52
158
|
def create_fields(field_names, fields)
|
53
159
|
Fields.new(field_names, fields)
|
54
160
|
end
|
55
161
|
|
56
|
-
def
|
57
|
-
mapper.new(
|
162
|
+
def save_to_models
|
163
|
+
representer = mapper.new(model)
|
164
|
+
|
165
|
+
representer.nested_forms do |attr, model|
|
166
|
+
attr.options.merge!(
|
167
|
+
:decorator => attr.options[:form].representer_class
|
168
|
+
)
|
169
|
+
|
170
|
+
if attr.options[:form_collection]
|
171
|
+
attr.options.merge!(
|
172
|
+
:collection => true
|
173
|
+
)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
representer.from_hash(to_hash)
|
58
178
|
end
|
59
179
|
|
60
180
|
# FIXME: make AM optional.
|
61
181
|
require 'active_model'
|
62
182
|
include ActiveModel::Validations
|
63
|
-
end
|
64
|
-
|
65
|
-
# Keeps values of the form fields. What's in here is to be displayed in the browser!
|
66
|
-
# we need this intermediate object to display both "original values" and new input from the form after submitting.
|
67
|
-
class Fields < OpenStruct
|
68
|
-
def initialize(properties, values={})
|
69
|
-
fields = properties.inject({}) { |hsh, attr| hsh.merge!(attr => nil) }
|
70
|
-
super(fields.merge!(values)) # TODO: stringify value keys!
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
# Keeps composition of models and knows how to transform a plain hash into a nested hash.
|
75
|
-
class Composition
|
76
|
-
class << self
|
77
|
-
def map(options)
|
78
|
-
@attr2obj = {} # {song: ["title", "track"], artist: ["name"]}
|
79
|
-
|
80
|
-
options.each do |mdl, meths|
|
81
|
-
create_accessors(mdl, meths)
|
82
|
-
attr_reader mdl # FIXME: unless already defined!!
|
83
183
|
|
84
|
-
|
85
|
-
|
184
|
+
# The Errors class is planned to replace AM::Errors. It provides proper nested error messages.
|
185
|
+
class Errors < ActiveModel::Errors
|
186
|
+
def messages
|
187
|
+
return super unless Reform.rails3_0?
|
188
|
+
self
|
86
189
|
end
|
87
190
|
|
88
|
-
#
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
options[cfg.options[:on]] << cfg.name
|
94
|
-
end
|
191
|
+
# def each
|
192
|
+
# messages.each_key do |attribute|
|
193
|
+
# self[attribute].each { |error| yield attribute, Array.wrap(error) }
|
194
|
+
# end
|
195
|
+
# end
|
95
196
|
|
96
|
-
|
97
|
-
|
197
|
+
def merge!(errors, prefix=nil)
|
198
|
+
# TODO: merge into AM.
|
199
|
+
errors.messages.each do |field, msgs|
|
200
|
+
field = "#{prefix}.#{field}" if prefix
|
98
201
|
|
99
|
-
|
100
|
-
@attr2obj.fetch(name.to_s)
|
101
|
-
end
|
202
|
+
msgs = [msgs] if Reform.rails3_0? # DISCUSS: fix in #each?
|
102
203
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
204
|
+
msgs.each do |msg|
|
205
|
+
next if messages[field] and messages[field].include?(msg)
|
206
|
+
add(field, msg)
|
207
|
+
end # Forms now contains a plain errors hash. the errors for each item are still available in item.errors.
|
208
|
+
end
|
107
209
|
end
|
108
210
|
end
|
109
211
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
212
|
+
require "representable/hash/collection"
|
213
|
+
require 'active_model'
|
214
|
+
class Forms < Array # DISCUSS: this should be a Form subclass.
|
215
|
+
include Form::ValidateMethods
|
216
|
+
|
217
|
+
def valid?
|
218
|
+
inject(true) do |res, form|
|
219
|
+
res = validate_for(form, res)
|
117
220
|
end
|
118
221
|
end
|
119
|
-
end
|
120
222
|
|
121
|
-
|
122
|
-
|
123
|
-
instance_variable_set(:"@#{name}", obj)
|
223
|
+
def errors
|
224
|
+
@errors ||= Form::Errors.new(self)
|
124
225
|
end
|
226
|
+
|
227
|
+
# this gives us each { to_hash }
|
228
|
+
include Representable::Hash::Collection
|
229
|
+
items :parse_strategy => :sync, :instance => true
|
125
230
|
end
|
126
231
|
end
|
127
232
|
|
128
|
-
require 'representable/hash'
|
129
|
-
class Representer < Representable::Decorator
|
130
|
-
include Representable::Hash
|
131
233
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
234
|
+
# Keeps values of the form fields. What's in here is to be displayed in the browser!
|
235
|
+
# we need this intermediate object to display both "original values" and new input from the form after submitting.
|
236
|
+
class Fields < OpenStruct
|
237
|
+
def initialize(properties, values={})
|
238
|
+
fields = properties.inject({}) { |hsh, attr| hsh.merge!(attr => nil) }
|
239
|
+
super(fields.merge!(values)) # TODO: stringify value keys!
|
136
240
|
end
|
241
|
+
end
|
137
242
|
|
138
|
-
|
139
|
-
|
140
|
-
representable_attrs.collect { |cfg| cfg.name }
|
141
|
-
end
|
243
|
+
def self.rails3_0?
|
244
|
+
::ActiveModel::VERSION::MAJOR == 3 and ::ActiveModel::VERSION::MINOR == 0
|
142
245
|
end
|
143
246
|
end
|
data/lib/reform/rails.rb
CHANGED
@@ -1,2 +1,9 @@
|
|
1
1
|
require 'reform/form/active_model'
|
2
|
-
|
2
|
+
if defined?(ActiveRecord)
|
3
|
+
require 'reform/form/active_record'
|
4
|
+
end
|
5
|
+
|
6
|
+
Reform::Form.class_eval do # DISCUSS: i'd prefer having a separate Rails module to be mixed into the Form but this is way more convenient for 99% users.
|
7
|
+
include Reform::Form::ActiveModel
|
8
|
+
include Reform::Form::ActiveModel::FormBuilderMethods
|
9
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'representable/hash'
|
2
|
+
require 'representable/decorator'
|
3
|
+
|
4
|
+
module Reform
|
5
|
+
class Representer < Representable::Decorator
|
6
|
+
include Representable::Hash
|
7
|
+
|
8
|
+
# Returns hash of all property names.
|
9
|
+
def fields
|
10
|
+
representable_attrs.map(&:name)
|
11
|
+
end
|
12
|
+
|
13
|
+
def nested_forms(&block)
|
14
|
+
clone_config!.
|
15
|
+
find_all { |attr| attr.options[:form] }.
|
16
|
+
collect { |attr| [attr, represented.send(attr.getter)] }. # DISCUSS: can't we do this with the Binding itself?
|
17
|
+
each(&block)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
def clone_config!
|
22
|
+
# TODO: representable_attrs.clone! which does exactly what's done below.
|
23
|
+
attrs = Representable::Config.new
|
24
|
+
attrs.inherit(representable_attrs) # since in every use case we modify Config we clone.
|
25
|
+
@representable_attrs = attrs
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.inline_representer(base_module, &block) # DISCUSS: separate module?
|
29
|
+
Class.new(Form) do
|
30
|
+
instance_exec &block
|
31
|
+
|
32
|
+
def self.name # FIXME: needed by ActiveModel::Validation - why?
|
33
|
+
"AnonInlineForm"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/reform/version.rb
CHANGED
data/lib/reform.rb
CHANGED
data/reform.gemspec
CHANGED
@@ -18,12 +18,13 @@ 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.7.0"
|
22
22
|
spec.add_dependency "activemodel"
|
23
23
|
spec.add_development_dependency "bundler", "~> 1.3"
|
24
|
-
spec.add_development_dependency "rake"
|
24
|
+
spec.add_development_dependency "rake", ">= 10.1.0"
|
25
25
|
spec.add_development_dependency "minitest"
|
26
26
|
spec.add_development_dependency "activerecord"
|
27
27
|
spec.add_development_dependency "sqlite3"
|
28
28
|
spec.add_development_dependency "virtus"
|
29
|
+
spec.add_development_dependency "rails"
|
29
30
|
end
|