super_form 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +12 -0
- data/Rakefile +1 -0
- data/bin/autospec +16 -0
- data/bin/rspec +16 -0
- data/lib/attribute.rb +6 -0
- data/lib/attribute/cnpj.rb +10 -0
- data/lib/attribute/cpf.rb +9 -0
- data/lib/attribute/person_type.rb +9 -0
- data/lib/attribute/telephone.rb +9 -0
- data/lib/cnpj_validator.rb +10 -0
- data/lib/cpf_validator.rb +10 -0
- data/lib/field.rb +11 -0
- data/lib/field/base.rb +35 -0
- data/lib/field/cnpj.rb +35 -0
- data/lib/field/cpf.rb +35 -0
- data/lib/field/email.rb +32 -0
- data/lib/field/error.rb +3 -0
- data/lib/field/password.rb +21 -0
- data/lib/field/person_type.rb +13 -0
- data/lib/field/telephone.rb +21 -0
- data/lib/field/text.rb +5 -0
- data/lib/person_type.rb +51 -0
- data/lib/super_form.rb +216 -0
- data/lib/super_form/version.rb +3 -0
- data/lib/tasks/.keep +0 -0
- data/lib/telephone.rb +25 -0
- data/lib/telephone_validator.rb +10 -0
- data/lib/uniqueness_validator.rb +44 -0
- data/spec/lib/super_form_spec.rb +98 -0
- data/spec/spec_helper.rb +17 -0
- data/super_form.gemspec +32 -0
- metadata +240 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a5f0e95c7ffd7c811db1288e98272f0544bcacaa
|
4
|
+
data.tar.gz: f5e6d6330f0524b513fdf6cf4b4305cf90e32a1d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7617b22b0f8b2530b9ec7a08cc3ba3e79f7b87f3526a6f3ecadea596b9ccb2ccc45121afd5ada9a95b57ff87d42707638e3818b49c618c2e6e1d5cb1019c502c
|
7
|
+
data.tar.gz: af71f75d3eb608ba7975890d02c9379a2f398c567720cc1e4e2997b5814101e58342acffbb6a5715b3a0f5828a7a62e69f5e1ea642fb1021a37b06b54d0ceb8b
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Thiago A. Silva
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
SuperForm
|
2
|
+
---------
|
3
|
+
|
4
|
+
SuperForm adopts the concept of forms, fields and fieldsets, making it easy to define fields containing
|
5
|
+
bundled validations and attributes. Form fields will be truly object oriented; that will make your code more DRY,
|
6
|
+
so you won't need to define the same validations and attributes over and over again. SuperForm also contains
|
7
|
+
fields for mundane concepts (like email and phone) - but you can still use it to code specific fields for
|
8
|
+
your application. Forms are flexible, and allow you to provide specific validations and attributes. Also, SuperForm
|
9
|
+
aims to supply a handy form builder for these fields.
|
10
|
+
|
11
|
+
This is still a work in progress, an early prototype. No release has been made yet. I'm working towards
|
12
|
+
the first release.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/autospec
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'autospec' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('rspec-core', 'autospec')
|
data/bin/rspec
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'rspec' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('rspec-core', 'rspec')
|
data/lib/attribute.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
class CnpjValidator < ActiveModel::EachValidator
|
2
|
+
def validate_each(record, attribute, value)
|
3
|
+
return if value.blank?
|
4
|
+
|
5
|
+
unless CNPJ.new(value).valid?
|
6
|
+
default_message = I18n.t('activemodel.errors.messages.cnpj', 'invalid CNPJ', default: 'invalid CNPJ')
|
7
|
+
record.errors[attribute] << (options[:message] || default_message)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
class CpfValidator < ActiveModel::EachValidator
|
2
|
+
def validate_each(record, attribute, value)
|
3
|
+
return if value.blank?
|
4
|
+
|
5
|
+
unless CPF.new(value).valid?
|
6
|
+
default_message = I18n.t('activemodel.errors.messages.cpf', 'invalid CPF', default: 'invalid CPF')
|
7
|
+
record.errors[attribute] << (options[:message] || default_message)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
data/lib/field.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
module Field
|
2
|
+
autoload :Error, 'field/error'
|
3
|
+
autoload :Base, 'field/base'
|
4
|
+
autoload :Text, 'field/text'
|
5
|
+
autoload :Password, 'field/password'
|
6
|
+
autoload :Email, 'field/email'
|
7
|
+
autoload :CPF, 'field/cpf'
|
8
|
+
autoload :CNPJ, 'field/cnpj'
|
9
|
+
autoload :Telephone, 'field/telephone'
|
10
|
+
autoload :PersonType, 'field/person_type'
|
11
|
+
end
|
data/lib/field/base.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
module Field
|
2
|
+
class Base
|
3
|
+
attr_accessor :name
|
4
|
+
attr_accessor :form
|
5
|
+
attr_accessor :value
|
6
|
+
attr_reader :fieldset
|
7
|
+
|
8
|
+
def initialize(name, fieldset)
|
9
|
+
@name = name
|
10
|
+
@fieldset = fieldset
|
11
|
+
end
|
12
|
+
|
13
|
+
def add_attributes(klass, options)
|
14
|
+
klass.attribute name, self.attribute
|
15
|
+
end
|
16
|
+
|
17
|
+
def add_validations(klass, options)
|
18
|
+
if options.any?
|
19
|
+
klass.send(:validates, name, options)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def form?
|
24
|
+
false
|
25
|
+
end
|
26
|
+
|
27
|
+
def attribute
|
28
|
+
String
|
29
|
+
end
|
30
|
+
|
31
|
+
def output
|
32
|
+
value
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/field/cnpj.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'cnpj'
|
2
|
+
require 'cnpj_validator'
|
3
|
+
|
4
|
+
module Field
|
5
|
+
class CNPJ < Base
|
6
|
+
def add_validations(klass, options)
|
7
|
+
klass.validates name, cnpj: true
|
8
|
+
|
9
|
+
if options[:uniqueness]
|
10
|
+
unless options[:uniqueness].is_a?(Hash) && options[:uniqueness][:model]
|
11
|
+
raise Field::Error, "Must specify a model to validate uniqueness"
|
12
|
+
end
|
13
|
+
|
14
|
+
required = {
|
15
|
+
attribute: name,
|
16
|
+
allow_nil: true,
|
17
|
+
allow_blank: true
|
18
|
+
}
|
19
|
+
|
20
|
+
klass.validates name, uniqueness: options[:uniqueness].merge(required)
|
21
|
+
options.reject! { |k| k == :uniqueness }
|
22
|
+
end
|
23
|
+
|
24
|
+
super
|
25
|
+
end
|
26
|
+
|
27
|
+
def attribute
|
28
|
+
::Attribute::CNPJ
|
29
|
+
end
|
30
|
+
|
31
|
+
def output
|
32
|
+
::CNPJ.new(value).formatted
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/field/cpf.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'cpf'
|
2
|
+
require 'cpf_validator'
|
3
|
+
|
4
|
+
module Field
|
5
|
+
class CPF < ::Field::Base
|
6
|
+
def add_validations(klass, options)
|
7
|
+
klass.validates name, cpf: true
|
8
|
+
|
9
|
+
if options[:uniqueness]
|
10
|
+
unless options[:uniqueness].is_a?(Hash) && options[:uniqueness][:model]
|
11
|
+
raise Field::Error, "Must specify a model to validate uniqueness"
|
12
|
+
end
|
13
|
+
|
14
|
+
required = {
|
15
|
+
attribute: name,
|
16
|
+
allow_nil: true,
|
17
|
+
allow_blank: true
|
18
|
+
}
|
19
|
+
|
20
|
+
klass.validates name, uniqueness: options[:uniqueness].merge(required)
|
21
|
+
options.reject! { |k| k == :uniqueness }
|
22
|
+
end
|
23
|
+
|
24
|
+
super
|
25
|
+
end
|
26
|
+
|
27
|
+
def attribute
|
28
|
+
::Attribute::CPF
|
29
|
+
end
|
30
|
+
|
31
|
+
def output
|
32
|
+
::CPF.new(value).formatted
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/field/email.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'validates_email_format_of'
|
2
|
+
|
3
|
+
module Field
|
4
|
+
class Email < Base
|
5
|
+
def add_validations(klass, options)
|
6
|
+
klass.validates name, length: { maximum: 155 }
|
7
|
+
klass.validates name, email_format: {
|
8
|
+
message: I18n.t('activemodel.errors.messages.email'),
|
9
|
+
allow_nil: true,
|
10
|
+
allow_blank: true
|
11
|
+
}
|
12
|
+
|
13
|
+
if options[:uniqueness]
|
14
|
+
unless options[:uniqueness].is_a?(Hash) && options[:uniqueness][:model]
|
15
|
+
raise Field::Error, "Must specify a model to validate uniqueness"
|
16
|
+
end
|
17
|
+
|
18
|
+
required = {
|
19
|
+
case_sensitive: false,
|
20
|
+
attribute: name,
|
21
|
+
allow_nil: true,
|
22
|
+
allow_blank: true
|
23
|
+
}
|
24
|
+
|
25
|
+
klass.validates name, uniqueness: options[:uniqueness].merge(required)
|
26
|
+
options.reject! { |k| k == :uniqueness }
|
27
|
+
end
|
28
|
+
|
29
|
+
super
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/field/error.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
module Field
|
2
|
+
class Password < Base
|
3
|
+
def add_attributes(klass, options)
|
4
|
+
klass.attribute :"#{name}_confirmation", String
|
5
|
+
|
6
|
+
super
|
7
|
+
end
|
8
|
+
|
9
|
+
def add_validations(klass, options)
|
10
|
+
klass.validates name, presence: true, if: ->(f){ !f.to_param }
|
11
|
+
klass.validates name, confirmation: true, if: ->(f){ !f.to_param }
|
12
|
+
klass.validates name, length: { minimum: 5, maximum: 15 }, allow_blank: true
|
13
|
+
|
14
|
+
super
|
15
|
+
end
|
16
|
+
|
17
|
+
def output
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'telephone'
|
2
|
+
require 'telephone_validator'
|
3
|
+
|
4
|
+
module Field
|
5
|
+
class Telephone < Base
|
6
|
+
def add_validations(klass, options)
|
7
|
+
klass.validates name, telephone: true
|
8
|
+
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def attribute
|
13
|
+
::Attribute::Telephone
|
14
|
+
end
|
15
|
+
|
16
|
+
def output
|
17
|
+
return unless value
|
18
|
+
::Telephone.new(value).formatted
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/field/text.rb
ADDED
data/lib/person_type.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
class PersonType
|
2
|
+
TYPES = [NATURAL = 'natural_person', LEGAL = 'legal_entity']
|
3
|
+
|
4
|
+
attr_writer :value
|
5
|
+
|
6
|
+
def self.description(value, default = '')
|
7
|
+
description = case value
|
8
|
+
when NATURAL then 'Natural person'
|
9
|
+
when LEGAL then 'Legal entity'
|
10
|
+
else default
|
11
|
+
end
|
12
|
+
|
13
|
+
I18n.t(value, default: description)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.descriptions
|
17
|
+
TYPES.each_with_object({}) do |type, hash|
|
18
|
+
hash[type] = description(type)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.to_collection
|
23
|
+
descriptions.map { |k, v| [v, k] }
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(value)
|
27
|
+
@value = value
|
28
|
+
end
|
29
|
+
|
30
|
+
def value
|
31
|
+
@value if valid?
|
32
|
+
end
|
33
|
+
|
34
|
+
def description(default = '')
|
35
|
+
self.class.description(@value, default)
|
36
|
+
end
|
37
|
+
|
38
|
+
def valid?
|
39
|
+
TYPES.include? @value
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_s
|
43
|
+
value
|
44
|
+
end
|
45
|
+
|
46
|
+
class Attribute < Virtus::Attribute
|
47
|
+
def coerce(value)
|
48
|
+
::PersonType.new(value).value
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/super_form.rb
ADDED
@@ -0,0 +1,216 @@
|
|
1
|
+
require "super_form/version"
|
2
|
+
|
3
|
+
require 'field'
|
4
|
+
require 'attribute'
|
5
|
+
require 'virtus'
|
6
|
+
require 'active_support/inflector'
|
7
|
+
require 'active_support/concern'
|
8
|
+
require 'active_model'
|
9
|
+
|
10
|
+
autoload :UniquenessValidator, 'uniqueness_validator'
|
11
|
+
|
12
|
+
module SuperForm
|
13
|
+
def self.included(klass)
|
14
|
+
virtus = if @virtus_options
|
15
|
+
Virtus.model(@virtus_options)
|
16
|
+
else
|
17
|
+
Virtus.model
|
18
|
+
end
|
19
|
+
|
20
|
+
klass.include virtus
|
21
|
+
klass.include ActiveModel::Conversion
|
22
|
+
klass.include ActiveModel::Validations
|
23
|
+
klass.extend ActiveModel::Naming
|
24
|
+
klass.extend ActiveModel::Callbacks
|
25
|
+
klass.extend ClassMethods
|
26
|
+
|
27
|
+
add_callbacks(klass)
|
28
|
+
add_constructor(klass)
|
29
|
+
|
30
|
+
klass.send(:attr_reader, :fieldsets)
|
31
|
+
|
32
|
+
@virtus_options = nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.add_constructor(klass)
|
36
|
+
klass.class_eval do
|
37
|
+
alias_method :original_initializer, :initialize
|
38
|
+
|
39
|
+
def initialize(*args)
|
40
|
+
setup
|
41
|
+
original_initializer(*args)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.add_callbacks(klass)
|
47
|
+
klass.class_eval do
|
48
|
+
alias_method :ar_valid?, :valid?
|
49
|
+
|
50
|
+
def valid?
|
51
|
+
run_callbacks :validation do
|
52
|
+
ar_valid?
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
define_model_callbacks :validation, :save
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.base(virtus_options = {})
|
61
|
+
@virtus_options = virtus_options
|
62
|
+
Form
|
63
|
+
end
|
64
|
+
|
65
|
+
def setup
|
66
|
+
initialize_fields
|
67
|
+
instance_eval(&self.class.setup) if self.class.setup
|
68
|
+
end
|
69
|
+
|
70
|
+
def persisted?
|
71
|
+
false
|
72
|
+
end
|
73
|
+
|
74
|
+
def fields
|
75
|
+
@fields
|
76
|
+
end
|
77
|
+
|
78
|
+
def field(name)
|
79
|
+
if self.class.form?(name)
|
80
|
+
send(name)
|
81
|
+
else
|
82
|
+
@fields.fetch(name)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def form?
|
87
|
+
true
|
88
|
+
end
|
89
|
+
|
90
|
+
def form_label
|
91
|
+
self.class.name.to_s.gsub(/Form$/, '').humanize
|
92
|
+
end
|
93
|
+
|
94
|
+
def save
|
95
|
+
if valid?
|
96
|
+
run_callbacks :save do
|
97
|
+
persist!
|
98
|
+
end
|
99
|
+
true
|
100
|
+
else
|
101
|
+
false
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
def initialize_fields
|
108
|
+
@fields = {}
|
109
|
+
@fieldsets = {}
|
110
|
+
|
111
|
+
# take care of branches here
|
112
|
+
self.class.fields.each do |id, field|
|
113
|
+
if self.class.form?(id)
|
114
|
+
@fields[id] = field
|
115
|
+
|
116
|
+
fieldset = self.class.child_form_fieldsets[id]
|
117
|
+
(@fieldsets[fieldset] ||= []) << @fields[id]
|
118
|
+
else
|
119
|
+
@fields[id] = field.dup
|
120
|
+
(@fieldsets[field.fieldset] ||= []) << @fields[id]
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
module ClassMethods
|
126
|
+
def setup(&block)
|
127
|
+
@setup = block if block
|
128
|
+
@setup
|
129
|
+
end
|
130
|
+
|
131
|
+
def form?(field_id = nil)
|
132
|
+
field_id.nil? ? true : child_form_fieldsets.has_key?(field_id)
|
133
|
+
end
|
134
|
+
|
135
|
+
# fieldset may be extracted as a domain object
|
136
|
+
def fieldset(id)
|
137
|
+
initialize_fieldset(id)
|
138
|
+
yield
|
139
|
+
close_fieldset
|
140
|
+
end
|
141
|
+
|
142
|
+
def child_form_fieldsets
|
143
|
+
@child_form_fieldsets ||= {}
|
144
|
+
end
|
145
|
+
|
146
|
+
# take care of branches here
|
147
|
+
def field(name, field_class, options = {})
|
148
|
+
if field_class.ancestors.include? SuperForm
|
149
|
+
child_form_fieldsets[name] = current_fieldset_name
|
150
|
+
attribute name, field_class
|
151
|
+
|
152
|
+
define_method(name) do
|
153
|
+
var = :"@#{name.to_s}"
|
154
|
+
value = instance_variable_get(var)
|
155
|
+
|
156
|
+
unless value
|
157
|
+
value = instance_variable_set(var, field_class.new)
|
158
|
+
end
|
159
|
+
|
160
|
+
value
|
161
|
+
end
|
162
|
+
|
163
|
+
create_child_validation(name)
|
164
|
+
field = name
|
165
|
+
else
|
166
|
+
field = field_class.new(name, current_fieldset_name)
|
167
|
+
|
168
|
+
field.add_validations(self, options)
|
169
|
+
field.add_attributes(self, options)
|
170
|
+
|
171
|
+
alias_method :"original_#{name.to_s}=", "#{name}="
|
172
|
+
|
173
|
+
define_method "#{name}=" do |value|
|
174
|
+
field = self.field(name)
|
175
|
+
field.value = value if field
|
176
|
+
|
177
|
+
send "original_#{name.to_s}=", value
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
fields[name] = field
|
182
|
+
end
|
183
|
+
|
184
|
+
def fields
|
185
|
+
@fields ||= {}
|
186
|
+
end
|
187
|
+
|
188
|
+
private
|
189
|
+
|
190
|
+
def initialize_fieldset(id)
|
191
|
+
@current = id
|
192
|
+
end
|
193
|
+
|
194
|
+
def close_fieldset
|
195
|
+
@current = nil
|
196
|
+
end
|
197
|
+
|
198
|
+
def current_fieldset_name
|
199
|
+
@current ||= default_fieldset_name
|
200
|
+
end
|
201
|
+
|
202
|
+
def default_fieldset_name
|
203
|
+
:general
|
204
|
+
end
|
205
|
+
|
206
|
+
def create_child_validation(name)
|
207
|
+
validate :"ensure_valid_#{name.to_s}"
|
208
|
+
|
209
|
+
define_method "ensure_valid_#{name.to_s}" do
|
210
|
+
unless send(name).send(:valid?)
|
211
|
+
errors.add(:base, "Invalid #{name.to_s}")
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
data/lib/tasks/.keep
ADDED
File without changes
|
data/lib/telephone.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
class Telephone
|
2
|
+
FORMAT = /\A(\d{2})(\d{4})(\d{4})\Z/
|
3
|
+
|
4
|
+
attr_accessor :number
|
5
|
+
|
6
|
+
def initialize(number)
|
7
|
+
@number = number
|
8
|
+
end
|
9
|
+
|
10
|
+
def stripped
|
11
|
+
@number.gsub /[^\d]/, ''
|
12
|
+
end
|
13
|
+
|
14
|
+
def formatted
|
15
|
+
stripped.gsub FORMAT, "(\\1) \\2-\\3"
|
16
|
+
end
|
17
|
+
|
18
|
+
def valid?
|
19
|
+
stripped.match FORMAT
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_s
|
23
|
+
formatted
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
class TelephoneValidator < ActiveModel::EachValidator
|
2
|
+
def validate_each(record, attribute, value)
|
3
|
+
return if value.blank?
|
4
|
+
|
5
|
+
unless Telephone.new(value).valid?
|
6
|
+
default_message = I18n.t('activemodel.errors.messages.telephone', default: 'invalid telephone')
|
7
|
+
record.errors[attribute] << (options[:message] || default_message)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
class UniquenessValidator < ActiveRecord::Validations::UniquenessValidator
|
2
|
+
def setup(klass)
|
3
|
+
super
|
4
|
+
@klass = options[:model] if options[:model]
|
5
|
+
end
|
6
|
+
|
7
|
+
def validate_each(record, attribute, value)
|
8
|
+
# UniquenessValidator can't be used outside of ActiveRecord instances, here
|
9
|
+
# we return the exact same error, unless the 'model' option is given.
|
10
|
+
if ! options[:model] && ! record.class.ancestors.include?(ActiveRecord::Base)
|
11
|
+
raise ArgumentError, "Unknown validator: 'UniquenessValidator'"
|
12
|
+
|
13
|
+
# If we're inside an ActiveRecord class, and `model` isn't set, use the
|
14
|
+
# default behaviour of the validator.
|
15
|
+
elsif ! options[:model]
|
16
|
+
super
|
17
|
+
|
18
|
+
# Custom validator options. The validator can be called in any class, as
|
19
|
+
# long as it includes `ActiveModel::Validations`. You can tell the validator
|
20
|
+
# which ActiveRecord based class to check against, using the `model`
|
21
|
+
# option. Also, if you are using a different attribute name, you can set the
|
22
|
+
# correct one for the ActiveRecord class using the `attribute` option.
|
23
|
+
else
|
24
|
+
record_org, attribute_org = record, attribute
|
25
|
+
|
26
|
+
attribute = options[:attribute].to_sym if options[:attribute]
|
27
|
+
|
28
|
+
if options[:model]
|
29
|
+
record = options[:model].new(attribute => value)
|
30
|
+
else
|
31
|
+
model_name = options.fetch(:model_name) { record_org.class.to_s.downcase.split('::').last.gsub(/form$/, '') }
|
32
|
+
record = record_org.send(model_name)
|
33
|
+
end
|
34
|
+
record = record_org.send(options[:model].to_s.downcase)
|
35
|
+
|
36
|
+
super
|
37
|
+
|
38
|
+
if record.errors[attribute_org].any?
|
39
|
+
record_org.errors.add(attribute_org, :taken,
|
40
|
+
options.except(:case_sensitive, :scope).merge(value: value))
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'super_form'
|
3
|
+
require 'active_model'
|
4
|
+
|
5
|
+
module Field
|
6
|
+
class Null < Base; end
|
7
|
+
end
|
8
|
+
|
9
|
+
class DummyForm
|
10
|
+
include SuperForm
|
11
|
+
end
|
12
|
+
|
13
|
+
describe SuperForm do
|
14
|
+
before(:all) do
|
15
|
+
DummyForm.class_eval do
|
16
|
+
fieldset :general do
|
17
|
+
field :name, Field::Text, presence: true
|
18
|
+
end
|
19
|
+
|
20
|
+
fieldset :toys do
|
21
|
+
field :ball, Field::Null
|
22
|
+
field :car, Field::Text
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
@dummy_form = DummyForm.new
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'when a field is defined at class level' do
|
30
|
+
before(:all) { @fields = DummyForm.fields }
|
31
|
+
|
32
|
+
it 'stores the basic field objects at class level' do
|
33
|
+
expect(@fields.fetch(:name)).to be_instance_of Field::Text
|
34
|
+
expect(@fields.fetch(:ball)).to be_instance_of Field::Null
|
35
|
+
expect(@fields.fetch(:car)).to be_instance_of Field::Text
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'assigns the fieldset to the field' do
|
39
|
+
expect(@fields.fetch(:name).fieldset).to eq :general
|
40
|
+
expect(@fields.fetch(:ball).fieldset).to eq :toys
|
41
|
+
expect(@fields.fetch(:car).fieldset).to eq :toys
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'when a form object is created' do
|
46
|
+
before(:each) { @dummy_form = DummyForm.new }
|
47
|
+
|
48
|
+
# this can get better, but for now it's ok
|
49
|
+
it 'creates virtus attributes' do
|
50
|
+
expect(@dummy_form).to respond_to(:name)
|
51
|
+
expect(@dummy_form).to respond_to(:name=)
|
52
|
+
expect(@dummy_form).to respond_to(:ball)
|
53
|
+
expect(@dummy_form).to respond_to(:ball=)
|
54
|
+
expect(@dummy_form).to respond_to(:car)
|
55
|
+
expect(@dummy_form).to respond_to(:car=)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'assigns the fieldset id to the field' do
|
59
|
+
expect(@dummy_form.field(:name).fieldset).to eq :general
|
60
|
+
expect(@dummy_form.field(:ball).fieldset).to eq :toys
|
61
|
+
expect(@dummy_form.field(:car).fieldset).to eq :toys
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'defines fieldsets containing the bundled fields' do
|
65
|
+
general = @dummy_form.fieldsets.fetch(:general)
|
66
|
+
|
67
|
+
expect(general.first).to be_instance_of Field::Text
|
68
|
+
expect(general.first.name).to eq :name
|
69
|
+
|
70
|
+
toys = @dummy_form.fieldsets.fetch(:toys)
|
71
|
+
|
72
|
+
expect(toys.first).to be_instance_of Field::Null
|
73
|
+
expect(toys.first.name).to eq :ball
|
74
|
+
|
75
|
+
expect(toys.last).to be_instance_of Field::Text
|
76
|
+
expect(toys.last.name).to eq :car
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'fieldsets fields are the same instance of fields' do
|
80
|
+
@dummy_form.fieldsets.each do |id, fields|
|
81
|
+
fields.each do |field|
|
82
|
+
expect(field).to eq @dummy_form.field(field.name)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'is ready for validation' do
|
88
|
+
expect(@dummy_form).to_not be_valid
|
89
|
+
expect(@dummy_form.errors[:name]).to eq ["can't be blank"]
|
90
|
+
end
|
91
|
+
|
92
|
+
context 'when assigning a new attribute' do
|
93
|
+
it 'assigns the value of the attribute to the field' do
|
94
|
+
pending
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
4
|
+
# loaded once.
|
5
|
+
#
|
6
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
+
RSpec.configure do |config|
|
8
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
9
|
+
config.run_all_when_everything_filtered = true
|
10
|
+
config.filter_run :focus
|
11
|
+
|
12
|
+
# Run specs in random order to surface order dependencies. If you find an
|
13
|
+
# order dependency and want to debug it, you can fix the order by providing
|
14
|
+
# the seed, which is printed after each run.
|
15
|
+
# --seed 1234
|
16
|
+
config.order = 'random'
|
17
|
+
end
|
data/super_form.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'super_form/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "super_form"
|
8
|
+
spec.version = SuperForm::VERSION
|
9
|
+
spec.authors = ["Thiago A. Silva"]
|
10
|
+
spec.email = ["thiagoaraujos@gmail.com"]
|
11
|
+
spec.summary = 'Object oriented forms for Rails'
|
12
|
+
spec.description = 'Super Form adopts the concept of forms and fields, making it easy to define fields containing bundled validations and attributes. Also, it aims to supply a handy form builder for these fields.'
|
13
|
+
spec.homepage = "http://github.com/thiagoa/super_form"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "sqlite3", '1.3.8'
|
22
|
+
spec.add_development_dependency 'virtus', '1.0.1'
|
23
|
+
spec.add_development_dependency 'pry-rails', '0.3.2'
|
24
|
+
spec.add_development_dependency 'rspec', '2.14.1'
|
25
|
+
spec.add_development_dependency 'simplecov', '0.8.2'
|
26
|
+
spec.add_development_dependency 'rake'
|
27
|
+
spec.add_development_dependency 'capybara', '2.2.0'
|
28
|
+
spec.add_development_dependency 'activemodel', '4.0.2'
|
29
|
+
spec.add_development_dependency 'activesupport', '4.0.2'
|
30
|
+
spec.add_development_dependency 'cpf_cnpj', '0.2.0'
|
31
|
+
spec.add_development_dependency 'validates_email_format_of', '1.5.3'
|
32
|
+
end
|
metadata
ADDED
@@ -0,0 +1,240 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: super_form
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Thiago A. Silva
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-02-09 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: sqlite3
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.3.8
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.3.8
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: virtus
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.0.1
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.0.1
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: pry-rails
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.3.2
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.3.2
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 2.14.1
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 2.14.1
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: simplecov
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.8.2
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.8.2
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rake
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: capybara
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - '='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 2.2.0
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - '='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 2.2.0
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: activemodel
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - '='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 4.0.2
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - '='
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 4.0.2
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: activesupport
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - '='
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 4.0.2
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - '='
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 4.0.2
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: cpf_cnpj
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - '='
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: 0.2.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.2.0
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: validates_email_format_of
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - '='
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: 1.5.3
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - '='
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: 1.5.3
|
167
|
+
description: Super Form adopts the concept of forms and fields, making it easy to
|
168
|
+
define fields containing bundled validations and attributes. Also, it aims to supply
|
169
|
+
a handy form builder for these fields.
|
170
|
+
email:
|
171
|
+
- thiagoaraujos@gmail.com
|
172
|
+
executables:
|
173
|
+
- autospec
|
174
|
+
- rspec
|
175
|
+
extensions: []
|
176
|
+
extra_rdoc_files: []
|
177
|
+
files:
|
178
|
+
- ".gitignore"
|
179
|
+
- ".rspec"
|
180
|
+
- Gemfile
|
181
|
+
- LICENSE.txt
|
182
|
+
- README.md
|
183
|
+
- Rakefile
|
184
|
+
- bin/autospec
|
185
|
+
- bin/rspec
|
186
|
+
- lib/attribute.rb
|
187
|
+
- lib/attribute/cnpj.rb
|
188
|
+
- lib/attribute/cpf.rb
|
189
|
+
- lib/attribute/person_type.rb
|
190
|
+
- lib/attribute/telephone.rb
|
191
|
+
- lib/cnpj_validator.rb
|
192
|
+
- lib/cpf_validator.rb
|
193
|
+
- lib/field.rb
|
194
|
+
- lib/field/base.rb
|
195
|
+
- lib/field/cnpj.rb
|
196
|
+
- lib/field/cpf.rb
|
197
|
+
- lib/field/email.rb
|
198
|
+
- lib/field/error.rb
|
199
|
+
- lib/field/password.rb
|
200
|
+
- lib/field/person_type.rb
|
201
|
+
- lib/field/telephone.rb
|
202
|
+
- lib/field/text.rb
|
203
|
+
- lib/person_type.rb
|
204
|
+
- lib/super_form.rb
|
205
|
+
- lib/super_form/version.rb
|
206
|
+
- lib/tasks/.keep
|
207
|
+
- lib/telephone.rb
|
208
|
+
- lib/telephone_validator.rb
|
209
|
+
- lib/uniqueness_validator.rb
|
210
|
+
- spec/lib/super_form_spec.rb
|
211
|
+
- spec/spec_helper.rb
|
212
|
+
- super_form.gemspec
|
213
|
+
homepage: http://github.com/thiagoa/super_form
|
214
|
+
licenses:
|
215
|
+
- MIT
|
216
|
+
metadata: {}
|
217
|
+
post_install_message:
|
218
|
+
rdoc_options: []
|
219
|
+
require_paths:
|
220
|
+
- lib
|
221
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
222
|
+
requirements:
|
223
|
+
- - ">="
|
224
|
+
- !ruby/object:Gem::Version
|
225
|
+
version: '0'
|
226
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
227
|
+
requirements:
|
228
|
+
- - ">="
|
229
|
+
- !ruby/object:Gem::Version
|
230
|
+
version: '0'
|
231
|
+
requirements: []
|
232
|
+
rubyforge_project:
|
233
|
+
rubygems_version: 2.2.0
|
234
|
+
signing_key:
|
235
|
+
specification_version: 4
|
236
|
+
summary: Object oriented forms for Rails
|
237
|
+
test_files:
|
238
|
+
- spec/lib/super_form_spec.rb
|
239
|
+
- spec/spec_helper.rb
|
240
|
+
has_rdoc:
|