reform 2.1.0 → 2.2.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +0 -1
- data/.travis.yml +4 -12
- data/CHANGES.md +8 -0
- data/README.md +36 -743
- data/Rakefile +1 -31
- data/gemfiles/{Gemfile.rails-3.1 → Gemfile.disposable-0.3} +1 -2
- data/lib/reform.rb +0 -9
- data/lib/reform/contract.rb +5 -1
- data/lib/reform/form.rb +1 -4
- data/lib/reform/form/composition.rb +1 -2
- data/lib/reform/form/dry.rb +29 -16
- data/lib/reform/form/module.rb +15 -3
- data/lib/reform/form/validate.rb +1 -1
- data/lib/reform/validation.rb +3 -3
- data/lib/reform/version.rb +1 -1
- data/reform.gemspec +3 -10
- data/test/coercion_test.rb +7 -7
- data/test/composition_test.rb +5 -1
- data/test/contract_test.rb +10 -4
- data/test/deserialize_test.rb +3 -3
- data/test/errors_test.rb +48 -28
- data/test/form_option_test.rb +3 -1
- data/test/form_test.rb +19 -14
- data/test/module_test.rb +51 -11
- data/test/populate_test.rb +21 -7
- data/test/reform_test.rb +24 -20
- data/test/save_test.rb +10 -4
- data/test/skip_if_test.rb +5 -3
- data/test/test_helper.rb +3 -43
- data/test/validate_test.rb +34 -14
- data/test/validation/dry_test.rb +60 -0
- data/test/validation/dry_validation_test.rb +65 -43
- data/test/validation/errors.yml +4 -0
- metadata +16 -192
- data/database.sqlite3 +0 -0
- data/gemfiles/Gemfile.rails-3.2 +0 -7
- data/gemfiles/Gemfile.rails-4.0 +0 -8
- data/gemfiles/Gemfile.rails-4.1 +0 -8
- data/gemfiles/Gemfile.rails-4.2 +0 -8
- data/lib/reform/active_record.rb +0 -4
- data/lib/reform/form/active_model.rb +0 -87
- data/lib/reform/form/active_model/form_builder_methods.rb +0 -48
- data/lib/reform/form/active_model/model_reflections.rb +0 -46
- data/lib/reform/form/active_model/model_validations.rb +0 -110
- data/lib/reform/form/active_model/validations.rb +0 -107
- data/lib/reform/form/active_record.rb +0 -30
- data/lib/reform/form/lotus.rb +0 -59
- data/lib/reform/form/multi_parameter_attributes.rb +0 -48
- data/lib/reform/form/validation/unique_validator.rb +0 -54
- data/lib/reform/rails.rb +0 -13
- data/test/active_model_custom_validation_translations_test.rb +0 -75
- data/test/active_model_test.rb +0 -207
- data/test/active_model_validation_for_property_named_format_test.rb +0 -18
- data/test/active_record_test.rb +0 -273
- data/test/builder_test.rb +0 -32
- data/test/custom_validation_test.rb +0 -47
- data/test/dummy/Rakefile +0 -7
- data/test/dummy/app/controllers/albums_controller.rb +0 -18
- data/test/dummy/app/controllers/application_controller.rb +0 -4
- data/test/dummy/app/controllers/musician_controller.rb +0 -5
- data/test/dummy/app/forms/album_form.rb +0 -18
- data/test/dummy/app/helpers/application_helper.rb +0 -2
- data/test/dummy/app/models/album.rb +0 -4
- data/test/dummy/app/models/song.rb +0 -3
- data/test/dummy/app/views/albums/new.html.erb +0 -28
- data/test/dummy/app/views/layouts/application.html.erb +0 -14
- data/test/dummy/config.ru +0 -4
- data/test/dummy/config/application.rb +0 -20
- data/test/dummy/config/boot.rb +0 -10
- data/test/dummy/config/database.yml +0 -22
- data/test/dummy/config/environment.rb +0 -5
- data/test/dummy/config/environments/development.rb +0 -16
- data/test/dummy/config/environments/production.rb +0 -46
- data/test/dummy/config/environments/test.rb +0 -33
- data/test/dummy/config/locales/en.yml +0 -14
- data/test/dummy/config/routes.rb +0 -4
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/form_builder_test.rb +0 -138
- data/test/lotus/Gemfile +0 -5
- data/test/lotus/lotus_test.rb +0 -31
- data/test/lotus_test.rb +0 -150
- data/test/model_reflections_test.rb +0 -138
- data/test/model_validations_test.rb +0 -82
- data/test/mongoid_test.rb +0 -313
- data/test/multi_parameter_attributes_test.rb +0 -50
- data/test/rails/integration_test.rb +0 -54
- data/test/unique_test.rb +0 -135
- data/test/validation/activemodel_validation_test.rb +0 -252
data/database.sqlite3
DELETED
Binary file
|
data/gemfiles/Gemfile.rails-3.2
DELETED
data/gemfiles/Gemfile.rails-4.0
DELETED
data/gemfiles/Gemfile.rails-4.1
DELETED
data/gemfiles/Gemfile.rails-4.2
DELETED
data/lib/reform/active_record.rb
DELETED
@@ -1,87 +0,0 @@
|
|
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
|
@@ -1,48 +0,0 @@
|
|
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
|
@@ -1,46 +0,0 @@
|
|
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
|
@@ -1,110 +0,0 @@
|
|
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
|
@@ -1,107 +0,0 @@
|
|
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
|