reform-rails 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.travis.yml +15 -2
- data/CHANGES.md +3 -0
- data/Gemfile +2 -0
- data/README.md +11 -1
- data/database.sqlite3 +0 -0
- data/gemfiles/Gemfile.rails-3.1 +7 -0
- data/gemfiles/Gemfile.rails-3.1.lock +153 -0
- data/gemfiles/Gemfile.rails-3.2 +7 -0
- data/gemfiles/Gemfile.rails-4.0 +8 -0
- data/gemfiles/Gemfile.rails-4.1 +8 -0
- data/lib/reform/active_record.rb +4 -0
- data/lib/reform/form/active_model.rb +87 -0
- data/lib/reform/form/active_model/form_builder_methods.rb +48 -0
- data/lib/reform/form/active_model/model_reflections.rb +46 -0
- data/lib/reform/form/active_model/model_validations.rb +110 -0
- data/lib/reform/form/active_model/validations.rb +107 -0
- data/lib/reform/form/active_record.rb +30 -0
- data/lib/reform/form/multi_parameter_attributes.rb +48 -0
- data/lib/reform/form/validation/unique_validator.rb +54 -0
- data/lib/reform/rails.rb +10 -1
- data/lib/reform/rails/version.rb +1 -1
- data/reform-rails.gemspec +9 -1
- metadata +117 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 508979fd5b00727656341a9e9f917d85c4510000
|
4
|
+
data.tar.gz: 9bfa49a04a089b30d05022211a5b7b8bbaff8706
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2382c4784a2d6e7072b1ca796aaf44099cb3d2cf29dfa42e963db73216fcb9cf025aa09a1cf15a5e3e373244c29de2685e3f3262998ddf0e7aae06ff9cf1b661
|
7
|
+
data.tar.gz: f08da6791134be71db3891e05629d639ab61550cb3074478e10970b7ba2b18b70593d5a372c825e343c039eca61ba74935d822465ddd3172715895bc2966202a
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -1,4 +1,17 @@
|
|
1
1
|
language: ruby
|
2
2
|
rvm:
|
3
|
-
- 2.2.
|
4
|
-
|
3
|
+
- 2.2.3
|
4
|
+
- 2.0.0
|
5
|
+
services:
|
6
|
+
- mongodb
|
7
|
+
gemfile:
|
8
|
+
- gemfiles/Gemfile.rails-4.2
|
9
|
+
- gemfiles/Gemfile.rails-4.1
|
10
|
+
- gemfiles/Gemfile.rails-4.0
|
11
|
+
- gemfiles/Gemfile.rails-3.2
|
12
|
+
- gemfiles/Gemfile.rails-3.1
|
13
|
+
# - gemfiles/Gemfile.rails-3.0
|
14
|
+
matrix:
|
15
|
+
fast_finish: true
|
16
|
+
before_install:
|
17
|
+
- gem install bundler
|
data/CHANGES.md
ADDED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,8 +1,16 @@
|
|
1
1
|
# Reform::Rails
|
2
2
|
|
3
|
+
[![Gitter Chat](https://badges.gitter.im/trailblazer/chat.svg)](https://gitter.im/trailblazer/chat)
|
4
|
+
[![TRB Newsletter](https://img.shields.io/badge/TRB-newsletter-lightgrey.svg)](http://trailblazer.to/newsletter/)
|
5
|
+
[![Build
|
6
|
+
Status](https://travis-ci.org/apotonick/reform-rails.svg)](https://travis-ci.org/apotonick/reform-rails)
|
7
|
+
[![Gem Version](https://badge.fury.io/rb/reform-rails.svg)](http://badge.fury.io/rb/reform-rails)
|
8
|
+
|
9
|
+
_Rails-support for Reform_.
|
10
|
+
|
3
11
|
Loads Rails-specific Reform files and includes modules like `Reform::Form::ActiveModel` automatically.
|
4
12
|
|
5
|
-
Simply don't include this gem if you don't want to use the
|
13
|
+
Simply don't include this gem if you don't want to use the conventional Reform/Rails stack. For example in a Hanami environment or when using dry-validations, refrain from using this gem.
|
6
14
|
|
7
15
|
## Installation
|
8
16
|
|
@@ -12,6 +20,8 @@ Add this line to your application's Gemfile:
|
|
12
20
|
gem 'reform-rails'
|
13
21
|
```
|
14
22
|
|
23
|
+
Reform-rails needs Reform >= 2.2.
|
24
|
+
|
15
25
|
## License
|
16
26
|
|
17
27
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/database.sqlite3
ADDED
Binary file
|
@@ -0,0 +1,153 @@
|
|
1
|
+
PATH
|
2
|
+
remote: ../
|
3
|
+
specs:
|
4
|
+
reform (2.1.0.rc1)
|
5
|
+
disposable (>= 0.2.0.rc1, < 0.3.0)
|
6
|
+
representable (>= 2.4.0.rc1, < 2.5.0)
|
7
|
+
uber (~> 0.0.11)
|
8
|
+
|
9
|
+
PATH
|
10
|
+
remote: ../../declarative
|
11
|
+
specs:
|
12
|
+
declarative (0.0.4)
|
13
|
+
uber (>= 0.0.15)
|
14
|
+
|
15
|
+
GEM
|
16
|
+
remote: http://rubygems.org/
|
17
|
+
specs:
|
18
|
+
actionmailer (3.1.12)
|
19
|
+
actionpack (= 3.1.12)
|
20
|
+
mail (~> 2.4.4)
|
21
|
+
actionpack (3.1.12)
|
22
|
+
activemodel (= 3.1.12)
|
23
|
+
activesupport (= 3.1.12)
|
24
|
+
builder (~> 3.0.0)
|
25
|
+
erubis (~> 2.7.0)
|
26
|
+
i18n (~> 0.6)
|
27
|
+
rack (~> 1.3.6)
|
28
|
+
rack-cache (~> 1.2)
|
29
|
+
rack-mount (~> 0.8.2)
|
30
|
+
rack-test (~> 0.6.1)
|
31
|
+
sprockets (~> 2.0.4)
|
32
|
+
activemodel (3.1.12)
|
33
|
+
activesupport (= 3.1.12)
|
34
|
+
builder (~> 3.0.0)
|
35
|
+
i18n (~> 0.6)
|
36
|
+
activerecord (3.1.12)
|
37
|
+
activemodel (= 3.1.12)
|
38
|
+
activesupport (= 3.1.12)
|
39
|
+
arel (~> 2.2.3)
|
40
|
+
tzinfo (~> 0.3.29)
|
41
|
+
activeresource (3.1.12)
|
42
|
+
activemodel (= 3.1.12)
|
43
|
+
activesupport (= 3.1.12)
|
44
|
+
activesupport (3.1.12)
|
45
|
+
multi_json (~> 1.0)
|
46
|
+
arel (2.2.3)
|
47
|
+
axiom-types (0.1.1)
|
48
|
+
descendants_tracker (~> 0.0.4)
|
49
|
+
ice_nine (~> 0.11.0)
|
50
|
+
thread_safe (~> 0.3, >= 0.3.1)
|
51
|
+
builder (3.0.4)
|
52
|
+
coercible (1.0.0)
|
53
|
+
descendants_tracker (~> 0.0.1)
|
54
|
+
descendants_tracker (0.0.4)
|
55
|
+
thread_safe (~> 0.3, >= 0.3.1)
|
56
|
+
disposable (0.2.0.rc1)
|
57
|
+
declarative (~> 0.0.4)
|
58
|
+
representable (>= 2.4.0.rc1, < 2.5.0)
|
59
|
+
uber
|
60
|
+
equalizer (0.0.11)
|
61
|
+
erubis (2.7.0)
|
62
|
+
hike (1.2.3)
|
63
|
+
i18n (0.7.0)
|
64
|
+
ice_nine (0.11.1)
|
65
|
+
json (1.8.3)
|
66
|
+
lotus-utils (0.5.2)
|
67
|
+
lotus-validations (0.3.3)
|
68
|
+
lotus-utils (~> 0.5)
|
69
|
+
mail (2.4.4)
|
70
|
+
i18n (>= 0.4.0)
|
71
|
+
mime-types (~> 1.16)
|
72
|
+
treetop (~> 1.4.8)
|
73
|
+
mime-types (1.25.1)
|
74
|
+
minitest (5.8.2)
|
75
|
+
mongoid (3.0.23)
|
76
|
+
activemodel (~> 3.1)
|
77
|
+
moped (~> 1.2)
|
78
|
+
origin (~> 1.0)
|
79
|
+
tzinfo (~> 0.3.22)
|
80
|
+
moped (1.5.3)
|
81
|
+
multi_json (1.11.2)
|
82
|
+
origin (1.1.0)
|
83
|
+
polyglot (0.3.5)
|
84
|
+
rack (1.3.10)
|
85
|
+
rack-cache (1.5.1)
|
86
|
+
rack (>= 0.4)
|
87
|
+
rack-mount (0.8.3)
|
88
|
+
rack (>= 1.0.0)
|
89
|
+
rack-ssl (1.3.4)
|
90
|
+
rack
|
91
|
+
rack-test (0.6.3)
|
92
|
+
rack (>= 1.0)
|
93
|
+
rails (3.1.12)
|
94
|
+
actionmailer (= 3.1.12)
|
95
|
+
actionpack (= 3.1.12)
|
96
|
+
activerecord (= 3.1.12)
|
97
|
+
activeresource (= 3.1.12)
|
98
|
+
activesupport (= 3.1.12)
|
99
|
+
bundler (~> 1.0)
|
100
|
+
railties (= 3.1.12)
|
101
|
+
railties (3.1.12)
|
102
|
+
actionpack (= 3.1.12)
|
103
|
+
activesupport (= 3.1.12)
|
104
|
+
rack-ssl (~> 1.3.2)
|
105
|
+
rake (>= 0.8.7)
|
106
|
+
rdoc (~> 3.4)
|
107
|
+
thor (~> 0.14.6)
|
108
|
+
rake (10.4.2)
|
109
|
+
rdoc (3.12.2)
|
110
|
+
json (~> 1.4)
|
111
|
+
representable (2.4.0.rc1)
|
112
|
+
declarative (~> 0.0.4)
|
113
|
+
uber (~> 0.0.15)
|
114
|
+
sprockets (2.0.5)
|
115
|
+
hike (~> 1.2)
|
116
|
+
rack (~> 1.0)
|
117
|
+
tilt (~> 1.1, != 1.3.0)
|
118
|
+
sqlite3 (1.3.11)
|
119
|
+
thor (0.14.6)
|
120
|
+
thread_safe (0.3.5)
|
121
|
+
tilt (1.4.1)
|
122
|
+
treetop (1.4.15)
|
123
|
+
polyglot
|
124
|
+
polyglot (>= 0.3.1)
|
125
|
+
tzinfo (0.3.45)
|
126
|
+
uber (0.0.15)
|
127
|
+
virtus (1.0.5)
|
128
|
+
axiom-types (~> 0.1)
|
129
|
+
coercible (~> 1.0)
|
130
|
+
descendants_tracker (~> 0.0, >= 0.0.3)
|
131
|
+
equalizer (~> 0.0, >= 0.0.9)
|
132
|
+
|
133
|
+
PLATFORMS
|
134
|
+
ruby
|
135
|
+
|
136
|
+
DEPENDENCIES
|
137
|
+
actionpack
|
138
|
+
activerecord (~> 3.1.0)
|
139
|
+
bundler
|
140
|
+
declarative!
|
141
|
+
lotus-validations
|
142
|
+
minitest
|
143
|
+
mongoid
|
144
|
+
multi_json
|
145
|
+
rails
|
146
|
+
railties (~> 3.1.0)
|
147
|
+
rake
|
148
|
+
reform!
|
149
|
+
sqlite3
|
150
|
+
virtus
|
151
|
+
|
152
|
+
BUNDLED WITH
|
153
|
+
1.10.6
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require "reform/form/active_model/model_validations"
|
2
|
+
require "reform/form/active_model/form_builder_methods"
|
3
|
+
require "uber/delegates"
|
4
|
+
|
5
|
+
module Reform::Form::ActiveModel
|
6
|
+
def self.included(base)
|
7
|
+
base.class_eval do
|
8
|
+
extend ClassMethods
|
9
|
+
register_feature ActiveModel
|
10
|
+
|
11
|
+
extend Uber::Delegates
|
12
|
+
delegates :model, *[:persisted?, :to_key, :to_param, :id] # Uber::Delegates
|
13
|
+
|
14
|
+
def to_model # this is called somewhere in FormBuilder and ActionController.
|
15
|
+
self
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
module ClassMethods
|
22
|
+
# this module is only meant to extend (not include). # DISCUSS: is this a sustainable concept?
|
23
|
+
def self.extended(base)
|
24
|
+
base.class_eval do
|
25
|
+
extend Uber::InheritableAttribute
|
26
|
+
inheritable_attr :model_options
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# DISCUSS: can we achieve that somehow via features in build_inline?
|
31
|
+
def property(*)
|
32
|
+
super.tap do |dfn|
|
33
|
+
return dfn unless dfn[:nested]
|
34
|
+
_name = dfn[:name]
|
35
|
+
dfn[:nested].instance_eval do
|
36
|
+
@_name = _name.singularize.camelize
|
37
|
+
# this adds Form::name for AM::Validations and I18N.
|
38
|
+
def name
|
39
|
+
@_name
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
# Set a model name for this form if the infered is wrong.
|
47
|
+
#
|
48
|
+
# class CoverSongForm < Reform::Form
|
49
|
+
# model :song
|
50
|
+
#
|
51
|
+
# or we can setup a isolated namespace model ( which defined in isolated rails egine )
|
52
|
+
#
|
53
|
+
# class CoverSongForm < Reform::Form
|
54
|
+
# model "api/v1/song", namespace: "api"
|
55
|
+
def model(main_model, options={})
|
56
|
+
self.model_options = [main_model, options]
|
57
|
+
end
|
58
|
+
|
59
|
+
def model_name
|
60
|
+
if model_options
|
61
|
+
form_name = model_options.first.to_s.camelize
|
62
|
+
namespace = model_options.last[:namespace].present? ? model_options.last[:namespace].to_s.camelize.constantize : nil
|
63
|
+
else
|
64
|
+
if name
|
65
|
+
form_name = name.sub(/(::)?Form$/, "") # Song::Form => "Song"
|
66
|
+
namespace = nil
|
67
|
+
else # anonymous forms. let's drop AM and forget about all this.
|
68
|
+
form_name = "reform"
|
69
|
+
namespace = nil
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
active_model_name_for(form_name, namespace)
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
def active_model_name_for(string, namespace=nil)
|
78
|
+
return ::ActiveModel::Name.new(OpenStruct.new(:name => string)) if Reform.rails3_0?
|
79
|
+
::ActiveModel::Name.new(self, namespace, string)
|
80
|
+
end
|
81
|
+
end # ClassMethods
|
82
|
+
|
83
|
+
|
84
|
+
def model_name(*args)
|
85
|
+
self.class.model_name(*args)
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Reform::Form::ActiveModel
|
2
|
+
# Including FormBuilderMethods will allow using form instances with form_for, simple_form, etc.
|
3
|
+
# in Rails. It will further try to translate Rails' suboptimal songs_attributes weirdness
|
4
|
+
# back to normal `songs: ` naming in +#valiate+.
|
5
|
+
module FormBuilderMethods
|
6
|
+
def self.included(base)
|
7
|
+
base.extend ClassMethods # ::model_name
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
private
|
12
|
+
|
13
|
+
# TODO: add that shit in Form#present, not by overriding ::property.
|
14
|
+
def property(name, options={}, &block)
|
15
|
+
super.tap do |definition|
|
16
|
+
add_nested_attribute_compat(name) if definition[:nested] # TODO: fix that in Rails FB#1832 work.
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# The Rails FormBuilder "detects" nested attributes (which is what we want) by checking existance of a setter method.
|
21
|
+
def add_nested_attribute_compat(name)
|
22
|
+
define_method("#{name}_attributes=") {} # this is why i hate respond_to? in Rails.
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Modify the incoming Rails params hash to be representable compliant.
|
27
|
+
def deserialize!(params)
|
28
|
+
# this only happens in a Hash environment. other engines have to overwrite this method.
|
29
|
+
schema.each do |dfn|
|
30
|
+
rename_nested_param_for!(params, dfn)
|
31
|
+
end
|
32
|
+
|
33
|
+
super(params)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
def rename_nested_param_for!(params, dfn)
|
38
|
+
name = dfn[:name]
|
39
|
+
nested_name = "#{name}_attributes"
|
40
|
+
return unless params.has_key?(nested_name)
|
41
|
+
|
42
|
+
value = params["#{name}_attributes"]
|
43
|
+
value = value.values if dfn[:collection]
|
44
|
+
|
45
|
+
params[name] = value
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# ModelReflections will be the interface between the form object and form builders like simple_form.
|
2
|
+
#
|
3
|
+
# This module is meant to collect all dependencies simple_form needs in addition to the ActiveModel ones.
|
4
|
+
# Goal is to collect all methods and define a reflection API so simple_form works with all ORMs and Reform
|
5
|
+
# doesn't have to "guess" what simple_form and other form helpers need.
|
6
|
+
class Reform::Form < Reform::Contract
|
7
|
+
module ActiveModel::ModelReflections
|
8
|
+
def self.included(base)
|
9
|
+
base.extend ClassMethods
|
10
|
+
base.send :register_feature, self # makes it work in nested forms.
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
# Delegate reflect_on_association to the model class to support simple_form's
|
15
|
+
# association input.
|
16
|
+
def reflect_on_association(*args)
|
17
|
+
model_name.to_s.constantize.reflect_on_association(*args)
|
18
|
+
end
|
19
|
+
|
20
|
+
# this is needed in simpleform to infer required fields.
|
21
|
+
def validators_on(*args)
|
22
|
+
validation_groups.collect { |k, group| group.instance_variable_get(:@validations).validators_on(*args) }.flatten
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Delegate column for attribute to the model to support simple_form's
|
27
|
+
# attribute type interrogation.
|
28
|
+
def column_for_attribute(name)
|
29
|
+
model_for_property(name).column_for_attribute(name)
|
30
|
+
end
|
31
|
+
|
32
|
+
def has_attribute?(name)
|
33
|
+
model_for_property(name).has_attribute?(name)
|
34
|
+
end
|
35
|
+
|
36
|
+
def defined_enums
|
37
|
+
return model.defined_enums unless is_a?(Reform::Form::Composition)
|
38
|
+
|
39
|
+
mapper.each.with_object({}) { |m,h| h.merge! m.defined_enums }
|
40
|
+
end
|
41
|
+
|
42
|
+
# this should also contain to_param and friends as this is used by the form helpers.
|
43
|
+
end
|
44
|
+
|
45
|
+
ModelReflections = ActiveModel::ModelReflections
|
46
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module Reform::Form::ActiveModel
|
2
|
+
module ModelValidations
|
3
|
+
# TODO: extract Composition behaviour.
|
4
|
+
# reduce code in Mapping.
|
5
|
+
|
6
|
+
class ValidationCopier
|
7
|
+
|
8
|
+
def self.copy(form_class, mapping, models)
|
9
|
+
if models.is_a?(Hash)
|
10
|
+
models.each do |model_name, model|
|
11
|
+
new(form_class, mapping, model, model_name).copy
|
12
|
+
end
|
13
|
+
else
|
14
|
+
new(form_class, mapping, models).copy
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(form_class, mapping, model, model_name=nil)
|
19
|
+
@form_class = form_class
|
20
|
+
@mapping = mapping
|
21
|
+
@model = model
|
22
|
+
@model_name = model_name
|
23
|
+
end
|
24
|
+
|
25
|
+
def copy
|
26
|
+
@model.validators.each(&method(:add_validator))
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def add_validator(validator)
|
32
|
+
if validator.respond_to?(:attributes)
|
33
|
+
add_native_validator validator
|
34
|
+
else
|
35
|
+
add_custom_validator validator
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def add_native_validator validator
|
40
|
+
attributes = inverse_map_attributes(validator.attributes)
|
41
|
+
if attributes.any?
|
42
|
+
@form_class.validates(*attributes, {validator.kind => validator.options})
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def add_custom_validator validator
|
47
|
+
@form_class.validates(nil, {validator.kind => validator.options})
|
48
|
+
end
|
49
|
+
|
50
|
+
def inverse_map_attributes(attributes)
|
51
|
+
@mapping.inverse_image(create_attributes(attributes))
|
52
|
+
end
|
53
|
+
|
54
|
+
def create_attributes(attributes)
|
55
|
+
attributes.map do |attribute|
|
56
|
+
[@model_name, attribute].compact
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
class Mapping
|
63
|
+
def self.from_representable_attrs(attrs)
|
64
|
+
new.tap do |mapping|
|
65
|
+
attrs.each do |dfn|
|
66
|
+
from = dfn[:name].to_sym
|
67
|
+
to = [dfn[:on], (dfn[:private_name] || dfn[:name])].compact.map(&:to_sym)
|
68
|
+
mapping.add(from, to)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def initialize
|
74
|
+
@forward_map = {}
|
75
|
+
@inverse_map = {}
|
76
|
+
end
|
77
|
+
|
78
|
+
# from is a symbol attribute
|
79
|
+
# to is an 1 or 2 element array, depending on whether the attribute is 'namespaced', as it is with composite forms.
|
80
|
+
# eg, add(:phone_number, [:person, :phone])
|
81
|
+
def add(from, to)
|
82
|
+
raise 'Mapping is not one-to-one' if @forward_map.has_key?(from) || @inverse_map.has_key?(to)
|
83
|
+
@forward_map[from] = to
|
84
|
+
@inverse_map[to] = from
|
85
|
+
end
|
86
|
+
|
87
|
+
def forward_image(attrs)
|
88
|
+
@forward_map.values_at(*attrs).compact
|
89
|
+
end
|
90
|
+
|
91
|
+
def forward(attr)
|
92
|
+
@forward_map[attr]
|
93
|
+
end
|
94
|
+
|
95
|
+
def inverse_image(attrs)
|
96
|
+
@inverse_map.values_at(*attrs).compact
|
97
|
+
end
|
98
|
+
|
99
|
+
def inverse(attr)
|
100
|
+
@inverse_map[attr]
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
def copy_validations_from(models)
|
106
|
+
ValidationCopier.copy(self, Mapping.from_representable_attrs(definitions), models)
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require "active_model"
|
2
|
+
require "uber/delegates"
|
3
|
+
|
4
|
+
module Reform::Form::ActiveModel
|
5
|
+
# AM::Validations for your form.
|
6
|
+
# Provides ::validates, ::validate, #validate, and #valid?.
|
7
|
+
#
|
8
|
+
# Most of this file contains unnecessary wiring to make ActiveModel's error message magic work.
|
9
|
+
# Since Rails still thinks it's a good idea to do things like object.class.human_attribute_name,
|
10
|
+
# we have some hacks in here to provide that. If it doesn't work for you, don't blame us.
|
11
|
+
module Validations
|
12
|
+
def self.included(includer)
|
13
|
+
includer.instance_eval do
|
14
|
+
include Reform::Form::ActiveModel
|
15
|
+
|
16
|
+
class << self
|
17
|
+
extend Uber::Delegates
|
18
|
+
# # Hooray! Delegate translation back to Reform's Validator class which contains AM::Validations.
|
19
|
+
delegates :active_model_really_sucks, :human_attribute_name, :lookup_ancestors, :i18n_scope # Rails 3.1.
|
20
|
+
|
21
|
+
def validation_group_class
|
22
|
+
Group
|
23
|
+
end
|
24
|
+
|
25
|
+
# this is to allow calls like Form::human_attribute_name (note that this is on the CLASS level) to be resolved.
|
26
|
+
# those calls happen when adding errors in a custom validation method, which is defined on the form (as an instance method).
|
27
|
+
def active_model_really_sucks
|
28
|
+
Class.new(Validator).tap do |v|
|
29
|
+
v.model_name = model_name
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end # ::included
|
34
|
+
end
|
35
|
+
|
36
|
+
def build_errors
|
37
|
+
Errors.new(self)
|
38
|
+
end
|
39
|
+
|
40
|
+
# The concept of "composition" has still not arrived in Rails core and they rely on 400 methods being
|
41
|
+
# available in one object. This is why we need to provide parts of the I18N API in the form.
|
42
|
+
def read_attribute_for_validation(name)
|
43
|
+
send(name)
|
44
|
+
end
|
45
|
+
|
46
|
+
class Group
|
47
|
+
def initialize
|
48
|
+
@validations = Class.new(Reform::Form::ActiveModel::Validations::Validator)
|
49
|
+
end
|
50
|
+
|
51
|
+
extend Uber::Delegates
|
52
|
+
delegates :@validations, :validates, :validate, :validates_with, :validate_with
|
53
|
+
|
54
|
+
def call(fields, errors, form) # FIXME.
|
55
|
+
validator = @validations.new(form)
|
56
|
+
validator.valid?
|
57
|
+
|
58
|
+
validator.errors.each do |name, error| # TODO: handle with proper merge, or something. validator.errors is ALWAYS AM::Errors.
|
59
|
+
errors.add(name, error)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
# Validator is the validatable object. On the class level, we define validations,
|
66
|
+
# on instance, it exposes #valid?.
|
67
|
+
require "delegate"
|
68
|
+
class Validator < SimpleDelegator
|
69
|
+
# current i18n scope: :activemodel.
|
70
|
+
include ActiveModel::Validations
|
71
|
+
|
72
|
+
class << self
|
73
|
+
def model_name
|
74
|
+
@_active_model_sucks ||= ActiveModel::Name.new(Reform::Form, nil, "Reform::Form")
|
75
|
+
end
|
76
|
+
|
77
|
+
def model_name=(name)
|
78
|
+
@_active_model_sucks = name
|
79
|
+
end
|
80
|
+
|
81
|
+
def validates(*args, &block)
|
82
|
+
super(*Declarative::DeepDup.(args), &block)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Prevent AM:V from mutating the validator class
|
86
|
+
def attr_reader(*)
|
87
|
+
end
|
88
|
+
|
89
|
+
def attr_writer(*)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def initialize(form)
|
94
|
+
super(form)
|
95
|
+
self.class.model_name = form.model_name # one of the many reasons why i will drop support for AM::V in 2.1. or maybe a bit later.
|
96
|
+
end
|
97
|
+
|
98
|
+
def method_missing(m, *args, &block)
|
99
|
+
__getobj__.send(m, *args, &block) # send all methods to the form, even privates.
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
class Errors < ActiveModel::Errors
|
105
|
+
include Reform::Form::Errors::Merge
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require "reform/form/orm"
|
2
|
+
|
3
|
+
module Reform::Form::ActiveRecord
|
4
|
+
def self.included(base)
|
5
|
+
base.class_eval do
|
6
|
+
register_feature Reform::Form::ActiveRecord
|
7
|
+
include Reform::Form::ActiveModel
|
8
|
+
include Reform::Form::ORM
|
9
|
+
extend ClassMethods
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
def validates_uniqueness_of(attribute, options={})
|
15
|
+
options = options.merge(:attributes => [attribute])
|
16
|
+
validates_with(UniquenessValidator, options)
|
17
|
+
end
|
18
|
+
def i18n_scope
|
19
|
+
:activerecord
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_nested_hash(*)
|
24
|
+
super.with_indifferent_access
|
25
|
+
end
|
26
|
+
|
27
|
+
class UniquenessValidator < ::ActiveRecord::Validations::UniquenessValidator
|
28
|
+
include Reform::Form::ORM::UniquenessValidator
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Reform::Form::MultiParameterAttributes
|
2
|
+
# TODO: implement this with parse_filter, so we don't have to manually walk through the hash, etc.
|
3
|
+
class DateTimeParamsFilter
|
4
|
+
def call(params)
|
5
|
+
params = params.dup # DISCUSS: not sure if that slows down form processing?
|
6
|
+
date_attributes = {}
|
7
|
+
|
8
|
+
params.each do |attribute, value|
|
9
|
+
if value.is_a?(Hash)
|
10
|
+
params[attribute] = call(value) # TODO: #validate should only handle local form params.
|
11
|
+
elsif matches = attribute.match(/^(\w+)\(.i\)$/)
|
12
|
+
date_attribute = matches[1]
|
13
|
+
date_attributes[date_attribute] = params_to_date(
|
14
|
+
params.delete("#{date_attribute}(1i)"),
|
15
|
+
params.delete("#{date_attribute}(2i)"),
|
16
|
+
params.delete("#{date_attribute}(3i)"),
|
17
|
+
params.delete("#{date_attribute}(4i)"),
|
18
|
+
params.delete("#{date_attribute}(5i)")
|
19
|
+
)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
params.merge!(date_attributes)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
def params_to_date(year, month, day, hour, minute)
|
27
|
+
date_fields = [year, month, day].map!(&:to_i)
|
28
|
+
time_fields = [hour, minute].map!(&:to_i)
|
29
|
+
|
30
|
+
if date_fields.any?(&:zero?) || !Date.valid_date?(*date_fields)
|
31
|
+
return nil
|
32
|
+
end
|
33
|
+
|
34
|
+
if hour.blank? && minute.blank?
|
35
|
+
Date.new(*date_fields)
|
36
|
+
else
|
37
|
+
args = date_fields + time_fields
|
38
|
+
Time.zone ? Time.zone.local(*args) :
|
39
|
+
Time.new(*args)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# this hooks into the format-specific #deserialize! method.
|
45
|
+
def deserialize!(params)
|
46
|
+
super DateTimeParamsFilter.new.call(params) # if params.is_a?(Hash) # this currently works for hash, only.
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# === Unique Validation
|
2
|
+
# Reform's own implementation for uniqueness which does not write to model
|
3
|
+
#
|
4
|
+
# == Usage
|
5
|
+
# Pass a true boolean value to validate a field against all values available in
|
6
|
+
# the database:
|
7
|
+
# validates :title, unique: true
|
8
|
+
#
|
9
|
+
# == Options
|
10
|
+
# = Scope
|
11
|
+
# A scope can be use to filter the records that need to be compare with the
|
12
|
+
# current value to validate. A scope array can have one to many fields define.
|
13
|
+
#
|
14
|
+
# A scope can be define the following ways:
|
15
|
+
# validates :title, unique: { scope: :album_id }
|
16
|
+
# validates :title, unique: { scope: [:album_id] }
|
17
|
+
# validates :title, unique: { scope: [:album_id, ...] }
|
18
|
+
#
|
19
|
+
# All fields included in a scope must be declared as a property like this:
|
20
|
+
# property :album_id
|
21
|
+
# validates :title, unique: { scope: :album_id }
|
22
|
+
#
|
23
|
+
# Just remove write access to the property if the field must not be change:
|
24
|
+
# property :album_id, writeable: false
|
25
|
+
# validates :title, unique: { scope: :album_id }
|
26
|
+
#
|
27
|
+
# This use case is useful if album_id is set to a Song this way:
|
28
|
+
# song = album.songs.new
|
29
|
+
# album_id is automatically set and can't be change by the operation
|
30
|
+
|
31
|
+
class Reform::Form::UniqueValidator < ActiveModel::EachValidator
|
32
|
+
def validate_each(form, attribute, value)
|
33
|
+
model = form.model_for_property(attribute)
|
34
|
+
|
35
|
+
# search for models with attribute equals to form field value
|
36
|
+
query = model.class.where(attribute => value)
|
37
|
+
|
38
|
+
# apply scope if options has been declared
|
39
|
+
Array(options[:scope]).each do |field|
|
40
|
+
# add condition to only check unique value with the same scope
|
41
|
+
query = query.where(field => form.send(field))
|
42
|
+
end
|
43
|
+
|
44
|
+
# if model persisted, query may return 0 or 1 rows, else 0
|
45
|
+
allow_count = model.persisted? ? 1 : 0
|
46
|
+
form.errors.add(attribute, :taken) if query.count > allow_count
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# FIXME: ActiveModel loads validators via const_get(#{name}Validator). This magic forces us to
|
51
|
+
# make the new :unique validator available here.
|
52
|
+
Reform::Form::ActiveModel::Validations::Validator.class_eval do
|
53
|
+
UniqueValidator = Reform::Form::UniqueValidator
|
54
|
+
end
|
data/lib/reform/rails.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
1
|
require "reform/rails/version"
|
2
2
|
|
3
|
+
require "reform"
|
3
4
|
require "reform/form/active_model"
|
4
5
|
require "reform/form/active_model/validations"
|
6
|
+
require "reform/form/multi_parameter_attributes"
|
7
|
+
|
5
8
|
|
6
9
|
require "reform/active_record" if defined?(ActiveRecord)
|
7
10
|
require "reform/mongoid" if defined?(Mongoid)
|
@@ -12,4 +15,10 @@ Reform::Form.class_eval do
|
|
12
15
|
include Reform::Form::ActiveRecord if defined?(ActiveRecord)
|
13
16
|
include Reform::Form::Mongoid if defined?(Mongoid)
|
14
17
|
include Reform::Form::ActiveModel::Validations
|
15
|
-
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module Reform
|
21
|
+
def self.rails3_0?
|
22
|
+
::ActiveModel::VERSION::MAJOR == 3 and ::ActiveModel::VERSION::MINOR == 0
|
23
|
+
end
|
24
|
+
end
|
data/lib/reform/rails/version.rb
CHANGED
data/reform-rails.gemspec
CHANGED
@@ -11,14 +11,22 @@ Gem::Specification.new do |spec|
|
|
11
11
|
|
12
12
|
spec.summary = %q{Automatically load and include all common Rails form features.}
|
13
13
|
spec.description = %q{Automatically load and include all common Reform features for a standard Rails environment.}
|
14
|
-
spec.homepage = "
|
14
|
+
spec.homepage = "https://github.com/trailblazer/reform-rails"
|
15
15
|
spec.license = "MIT"
|
16
16
|
|
17
17
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
18
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
+
spec.add_dependency "reform", ">= 2.2.0"
|
22
|
+
spec.add_dependency "activemodel", ">= 3.2"
|
23
|
+
|
24
|
+
spec.add_development_dependency "rails"
|
21
25
|
spec.add_development_dependency "bundler", "~> 1.10"
|
22
26
|
spec.add_development_dependency "rake", "~> 10.0"
|
23
27
|
spec.add_development_dependency "minitest"
|
28
|
+
spec.add_development_dependency "actionpack"
|
29
|
+
spec.add_development_dependency "activerecord"
|
30
|
+
spec.add_development_dependency "mongoid"
|
31
|
+
spec.add_development_dependency "sqlite3"
|
24
32
|
end
|
metadata
CHANGED
@@ -1,15 +1,57 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: reform-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nick Sutterer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-06-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: reform
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 2.2.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 2.2.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activemodel
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.2'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rails
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
13
55
|
- !ruby/object:Gem::Dependency
|
14
56
|
name: bundler
|
15
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -52,6 +94,62 @@ dependencies:
|
|
52
94
|
- - ">="
|
53
95
|
- !ruby/object:Gem::Version
|
54
96
|
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: actionpack
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: activerecord
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: mongoid
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: sqlite3
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
55
153
|
description: Automatically load and include all common Reform features for a standard
|
56
154
|
Rails environment.
|
57
155
|
email:
|
@@ -62,14 +160,30 @@ extra_rdoc_files: []
|
|
62
160
|
files:
|
63
161
|
- ".gitignore"
|
64
162
|
- ".travis.yml"
|
163
|
+
- CHANGES.md
|
65
164
|
- Gemfile
|
66
165
|
- LICENSE.txt
|
67
166
|
- README.md
|
68
167
|
- Rakefile
|
168
|
+
- database.sqlite3
|
169
|
+
- gemfiles/Gemfile.rails-3.1
|
170
|
+
- gemfiles/Gemfile.rails-3.1.lock
|
171
|
+
- gemfiles/Gemfile.rails-3.2
|
172
|
+
- gemfiles/Gemfile.rails-4.0
|
173
|
+
- gemfiles/Gemfile.rails-4.1
|
174
|
+
- lib/reform/active_record.rb
|
175
|
+
- lib/reform/form/active_model.rb
|
176
|
+
- lib/reform/form/active_model/form_builder_methods.rb
|
177
|
+
- lib/reform/form/active_model/model_reflections.rb
|
178
|
+
- lib/reform/form/active_model/model_validations.rb
|
179
|
+
- lib/reform/form/active_model/validations.rb
|
180
|
+
- lib/reform/form/active_record.rb
|
181
|
+
- lib/reform/form/multi_parameter_attributes.rb
|
182
|
+
- lib/reform/form/validation/unique_validator.rb
|
69
183
|
- lib/reform/rails.rb
|
70
184
|
- lib/reform/rails/version.rb
|
71
185
|
- reform-rails.gemspec
|
72
|
-
homepage:
|
186
|
+
homepage: https://github.com/trailblazer/reform-rails
|
73
187
|
licenses:
|
74
188
|
- MIT
|
75
189
|
metadata: {}
|
@@ -94,4 +208,3 @@ signing_key:
|
|
94
208
|
specification_version: 4
|
95
209
|
summary: Automatically load and include all common Rails form features.
|
96
210
|
test_files: []
|
97
|
-
has_rdoc:
|