super_form 0.0.1
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.
- 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:
|