trans_forms 0.1.0
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 +15 -0
- data/.gitignore +3 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +88 -0
- data/Rakefile +27 -0
- data/lib/generators/rails/setup_generator.rb +14 -0
- data/lib/generators/rails/templates/application_trans_form.rb +13 -0
- data/lib/generators/rails/templates/trans_form.rb +31 -0
- data/lib/generators/rails/trans_form_generator.rb +36 -0
- data/lib/generators/rspec/setup_generator.rb +9 -0
- data/lib/generators/rspec/templates/application_trans_form_spec.rb +4 -0
- data/lib/generators/rspec/templates/trans_form_spec.rb +4 -0
- data/lib/generators/rspec/trans_form_generator.rb +9 -0
- data/lib/trans_forms.rb +9 -0
- data/lib/trans_forms/callbacks.rb +30 -0
- data/lib/trans_forms/errors.rb +13 -0
- data/lib/trans_forms/form_base.rb +47 -0
- data/lib/trans_forms/form_errors.rb +38 -0
- data/lib/trans_forms/main_model.rb +115 -0
- data/lib/trans_forms/main_model/proxy.rb +38 -0
- data/lib/trans_forms/nested_forms.rb +69 -0
- data/lib/trans_forms/version.rb +3 -0
- data/spec/generators/trans_form/trans_form_generator_spec.rb +8 -0
- data/spec/spec_helper.rb +38 -0
- data/spec/support/models.rb +12 -0
- data/spec/support/schema.rb +23 -0
- data/spec/support/trans_forms/callback_forms.rb +22 -0
- data/spec/support/trans_forms/main_model_forms.rb +27 -0
- data/spec/support/trans_forms/multiple_records_forms.rb +31 -0
- data/spec/support/trans_forms/nested_forms.rb +26 -0
- data/spec/support/trans_forms/simple_forms.rb +13 -0
- data/spec/support/trans_forms/user_creator_1.rb +13 -0
- data/spec/trans_forms/callbacks_spec.rb +27 -0
- data/spec/trans_forms/form_base_spec.rb +70 -0
- data/spec/trans_forms/form_errors_spec.rb +14 -0
- data/spec/trans_forms/main_model/proxy_spec.rb +15 -0
- data/spec/trans_forms/main_model_spec.rb +49 -0
- data/spec/trans_forms/nested_forms_spec.rb +53 -0
- data/trans_forms.gemspec +29 -0
- metadata +214 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
Y2Y0YmRmMDIyZjI0Y2MxYmEwNmY1YTI1Nzk2NjVhN2U0YWFmOGRhMg==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
MDQxZWY0NTJkNWQ5NWIyNTkyOTBjYjhlNmViMjUwZDI5NzkwOTcwZA==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
OTVhYjk0ZTMyZDgyZjMyOTIzMGI3YzU4MTAwNGFmYTI3ZTFjM2FmMWQzYzYw
|
10
|
+
ZWE3MjgwNTA3Y2Q3OTM5OGExMmU3Y2Q1Yzk2NzNiODI0MTg1YTY3ODUzOTVj
|
11
|
+
NTBhYjQ2YmRkNWFkNWFjNWVmMGFlMWZjYTMxYWRmMjkzMTk4YWQ=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
NDE0NzQ4YTY2MmVkZmZjZWUwNGU2NzE3ZGJiY2M0ZTdmMmRhZWM5NDFhMWI4
|
14
|
+
NDM1ODUxZTFmY2VjNGU0YWJlODQ0MjQ5ZThiOGNlZmZhODA0MGMwNmM5ZjVl
|
15
|
+
Mzc5MTBmNWQzNWNmNjQ4ODY3MTk4YmU2YTFjYzlkMDAyNTNlYzY=
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Daniel Viklund
|
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,88 @@
|
|
1
|
+
[](https://travis-ci.org/dannemanne/trans_forms)
|
2
|
+
[](https://codeclimate.com/github/dannemanne/trans_forms)
|
3
|
+
|
4
|
+
# TransForms
|
5
|
+
|
6
|
+
TransForms is short for Transactional Forms. I created it mainly because I felt that
|
7
|
+
the ActiveRecord Models all to often get cluttered with a lot of conditional validations
|
8
|
+
and processing. As some of my models grew, I noticed that most of the mess was caused
|
9
|
+
by complex validations and callbacks that were sometimes needed, and other times they
|
10
|
+
were out of scope and had to be skipped, just to allow the record to be saved. I felt
|
11
|
+
that there was another layer missing.
|
12
|
+
|
13
|
+
By placing some of the logic and validations, that only needs to be processed in certain
|
14
|
+
scenarios, into dedicated Form Models, the ActiveRecord Models got a lot cleaner. And
|
15
|
+
in the process I gained more control of the save transaction. And the greatest benefit of
|
16
|
+
this setup is the ability to easily update multiple records without having to rely on
|
17
|
+
messy callbacks and exceptions in association validations.
|
18
|
+
|
19
|
+
This is the first extract of what I have been using in one of my projects. So far it
|
20
|
+
is still missing some of the functionality I have in the original (like confirmation
|
21
|
+
handling when conflicts occur) but the core is there. And more will come as I manage
|
22
|
+
to detach it from the more specific business logic.
|
23
|
+
|
24
|
+
## Installation
|
25
|
+
|
26
|
+
Add this line to your application's Gemfile:
|
27
|
+
|
28
|
+
gem 'trans_forms'
|
29
|
+
|
30
|
+
And then execute:
|
31
|
+
|
32
|
+
$ bundle
|
33
|
+
|
34
|
+
Or install it yourself as:
|
35
|
+
|
36
|
+
$ gem install trans_forms
|
37
|
+
|
38
|
+
## Writing Form Models
|
39
|
+
|
40
|
+
Form Models inherit from `TransForms::FormBase` and live in the `app/trans_forms`
|
41
|
+
directory of your application.
|
42
|
+
|
43
|
+
# app/trans_forms/post_form.rb
|
44
|
+
class PostForm < TransForms::FormBase
|
45
|
+
# ...
|
46
|
+
end
|
47
|
+
|
48
|
+
### Generators
|
49
|
+
|
50
|
+
To manually generate a Form Model, you can run...
|
51
|
+
|
52
|
+
rails g trans_form PostForm
|
53
|
+
|
54
|
+
... to create a `PostForm` Form Model.
|
55
|
+
|
56
|
+
### Shared Form Model Methods
|
57
|
+
|
58
|
+
You might want to add some default functionality to all your Form Models. The common
|
59
|
+
practice to accomplish this is to have an application specific model that inherits
|
60
|
+
from the FormBase. Then you have all of your FormModels inherit from this model instead.
|
61
|
+
|
62
|
+
You can generate a default model like this by running:
|
63
|
+
|
64
|
+
TODO: Add generator
|
65
|
+
|
66
|
+
This will add an `ApplicationTransForm` model...
|
67
|
+
|
68
|
+
# app/trans_forms/application_trans_form.rb
|
69
|
+
class ApplicationTransForm < TransForms::FormBase
|
70
|
+
# ...
|
71
|
+
end
|
72
|
+
|
73
|
+
... that you can use in you Form Models:
|
74
|
+
|
75
|
+
class PostForm < ApplicationTransForm
|
76
|
+
# ...
|
77
|
+
end
|
78
|
+
|
79
|
+
Note that by having a model with the exact name `ApplicationTransForm`, the generator
|
80
|
+
for new Form Models will by default inherit from that model instead of the `TransForm::FormBase`
|
81
|
+
|
82
|
+
## Contributing
|
83
|
+
|
84
|
+
1. Fork it ( https://github.com/dannemanne/trans_forms/fork )
|
85
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
86
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
87
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
88
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'bundler/gem_tasks'
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
|
5
|
+
task 'default' => 'ci'
|
6
|
+
|
7
|
+
desc 'Run all tests for CI'
|
8
|
+
task 'ci' => 'spec'
|
9
|
+
|
10
|
+
desc 'Run all specs'
|
11
|
+
task 'spec' => 'spec:all'
|
12
|
+
|
13
|
+
namespace 'spec' do
|
14
|
+
task 'all' => ['trans_forms', 'generators']
|
15
|
+
|
16
|
+
def spec_task(name)
|
17
|
+
desc "Run #{name} specs"
|
18
|
+
RSpec::Core::RakeTask.new(name) do |t|
|
19
|
+
t.pattern = "spec/#{name}/**/*_spec.rb"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
spec_task 'trans_forms'
|
24
|
+
spec_task 'generators'
|
25
|
+
|
26
|
+
end
|
27
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Rails
|
2
|
+
module Generators
|
3
|
+
class SetupGenerator < Base
|
4
|
+
source_root File.expand_path('../templates', __FILE__)
|
5
|
+
|
6
|
+
def create_form_file
|
7
|
+
template 'application_trans_form.rb', File.join('app/trans_forms', 'application_trans_form.rb')
|
8
|
+
end
|
9
|
+
|
10
|
+
hook_for :test_framework
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class ApplicationTransForm < TransForms::FormBase
|
2
|
+
# Here you can place application specific code to customize the
|
3
|
+
# default behavior of TransForms.
|
4
|
+
#
|
5
|
+
# Here is an example of a custom instantiator that works to set
|
6
|
+
# a model and current_user attributes in addition to the params
|
7
|
+
# which might come directly from the controller.
|
8
|
+
#
|
9
|
+
# def self.new_in_model(model, params = {}, current_user = nil)
|
10
|
+
# new(params.merge(model: model, current_user: current_user))
|
11
|
+
# end
|
12
|
+
|
13
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
<%- module_namespacing do -%>
|
2
|
+
<%- if parent_class_name.present? -%>
|
3
|
+
class <%= class_name %> < <%= parent_class_name %>
|
4
|
+
<%- else -%>
|
5
|
+
class <%= class_name %>
|
6
|
+
<%- end -%>
|
7
|
+
|
8
|
+
# Define the attributes available for this specific form model. The attributes
|
9
|
+
# are declared according to the Virtus standard
|
10
|
+
#
|
11
|
+
# attribute :name, String
|
12
|
+
# attribute :age, Numeric
|
13
|
+
# attribute :phone_no, Array
|
14
|
+
|
15
|
+
|
16
|
+
# Define validations according to the ActiveModel conventions
|
17
|
+
#
|
18
|
+
# validates :name, presence: true
|
19
|
+
# validates :age, numericality: { greater_than_or_equal_to: 18 }
|
20
|
+
|
21
|
+
transaction do
|
22
|
+
# Perform all actions inside this block. If anything goes wrong, i.e. an
|
23
|
+
# an error is raised because of validation errors, then everything that
|
24
|
+
# has already been done inside this block is rolled back.
|
25
|
+
#
|
26
|
+
# self.user = User.create!(name: name, age: age, phone_no: phone_no)
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
<% end -%>
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Rails
|
2
|
+
module Generators
|
3
|
+
class TransFormGenerator < NamedBase
|
4
|
+
source_root File.expand_path('../templates', __FILE__)
|
5
|
+
check_class_collision
|
6
|
+
|
7
|
+
class_option :parent, type: :string, desc: 'The parent class for the generated form model'
|
8
|
+
|
9
|
+
def create_form_file
|
10
|
+
template 'trans_form.rb', File.join('app/trans_forms', class_path, "#{file_name}.rb")
|
11
|
+
end
|
12
|
+
|
13
|
+
hook_for :test_framework
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def parent_class_name
|
18
|
+
options.fetch('parent') do
|
19
|
+
begin
|
20
|
+
require 'application_trans_form'
|
21
|
+
ApplicationTransForm
|
22
|
+
rescue LoadError
|
23
|
+
'TransForms::FormBase'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Rails 3.0.X compatibility, copied from https://github.com/jnunemaker/mongomapper/pull/385/files#L1R32
|
29
|
+
unless methods.include?(:module_namespacing)
|
30
|
+
def module_namespacing
|
31
|
+
yield if block_given?
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module Rspec
|
2
|
+
class SetupGenerator < ::Rails::Generators::Base
|
3
|
+
source_root File.expand_path('../templates', __FILE__)
|
4
|
+
|
5
|
+
def create_spec_file
|
6
|
+
template 'application_trans_form_spec.rb', File.join('spec/trans_forms', 'application_trans_form_spec.rb')
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module Rspec
|
2
|
+
class TransFormGenerator < ::Rails::Generators::NamedBase
|
3
|
+
source_root File.expand_path('../templates', __FILE__)
|
4
|
+
|
5
|
+
def create_spec_file
|
6
|
+
template 'trans_form_spec.rb', File.join('spec/trans_forms', class_path, "#{file_name}_spec.rb")
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
data/lib/trans_forms.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'trans_forms/callbacks'
|
2
|
+
require 'trans_forms/form_base'
|
3
|
+
|
4
|
+
require 'trans_forms/errors'
|
5
|
+
require 'trans_forms/form_errors'
|
6
|
+
require 'trans_forms/version'
|
7
|
+
require 'trans_forms/nested_forms'
|
8
|
+
require 'trans_forms/main_model'
|
9
|
+
require 'trans_forms/main_model/proxy'
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module TransForms
|
4
|
+
module Callbacks
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
class_attribute :_tf_cb
|
9
|
+
end
|
10
|
+
|
11
|
+
def after_save_on_error_callback(active_record_error)
|
12
|
+
if _tf_cb && _tf_cb[:after_save_on_error].is_a?(Array)
|
13
|
+
_tf_cb[:after_save_on_error].each do |method|
|
14
|
+
send method, active_record_error
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module ClassMethods
|
20
|
+
|
21
|
+
def after_save_on_error(*args)
|
22
|
+
self._tf_cb ||= {}
|
23
|
+
_tf_cb[:after_save_on_error] ||= []
|
24
|
+
_tf_cb[:after_save_on_error] += args
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
|
2
|
+
require 'active_record/errors'
|
3
|
+
module TransForms
|
4
|
+
|
5
|
+
class NotImplementedError < StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
# Custom error class to distinguish from regular ActiveRecord not found errors, but
|
9
|
+
# will trigger the same effect when raised within a transaction.
|
10
|
+
class NotFoundFromError < ActiveRecord::RecordNotFound
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'virtus'
|
2
|
+
|
3
|
+
# TODO: Determine the specific modules that needs to be loaded instead of rails/all
|
4
|
+
require 'rails/all'
|
5
|
+
|
6
|
+
module TransForms
|
7
|
+
class FormBase
|
8
|
+
include Virtus.model
|
9
|
+
|
10
|
+
extend ActiveModel::Naming
|
11
|
+
include ActiveModel::Conversion
|
12
|
+
include ActiveModel::Validations
|
13
|
+
include ActiveModel::Validations::Callbacks
|
14
|
+
|
15
|
+
include TransForms::Callbacks
|
16
|
+
|
17
|
+
def save
|
18
|
+
valid? && run_transaction
|
19
|
+
end
|
20
|
+
|
21
|
+
def save!
|
22
|
+
save || (raise ActiveRecord::RecordInvalid)
|
23
|
+
end
|
24
|
+
|
25
|
+
# ActiveModel support
|
26
|
+
def persisted?; false end
|
27
|
+
def to_key; nil end
|
28
|
+
|
29
|
+
protected
|
30
|
+
def self.transaction(&block)
|
31
|
+
class_attribute :_transaction
|
32
|
+
self._transaction = block
|
33
|
+
end
|
34
|
+
|
35
|
+
def run_transaction
|
36
|
+
ActiveRecord::Base.transaction do
|
37
|
+
instance_eval &_transaction
|
38
|
+
true
|
39
|
+
end
|
40
|
+
rescue ActiveRecord::ActiveRecordError => e
|
41
|
+
# Triggers callback
|
42
|
+
after_save_on_error_callback e
|
43
|
+
false
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module TransForms
|
2
|
+
class FormErrors
|
3
|
+
attr_accessor :form_model, :original
|
4
|
+
|
5
|
+
def initialize(form_model, original)
|
6
|
+
self.form_model, self.original = form_model, original
|
7
|
+
end
|
8
|
+
|
9
|
+
def error_models
|
10
|
+
[original, form_model.main_instance.try(:errors)].compact
|
11
|
+
end
|
12
|
+
|
13
|
+
def full_messages
|
14
|
+
original.full_messages
|
15
|
+
end
|
16
|
+
|
17
|
+
def clear
|
18
|
+
error_models.each do |em|
|
19
|
+
em.clear
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def [](key)
|
24
|
+
error_models.inject([]) { |acc, error_model|
|
25
|
+
acc += (error_model[key])
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
def empty?
|
30
|
+
error_models.all?(&:empty?)
|
31
|
+
end
|
32
|
+
|
33
|
+
def method_missing(m, *args, &block)
|
34
|
+
original.send(m, *args, &block)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|