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.
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