trans_forms 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +3 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +5 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE +22 -0
  7. data/README.md +88 -0
  8. data/Rakefile +27 -0
  9. data/lib/generators/rails/setup_generator.rb +14 -0
  10. data/lib/generators/rails/templates/application_trans_form.rb +13 -0
  11. data/lib/generators/rails/templates/trans_form.rb +31 -0
  12. data/lib/generators/rails/trans_form_generator.rb +36 -0
  13. data/lib/generators/rspec/setup_generator.rb +9 -0
  14. data/lib/generators/rspec/templates/application_trans_form_spec.rb +4 -0
  15. data/lib/generators/rspec/templates/trans_form_spec.rb +4 -0
  16. data/lib/generators/rspec/trans_form_generator.rb +9 -0
  17. data/lib/trans_forms.rb +9 -0
  18. data/lib/trans_forms/callbacks.rb +30 -0
  19. data/lib/trans_forms/errors.rb +13 -0
  20. data/lib/trans_forms/form_base.rb +47 -0
  21. data/lib/trans_forms/form_errors.rb +38 -0
  22. data/lib/trans_forms/main_model.rb +115 -0
  23. data/lib/trans_forms/main_model/proxy.rb +38 -0
  24. data/lib/trans_forms/nested_forms.rb +69 -0
  25. data/lib/trans_forms/version.rb +3 -0
  26. data/spec/generators/trans_form/trans_form_generator_spec.rb +8 -0
  27. data/spec/spec_helper.rb +38 -0
  28. data/spec/support/models.rb +12 -0
  29. data/spec/support/schema.rb +23 -0
  30. data/spec/support/trans_forms/callback_forms.rb +22 -0
  31. data/spec/support/trans_forms/main_model_forms.rb +27 -0
  32. data/spec/support/trans_forms/multiple_records_forms.rb +31 -0
  33. data/spec/support/trans_forms/nested_forms.rb +26 -0
  34. data/spec/support/trans_forms/simple_forms.rb +13 -0
  35. data/spec/support/trans_forms/user_creator_1.rb +13 -0
  36. data/spec/trans_forms/callbacks_spec.rb +27 -0
  37. data/spec/trans_forms/form_base_spec.rb +70 -0
  38. data/spec/trans_forms/form_errors_spec.rb +14 -0
  39. data/spec/trans_forms/main_model/proxy_spec.rb +15 -0
  40. data/spec/trans_forms/main_model_spec.rb +49 -0
  41. data/spec/trans_forms/nested_forms_spec.rb +53 -0
  42. data/trans_forms.gemspec +29 -0
  43. 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
@@ -0,0 +1,3 @@
1
+ .bundle
2
+ .idea
3
+ Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ script:
5
+ - bundle exec rake
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in test-gem.gemspec
4
+ gemspec
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
+ [![Build Status](https://travis-ci.org/dannemanne/trans_forms.svg?branch=master)](https://travis-ci.org/dannemanne/trans_forms)
2
+ [![Code Climate](https://codeclimate.com/github/dannemanne/trans_forms.png)](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,4 @@
1
+ require 'spec_helper'
2
+
3
+ describe ApplicationTransForm do
4
+ end
@@ -0,0 +1,4 @@
1
+ require 'spec_helper'
2
+
3
+ describe <%= class_name %> do
4
+ 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
@@ -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