base_form 0.1.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/MIT-LICENSE +20 -0
- data/README.md +190 -0
- data/Rakefile +18 -0
- data/lib/base_form.rb +10 -0
- data/lib/base_form/form.rb +80 -0
- data/lib/base_form/version.rb +3 -0
- metadata +79 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e17e616e68d129b4df560fbeefdc03a4ba59b6d9
|
4
|
+
data.tar.gz: 8d8abb13435b58487acc419ae77f4bdd4ca7f5fb
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 92584db37f2726475eb530296972649793e615c45d69ee7f14e867af6aa51565fe76129c0d748aa8dec9967b5bf47c9daf47c2b12b00adc62178310680a6732a
|
7
|
+
data.tar.gz: 2215a7f4b46b1d2648979e2a7b3ce9bc1a78dc9082561d5912dae0a3451bfbbf8c9ffcbf2c74cf3eac26415b497aa0fbf686bd8a7c30553ffc6c30208b9e1861
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2016 andrerpbts
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,190 @@
|
|
1
|
+
# BaseForm
|
2
|
+
[](https://codeclimate.com/github/andrerpbts/base_form)
|
3
|
+
[](https://codeclimate.com/github/andrerpbts/base_form/coverage)
|
4
|
+
[ ](https://app.codeship.com/projects/182157)
|
5
|
+
|
6
|
+
A simple and small Form Objects Rails plugin for ActiveRecord based projects.
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'base_form'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
```bash
|
17
|
+
$ bundle
|
18
|
+
```
|
19
|
+
|
20
|
+
Or install it yourself as:
|
21
|
+
```bash
|
22
|
+
$ gem install base_form
|
23
|
+
```
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
In a development day-to-day basis, we commonly are confronted with situation where we
|
27
|
+
need to save data in more than one database table, running it's own validations, and
|
28
|
+
the validations of the all context together. In most cases a Form Object is a perfect
|
29
|
+
solution to deliver those records in a fun and maintenable code.
|
30
|
+
|
31
|
+
Actually, there's a lot of another gems to do that, like the great
|
32
|
+
[reform](https://github.com/apotonick/reform) or
|
33
|
+
[activeform-rails](https://github.com/GCorbel/activeform-rails), which are a more complete
|
34
|
+
solution for this problem. But, if you are looking for something lighter, maybe this
|
35
|
+
gem could fit well for you.
|
36
|
+
|
37
|
+
That said, let me show some simple examples to you. Let's suppose you want to create a
|
38
|
+
signup form (you can check this example in the dummy app on this gem specs), with
|
39
|
+
receiving a user email, a user password, a user password confirmation, and a plan. In your
|
40
|
+
signup form, you need to create an account for this user, associate it to a entrance plan
|
41
|
+
if it's not given, and make this user as an owner of this recently created account. Of course,
|
42
|
+
in this case, a simple user model saving will not be sufficient to save all those data and
|
43
|
+
accomplish with the requested business logic. So, you go to the Form Objects way,
|
44
|
+
installs this gem, creates your Ruby class to handle all this logic:
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
class SignupForm < BaseForm::Form
|
48
|
+
|
49
|
+
end
|
50
|
+
```
|
51
|
+
|
52
|
+
With this empty class created, the easy way to start may be adding the attributes expected
|
53
|
+
in this form, like:
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
class SignupForm < BaseForm::Form
|
57
|
+
attribute :email
|
58
|
+
attribute :password
|
59
|
+
attribute :password_confirmation
|
60
|
+
attribute :plan, Plan, default: proc { Plan.default }
|
61
|
+
end
|
62
|
+
```
|
63
|
+
|
64
|
+
Note, if you don't specifies a Plan to this form, it will call a default value, which in
|
65
|
+
this case is calling a proc that will call a `default` method in `Plan` model and probably
|
66
|
+
this will return the default plan instance, fetched from the database or something like that.
|
67
|
+
|
68
|
+
Let's put some form specific validation here. For example, we don't want the Plan being forced
|
69
|
+
with an empty string for example:
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
class SignupForm < BaseForm::Form
|
73
|
+
# ... attributes, validations ...
|
74
|
+
|
75
|
+
validates :plan, presence: true
|
76
|
+
end
|
77
|
+
```
|
78
|
+
|
79
|
+
Now you may be asking: What about email and password? Did not should be validated as well?
|
80
|
+
Well, you could, in fact, perform all validations in this form, but sometimes this can happen.
|
81
|
+
Then, I'm showing here the case that `User` model has those validations. Don't be mad ok? :)
|
82
|
+
|
83
|
+
The form validations are the first validations tha are performed before it try to persist
|
84
|
+
something here. If this validation fails, for an example, the persist method will not even
|
85
|
+
be called, and the talk is over. Otherwise, it wil try to persist your logic, that we'll
|
86
|
+
implement next.
|
87
|
+
|
88
|
+
Ok, now, you need to set the records that you will persist here.
|
89
|
+
In this case is the `:user` you want to save, and the `:account` you will want to associate
|
90
|
+
to this user. So, you put it there (I recommend you let this in the top of the class to make
|
91
|
+
it clear):
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
class SignupForm < BaseForm::Form
|
95
|
+
use_form_records :user, :account
|
96
|
+
|
97
|
+
# ... attributes, validations ...
|
98
|
+
end
|
99
|
+
```
|
100
|
+
|
101
|
+
This line will automatically generate `attr_readers` to each record there, and will add this
|
102
|
+
symbols in an array called `form_records` in your class. To understand it better, let's talk
|
103
|
+
about the `persist` implementation itself.
|
104
|
+
|
105
|
+
By the rule, `persist` method is obligatory, and not implementing it, will cause your form
|
106
|
+
raise a `NotImplementedError` when calling `save` to it.
|
107
|
+
|
108
|
+
All things written inside `persist` method, will automatically run in a ActiveRecord transaction,
|
109
|
+
and if some record have its validation failed, this will perform a Rollback and deliver the form
|
110
|
+
to you with those erros grouped through `errors` method, like any AR model you are already
|
111
|
+
familiar with.
|
112
|
+
|
113
|
+
Let me stop to talk and show you something we can call as implementation of this:
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
class SignupForm < BaseForm::Form
|
117
|
+
# form records, attributes, validations, whatever
|
118
|
+
|
119
|
+
private # because isolation is still a necessary evil ;)
|
120
|
+
|
121
|
+
def persist
|
122
|
+
@account ||= Account.create plan: plan
|
123
|
+
@user ||= account.users.create user_params
|
124
|
+
end
|
125
|
+
|
126
|
+
def user_params
|
127
|
+
{
|
128
|
+
email: email,
|
129
|
+
password: password,
|
130
|
+
password_confirmation: password_confirmation,
|
131
|
+
account_owner: true
|
132
|
+
}
|
133
|
+
end
|
134
|
+
end
|
135
|
+
```
|
136
|
+
|
137
|
+
So, here is the thing, see the variables names I've associated there, are the name os
|
138
|
+
form_records I've defined before. It tries to create an account, setting a plan to it
|
139
|
+
and then tries to create a user associated to this brand new account.
|
140
|
+
|
141
|
+
Is this `form_records` that will call each object associated here to check its errors,
|
142
|
+
and group it in `errors` object in your form itself in case of some validation brakes,
|
143
|
+
but if all is fine, the form instance is returned to you and you will be able to call
|
144
|
+
methods like `persisted?`, `account`, `user`, `valid?` and etc...
|
145
|
+
|
146
|
+
Are you still there? :D
|
147
|
+
|
148
|
+
Let's see this class complete?
|
149
|
+
|
150
|
+
```ruby
|
151
|
+
class SignupForm < BaseForm
|
152
|
+
use_form_records :account, :user
|
153
|
+
|
154
|
+
attribute :email
|
155
|
+
attribute :password
|
156
|
+
attribute :password_confirmation
|
157
|
+
attribute :plan, Plan, default: proc { Plan.default }
|
158
|
+
|
159
|
+
validates :plan, presence: true
|
160
|
+
|
161
|
+
private
|
162
|
+
|
163
|
+
def persist
|
164
|
+
@account ||= Account.create plan: plan
|
165
|
+
@user ||= account.users.create user_params
|
166
|
+
end
|
167
|
+
|
168
|
+
def user_params
|
169
|
+
{
|
170
|
+
email: email,
|
171
|
+
password: password,
|
172
|
+
password_confirmation: password_confirmation,
|
173
|
+
account_owner: true
|
174
|
+
}
|
175
|
+
end
|
176
|
+
end
|
177
|
+
```
|
178
|
+
|
179
|
+
Hmmmm, seems to be good enough. I hope this helps someone in the same way it
|
180
|
+
helped me. Thanks!
|
181
|
+
|
182
|
+
## Contributing
|
183
|
+
- Fork it
|
184
|
+
- Make your implementations
|
185
|
+
- Send me a pull request
|
186
|
+
|
187
|
+
Thank you!
|
188
|
+
|
189
|
+
## License
|
190
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'BaseForm'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.md')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
require 'bundler/gem_tasks'
|
18
|
+
|
data/lib/base_form.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
module BaseForm
|
2
|
+
# This class is the main core functionality, by being an inheritable
|
3
|
+
# class, which controls the form attributes assignments, validations
|
4
|
+
# and persisting.
|
5
|
+
#
|
6
|
+
# Basically you should create your own Form Object Class, and inherit
|
7
|
+
# this class (BaseForm::Form). After that you should put all records
|
8
|
+
# in the `@form_records` variable, through `use_form_records` class
|
9
|
+
# method. In your `persist` method implementation, just create the
|
10
|
+
# record objects associating it to each variable in `form_records.`
|
11
|
+
class Form
|
12
|
+
include ActiveModel::Model
|
13
|
+
include Virtus.model
|
14
|
+
|
15
|
+
class << self
|
16
|
+
attr_reader :form_records
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
def use_form_records(*records)
|
21
|
+
attr_reader(*records)
|
22
|
+
|
23
|
+
@form_records = records
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.save(*params)
|
28
|
+
new(*params).save
|
29
|
+
end
|
30
|
+
|
31
|
+
# This method will make the things happen. It'll try run validations
|
32
|
+
# set in your form class, and if it passes, it'll run your persist
|
33
|
+
# instructions in a block of ActiveRecord transaction. If some
|
34
|
+
# record fails it's persistence/validation, then a rollback will be
|
35
|
+
# raised, the form will return those errors grouped in it. Otherwise
|
36
|
+
# everything is commited and `persisted?` method will return true.
|
37
|
+
def save
|
38
|
+
perform_in_transaction { persist } if valid?
|
39
|
+
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
def valid?
|
44
|
+
errors.empty? && super
|
45
|
+
end
|
46
|
+
|
47
|
+
def persisted?
|
48
|
+
@persisted
|
49
|
+
end
|
50
|
+
|
51
|
+
protected
|
52
|
+
|
53
|
+
def persist
|
54
|
+
raise NotImplementedError
|
55
|
+
end
|
56
|
+
|
57
|
+
def perform_in_transaction
|
58
|
+
ActiveRecord::Base.transaction do
|
59
|
+
yield if block_given?
|
60
|
+
|
61
|
+
records_errors.each { |error| add_errors_for(error) }
|
62
|
+
raise ActiveRecord::Rollback if errors.any?
|
63
|
+
|
64
|
+
@persisted = true
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def records_errors
|
69
|
+
self.class.form_records.map do |form_record|
|
70
|
+
send(form_record).try(:errors)
|
71
|
+
end.compact.flatten
|
72
|
+
end
|
73
|
+
|
74
|
+
def add_errors_for(error)
|
75
|
+
error.each do |attribute, message|
|
76
|
+
errors.add attribute, message
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
metadata
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: base_form
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- andrerpbts
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-10-31 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.2'
|
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'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.0'
|
41
|
+
description: BaseForm is a small and simple Rails plugin to work with Form Objects
|
42
|
+
email:
|
43
|
+
- andrerpbts@gmail.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- MIT-LICENSE
|
49
|
+
- README.md
|
50
|
+
- Rakefile
|
51
|
+
- lib/base_form.rb
|
52
|
+
- lib/base_form/form.rb
|
53
|
+
- lib/base_form/version.rb
|
54
|
+
homepage: https://github.com/andrerpbts/base_form
|
55
|
+
licenses:
|
56
|
+
- MIT
|
57
|
+
metadata: {}
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options: []
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
requirements: []
|
73
|
+
rubyforge_project:
|
74
|
+
rubygems_version: 2.5.1
|
75
|
+
signing_key:
|
76
|
+
specification_version: 4
|
77
|
+
summary: A simple and small form objects Rails plugin
|
78
|
+
test_files: []
|
79
|
+
has_rdoc:
|