lev 0.0.3 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -1
- data/.rspec +2 -0
- data/Gemfile.lock +32 -1
- data/README.md +53 -2
- data/lev.gemspec +9 -3
- data/lib/lev.rb +10 -7
- data/lib/lev/delegate_to_routine.rb +25 -0
- data/lib/lev/error.rb +28 -0
- data/lib/lev/error_transferer.rb +41 -0
- data/lib/lev/{handler/error_translator.rb → error_translator.rb} +1 -1
- data/lib/lev/errors.rb +41 -0
- data/lib/lev/exceptions.rb +3 -1
- data/lib/lev/form_builder.rb +3 -3
- data/lib/lev/handle_with.rb +5 -6
- data/lib/lev/handler.rb +55 -53
- data/lib/lev/handler_helper.rb +1 -1
- data/lib/lev/routine.rb +424 -0
- data/lib/lev/term_mapper.rb +41 -0
- data/lib/lev/utilities.rb +14 -0
- data/lib/lev/version.rb +1 -1
- data/spec/create_sprocket_spec.rb +9 -0
- data/spec/deep_merge_spec.rb +41 -0
- data/spec/routine_spec.rb +7 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/sprocket_spec.rb +11 -0
- data/spec/support/create_sprocket.rb +14 -0
- data/spec/support/sprocket.rb +10 -0
- metadata +108 -14
- data/lib/lev/algorithm.rb +0 -26
- data/lib/lev/delegate_to_algorithm.rb +0 -25
- data/lib/lev/handler/error.rb +0 -30
- data/lib/lev/handler/error_transferer.rb +0 -28
- data/lib/lev/handler/errors.rb +0 -19
- data/lib/lev/routine_nesting.rb +0 -127
data/.gitignore
CHANGED
data/.rspec
ADDED
data/Gemfile.lock
CHANGED
@@ -1,14 +1,23 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
lev (0.0
|
4
|
+
lev (1.0.0)
|
5
|
+
actionpack (>= 3.0)
|
5
6
|
active_attr
|
7
|
+
activemodel (>= 3.0)
|
8
|
+
activerecord (>= 3.0)
|
6
9
|
transaction_isolation
|
7
10
|
transaction_retry
|
8
11
|
|
9
12
|
GEM
|
10
13
|
remote: https://rubygems.org/
|
11
14
|
specs:
|
15
|
+
actionpack (4.0.0)
|
16
|
+
activesupport (= 4.0.0)
|
17
|
+
builder (~> 3.1.0)
|
18
|
+
erubis (~> 2.7.0)
|
19
|
+
rack (~> 1.5.2)
|
20
|
+
rack-test (~> 0.6.2)
|
12
21
|
active_attr (0.8.2)
|
13
22
|
activemodel (>= 3.0.2, < 4.1)
|
14
23
|
activesupport (>= 3.0.2, < 4.1)
|
@@ -30,10 +39,30 @@ GEM
|
|
30
39
|
arel (4.0.0)
|
31
40
|
atomic (1.1.14)
|
32
41
|
builder (3.1.4)
|
42
|
+
columnize (0.3.6)
|
43
|
+
debugger (1.6.2)
|
44
|
+
columnize (>= 0.3.1)
|
45
|
+
debugger-linecache (~> 1.2.0)
|
46
|
+
debugger-ruby_core_source (~> 1.2.3)
|
47
|
+
debugger-linecache (1.2.0)
|
48
|
+
debugger-ruby_core_source (1.2.3)
|
49
|
+
diff-lcs (1.2.4)
|
50
|
+
erubis (2.7.0)
|
33
51
|
i18n (0.6.5)
|
34
52
|
minitest (4.7.5)
|
35
53
|
multi_json (1.8.0)
|
54
|
+
rack (1.5.2)
|
55
|
+
rack-test (0.6.2)
|
56
|
+
rack (>= 1.0)
|
36
57
|
rake (10.1.0)
|
58
|
+
rspec (2.14.1)
|
59
|
+
rspec-core (~> 2.14.0)
|
60
|
+
rspec-expectations (~> 2.14.0)
|
61
|
+
rspec-mocks (~> 2.14.0)
|
62
|
+
rspec-core (2.14.5)
|
63
|
+
rspec-expectations (2.14.3)
|
64
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
65
|
+
rspec-mocks (2.14.3)
|
37
66
|
thread_safe (0.1.3)
|
38
67
|
atomic
|
39
68
|
transaction_isolation (1.0.3)
|
@@ -48,5 +77,7 @@ PLATFORMS
|
|
48
77
|
|
49
78
|
DEPENDENCIES
|
50
79
|
bundler (~> 1.3)
|
80
|
+
debugger
|
51
81
|
lev!
|
52
82
|
rake
|
83
|
+
rspec
|
data/README.md
CHANGED
@@ -1,6 +1,50 @@
|
|
1
1
|
# Lev
|
2
2
|
|
3
|
-
|
3
|
+
Rails is fantastic and obviously super successful. Lev is an attempt to improve Rails by:
|
4
|
+
|
5
|
+
1. Providing a better, more structured, and more organized way to implement code features
|
6
|
+
2. De-emphasizing the "model is king" mindset when appropriate
|
7
|
+
|
8
|
+
Rails' MVC-view of the world is very compelling and provides a sturdy scaffold with which to create applications quickly. However, sometimes it can lead to business logic code getting a little spread out. When trying to figure out where to best put business logic, you often hear folks recommending "fat models" and "skinny controllers". They are saying that the business logic of your app should live in the model classes and not in the controllers. I agree that the logic shouldn't live in the controllers, but I also argue that it shouldn't always live in the models either, especially when that logic touches multiple models.
|
9
|
+
|
10
|
+
When all of the business logic lives in the models, some bad things can happen:
|
11
|
+
|
12
|
+
1. your models can become bloated with code that only applies to certain features
|
13
|
+
2. your models end up knowing way too much about other models, sometimes multiple hops away
|
14
|
+
3. your business logic gets spread all over the place. The execution of one "feature" can jump between bits of code in multiple models, their various ActiveRecord life cycle callbacks (before_create, etc), and their associated observers.
|
15
|
+
|
16
|
+
Lev introduces "routines" which you can think of as pieces of code that have all the responsibility for making one thing (use case) happen, e.g. "add an email to a user", "register a student to a class", etc).
|
17
|
+
|
18
|
+
Routines...
|
19
|
+
|
20
|
+
1. Can call other routines
|
21
|
+
2. Have a common error reporting framework
|
22
|
+
3. Run within a single transaction with a controllable isolation level
|
23
|
+
|
24
|
+
Handlers are specialized routines that take user input (e.g. form data) and then take an action based on that input.
|
25
|
+
|
26
|
+
Handlers...
|
27
|
+
|
28
|
+
1. Help you verify that the calling user is authorized to run the handler
|
29
|
+
2. Provide ways to validate incoming parameters in a very ActiveModel-like way (even when the parameters are not associated with a model)
|
30
|
+
3. Integrate will with basic routines
|
31
|
+
|
32
|
+
In a Lev-oriented Rails app, controllers are just responsible for connecting routes to Handlers. In fact, controller methods just end up being calls to ```handle_with(MyHandler)```, ```handle_with``` being a helper method provided by Lev.
|
33
|
+
|
34
|
+
Lev also provides a ```lev_form_for``` form builder to replace ```form_for```. This builder integrates well with the error reporting infrastructure in routines and handlers, and in general is a nice way to get away from forms that are very single-model-centric.
|
35
|
+
|
36
|
+
When using Lev, model classes have the following responsibilities:
|
37
|
+
|
38
|
+
|
39
|
+
1. Hook into the ORM (i.e., inherit from ActiveRecord::Base)
|
40
|
+
2. Establish relationships to other models (e.g. belongs_to, has_many, including dependent: :destroy)
|
41
|
+
3. Validate internal state
|
42
|
+
1. Do not validate state in related models
|
43
|
+
2. Can validate the presence of relationship (can check that a foreign key is present)
|
44
|
+
4. Can perform queries when those queries only use internal model state and are aware of internal model state (e.g. arguments to queries should be in the language of the model state)
|
45
|
+
5. Can create records when those creations only need values internal to this model and take arguments in the language of the internal model state.
|
46
|
+
|
47
|
+
The result of the principles above and below, model classes end up being very small. This is good because a lot of code depends on the models and having the be small normally means they are also stable.
|
4
48
|
|
5
49
|
## Installation
|
6
50
|
|
@@ -18,7 +62,14 @@ Or install it yourself as:
|
|
18
62
|
|
19
63
|
## Usage
|
20
64
|
|
21
|
-
|
65
|
+
For the moment, details on how to use lev live in big sections of comments at the top of:
|
66
|
+
|
67
|
+
* https://github.com/lml/lev/blob/master/lib/lev/routine.rb
|
68
|
+
* https://github.com/lml/lev/blob/master/lib/lev/handler.rb
|
69
|
+
* https://github.com/lml/lev/blob/master/lib/lev/handle_with.rb
|
70
|
+
* https://github.com/lml/lev/blob/master/lib/lev/form_builder.rb
|
71
|
+
|
72
|
+
TBD: talk about ```delegate_to_routine```.
|
22
73
|
|
23
74
|
## Contributing
|
24
75
|
|
data/lev.gemspec
CHANGED
@@ -18,10 +18,16 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.
|
22
|
-
spec.
|
23
|
-
spec.
|
21
|
+
spec.add_runtime_dependency(%q<activemodel>, [">= 3.0"])
|
22
|
+
spec.add_runtime_dependency(%q<activerecord>, [">= 3.0"])
|
23
|
+
spec.add_runtime_dependency(%q<actionpack>, [">= 3.0"])
|
24
|
+
spec.add_runtime_dependency "transaction_isolation"
|
25
|
+
spec.add_runtime_dependency "transaction_retry"
|
26
|
+
spec.add_runtime_dependency "active_attr"
|
24
27
|
|
25
28
|
spec.add_development_dependency "bundler", "~> 1.3"
|
26
29
|
spec.add_development_dependency "rake"
|
30
|
+
spec.add_development_dependency "rspec"
|
31
|
+
spec.add_development_dependency "debugger"
|
32
|
+
|
27
33
|
end
|
data/lib/lev.rb
CHANGED
@@ -1,21 +1,24 @@
|
|
1
|
+
require "action_view"
|
1
2
|
require "transaction_isolation"
|
2
3
|
require "transaction_retry"
|
3
4
|
require "active_attr"
|
4
5
|
|
5
6
|
require "lev/version"
|
7
|
+
require "lev/utilities"
|
6
8
|
require "lev/exceptions"
|
7
|
-
require "lev/routine_nesting"
|
8
9
|
require "lev/better_active_model_errors"
|
10
|
+
require "lev/term_mapper"
|
11
|
+
require "lev/routine"
|
9
12
|
require "lev/handler"
|
10
13
|
require "lev/handle_with"
|
11
14
|
require "lev/handler_helper"
|
12
|
-
require "lev/
|
13
|
-
require "lev/
|
14
|
-
require "lev/
|
15
|
-
require "lev/
|
15
|
+
require "lev/error"
|
16
|
+
require "lev/errors"
|
17
|
+
require "lev/error_transferer"
|
18
|
+
require "lev/error_translator"
|
19
|
+
|
16
20
|
require "lev/form_builder"
|
17
|
-
require "lev/
|
18
|
-
require "lev/delegate_to_algorithm"
|
21
|
+
require "lev/delegate_to_routine"
|
19
22
|
require "lev/transaction_isolation"
|
20
23
|
|
21
24
|
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# ActiveRecord::Base.delegate_to_routine
|
2
|
+
#
|
3
|
+
# Let active records delegate certain (likely non-trivial) actions to routines
|
4
|
+
#
|
5
|
+
# Arguments:
|
6
|
+
# method: a symbol for the instance method to delegate, e.g. :destroy
|
7
|
+
# options: a hash of options including...
|
8
|
+
# :routine_class => The class of the routine to delegate to; if not
|
9
|
+
# given,
|
10
|
+
ActiveRecord::Base.define_singleton_method(:delegate_to_routine) do |method, options={}|
|
11
|
+
routine_class = options[:routine_class]
|
12
|
+
|
13
|
+
if routine_class.nil?
|
14
|
+
routine_class_name = "#{method.to_s.capitalize}#{self.name}"
|
15
|
+
routine_class = Kernel.const_get(routine_class_name)
|
16
|
+
end
|
17
|
+
|
18
|
+
self.instance_eval do
|
19
|
+
alias_method "#{method}_original".to_sym, method
|
20
|
+
define_method method do
|
21
|
+
routine_class.call(self)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
data/lib/lev/error.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
module Lev
|
2
|
+
|
3
|
+
class Error
|
4
|
+
|
5
|
+
attr_accessor :code
|
6
|
+
attr_accessor :data
|
7
|
+
attr_accessor :kind
|
8
|
+
attr_accessor :message
|
9
|
+
|
10
|
+
# The inputs related to this error
|
11
|
+
attr_accessor :offending_inputs
|
12
|
+
|
13
|
+
def initialize(args={})
|
14
|
+
raise IllegalArgument, "must supply a :code" if args[:code].blank?
|
15
|
+
|
16
|
+
self.code = args[:code]
|
17
|
+
self.data = args[:data]
|
18
|
+
self.kind = args[:kind]
|
19
|
+
self.message = args[:message]
|
20
|
+
self.offending_inputs = args[:offending_inputs]
|
21
|
+
end
|
22
|
+
|
23
|
+
def translate
|
24
|
+
ErrorTranslator.translate(self)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Lev
|
2
|
+
|
3
|
+
class ErrorTransferer
|
4
|
+
|
5
|
+
def self.transfer(source, target_routine, input_mapper, fail_if_errors=false)
|
6
|
+
case source
|
7
|
+
when ActiveRecord::Base, Lev::Paramifier
|
8
|
+
source.errors.each_with_type_and_message do |attribute, type, message|
|
9
|
+
target_routine.nonfatal_error(
|
10
|
+
code: type,
|
11
|
+
data: {
|
12
|
+
model: source,
|
13
|
+
attribute: attribute
|
14
|
+
},
|
15
|
+
kind: :activerecord,
|
16
|
+
message: message,
|
17
|
+
offending_inputs: input_mapper.map(attribute)
|
18
|
+
)
|
19
|
+
end
|
20
|
+
when Lev::Errors
|
21
|
+
source.each do |error|
|
22
|
+
target_routine.nonfatal_error(
|
23
|
+
code: error.code,
|
24
|
+
data: error.data,
|
25
|
+
kind: error.kind,
|
26
|
+
message: error.message,
|
27
|
+
offending_inputs: input_mapper.map(error.offending_inputs)
|
28
|
+
)
|
29
|
+
end
|
30
|
+
else
|
31
|
+
raise Exception
|
32
|
+
end
|
33
|
+
|
34
|
+
# We add nonfatal errors above and then have this call here so that all
|
35
|
+
# errors can be transferred before we freak out.
|
36
|
+
throw :fatal_errors_encountered if target_routine.errors? && fail_if_errors
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
data/lib/lev/errors.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
module Lev
|
2
|
+
|
3
|
+
# A collection of Error objects.
|
4
|
+
#
|
5
|
+
class Errors < Array
|
6
|
+
|
7
|
+
def add(fail, args={})
|
8
|
+
args[:kind] ||= :lev
|
9
|
+
error = Error.new(args)
|
10
|
+
return if ignored_error_procs.any?{|proc| proc.call(error)}
|
11
|
+
self.push(error)
|
12
|
+
throw :fatal_errors_encountered if fail
|
13
|
+
end
|
14
|
+
|
15
|
+
def ignore(arg)
|
16
|
+
proc = arg.is_a?(Symbol) ?
|
17
|
+
Proc.new{|error| error.code == arg} :
|
18
|
+
arg
|
19
|
+
|
20
|
+
raise IllegalArgument if !proc.respond_to?(:call)
|
21
|
+
|
22
|
+
ignored_error_procs.push(proc)
|
23
|
+
end
|
24
|
+
|
25
|
+
def [](key)
|
26
|
+
self[key]
|
27
|
+
end
|
28
|
+
|
29
|
+
# Checks to see if the provided input is associated with one of the errors.
|
30
|
+
def has_offending_input?(input)
|
31
|
+
self.any? {|error| [error.offending_inputs].flatten.include? input}
|
32
|
+
end
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
def ignored_error_procs
|
37
|
+
@ignored_error_procs ||= []
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
data/lib/lev/exceptions.rb
CHANGED
data/lib/lev/form_builder.rb
CHANGED
@@ -26,7 +26,7 @@ module Lev
|
|
26
26
|
def fields_for(record_name, record_object = nil, fields_options = {}, &block)
|
27
27
|
raise "Didn't put fields_for into LevitateFormBuilder yet"
|
28
28
|
end
|
29
|
-
|
29
|
+
|
30
30
|
protected
|
31
31
|
|
32
32
|
def get_form_params_entry(name)
|
@@ -38,7 +38,7 @@ module Lev
|
|
38
38
|
end
|
39
39
|
|
40
40
|
def has_error?(name)
|
41
|
-
@options[:errors].present? ? @options[:errors].
|
41
|
+
@options[:errors].present? ? @options[:errors].has_offending_input?([@object_name, name]) : false
|
42
42
|
end
|
43
43
|
|
44
44
|
def set_value_if_available(method, options)
|
@@ -58,6 +58,6 @@ end
|
|
58
58
|
def lev_form_for(record_or_name_or_array, *args, &proc)
|
59
59
|
options = args.extract_options!
|
60
60
|
options[:params] = params
|
61
|
-
options[:errors] = @errors || []
|
61
|
+
options[:errors] = handler_errors # @errors || (@handler_outcome ? @handler_outcome.errors : [])
|
62
62
|
form_for(record_or_name_or_array, *(args << options.merge(:builder => Lev::FormBuilder)), &proc)
|
63
63
|
end
|
data/lib/lev/handle_with.rb
CHANGED
@@ -18,14 +18,13 @@ module Lev
|
|
18
18
|
# failure: lambda { render 'new', alert: 'Error' })
|
19
19
|
#
|
20
20
|
# handle_with takes care of calling the handler and populates
|
21
|
-
#
|
22
|
-
#
|
21
|
+
# a @handler_result object with the return value from the handler
|
23
22
|
#
|
24
23
|
# The 'success' and 'failure' lambdas are called if there aren't or are errors,
|
25
24
|
# respectively. Alternatively, if you supply a 'complete' lambda, that lambda
|
26
25
|
# will be called regardless of whether there are any errors. Inside these lambdas
|
27
|
-
# (and inside the views they connect to), there will be
|
28
|
-
#
|
26
|
+
# (and inside the views they connect to), there will be the @handler_outcome
|
27
|
+
# variable containing the errors and results from the handler.
|
29
28
|
#
|
30
29
|
# Specifying 'params' is optional. If you don't specify it, HandleWith will
|
31
30
|
# use the entire params hash from the request.
|
@@ -42,10 +41,10 @@ module Lev
|
|
42
41
|
options[:request] ||= request
|
43
42
|
options[:caller] ||= current_user
|
44
43
|
|
45
|
-
@
|
44
|
+
@handler_result = handler.handle(options)
|
46
45
|
|
47
46
|
if complete_action.nil?
|
48
|
-
@errors.empty? ?
|
47
|
+
@handler_result.errors.empty? ?
|
49
48
|
success_action.call :
|
50
49
|
failure_action.call
|
51
50
|
else
|
data/lib/lev/handler.rb
CHANGED
@@ -2,29 +2,33 @@
|
|
2
2
|
module Lev
|
3
3
|
|
4
4
|
class Paramifier
|
5
|
+
def as_hash(keys)
|
6
|
+
keys = [keys].flatten.compact
|
7
|
+
Hash[keys.collect { |key| [key, self.send(key)] }]
|
8
|
+
end
|
5
9
|
end
|
6
10
|
|
7
|
-
# Common methods for all handlers. Handlers are
|
8
|
-
# for taking input data from a form or other widget and
|
9
|
-
# with it.
|
11
|
+
# Common methods for all handlers. Handlers are extensions of Routines
|
12
|
+
# and are responsible for taking input data from a form or other widget and
|
13
|
+
# doing something with it. See Lev::Routine for more information.
|
10
14
|
#
|
11
15
|
# All handlers must:
|
12
16
|
# 2) include this module ("include Lev::Handler")
|
13
|
-
# 3) implement the '
|
17
|
+
# 3) implement the 'handle' method which takes no arguments and does the
|
14
18
|
# work the handler is charged with
|
15
19
|
# 4) implement the 'authorized?' method which returns true iff the
|
16
20
|
# caller is authorized to do what the handler is charged with
|
17
21
|
#
|
18
22
|
# Handlers may:
|
19
|
-
# 1) implement the 'setup' method which runs before 'authorized?' and '
|
23
|
+
# 1) implement the 'setup' method which runs before 'authorized?' and 'handle'.
|
20
24
|
# This method can do anything, and will likely include setting up some
|
21
25
|
# instance objects based on the params.
|
22
26
|
# 2) Call the class method "paramify" to declare, cast, and validate parts of
|
23
27
|
# the params hash. The first argument to paramify is the key in params
|
24
28
|
# which points to a hash of params to be paramified. The block passed to
|
25
|
-
# paramify looks just like the guts of an ActiveAttr model.
|
29
|
+
# paramify looks just like the guts of an ActiveAttr model.
|
26
30
|
#
|
27
|
-
#
|
31
|
+
# When the incoming params includes :search => {:type, :terms, :num_results}
|
28
32
|
# the Handler class would look like:
|
29
33
|
#
|
30
34
|
# class MyHandler
|
@@ -44,7 +48,7 @@ module Lev
|
|
44
48
|
# greater_than_or_equal_to: 0 }
|
45
49
|
# end
|
46
50
|
#
|
47
|
-
# def
|
51
|
+
# def handle
|
48
52
|
# # By this time, if there were any errors the handler would have
|
49
53
|
# # already populated the errors object and returned.
|
50
54
|
# #
|
@@ -60,26 +64,27 @@ module Lev
|
|
60
64
|
# 2) 'caller' -- the user submitting the input
|
61
65
|
# 3) 'errors' -- an object in which to store errors
|
62
66
|
# 4) 'results' -- a hash in which to store results for return to calling code
|
63
|
-
# 5) '
|
67
|
+
# 5) 'request' -- the HTTP request
|
68
|
+
# 6) 'options' -- a hash containing the options passed in, useful for other
|
64
69
|
# nonstandard data.
|
65
|
-
#
|
66
|
-
#
|
70
|
+
#
|
71
|
+
# These methods are available iff these data were supplied in the call
|
72
|
+
# to the handler (not all handlers need all of this). However, note that
|
73
|
+
# the Lev::HandleWith module supplies an easy way to call Handlers from
|
74
|
+
# controllers -- when this way is used, all of the methods above are available.
|
75
|
+
#
|
76
|
+
# Handler 'handle' methods don't return anything; they just set values in
|
67
77
|
# the errors and results objects. The documentation for each handler
|
68
78
|
# should explain what the results will be and any nonstandard data required
|
69
79
|
# to be passed in in the options.
|
70
80
|
#
|
71
|
-
#
|
72
|
-
#
|
73
|
-
#
|
74
|
-
#
|
75
|
-
#
|
76
|
-
#
|
77
|
-
# request: the http request object
|
81
|
+
# In addition to the class- and instance-level "call" methods provided by
|
82
|
+
# Lev::Routine, Handlers have a class-level "handle" method (an alias of
|
83
|
+
# the class-level "call" method). The convention for handlers is that the
|
84
|
+
# call methods take a hash of options/inputs. The instance-level handle
|
85
|
+
# method doesn't take any arguments since the arguments have been stored
|
86
|
+
# as instance variables by the time the instance-level handle method is called.
|
78
87
|
#
|
79
|
-
# These arguments are optional or required depending on the implementation of
|
80
|
-
# the specific handler, i.e. if a handler wants to use the 'caller' method, it
|
81
|
-
# must have been supplied to the handle method.
|
82
|
-
#
|
83
88
|
# Example:
|
84
89
|
#
|
85
90
|
# class MyHandler
|
@@ -88,7 +93,7 @@ module Lev
|
|
88
93
|
# def authorized?
|
89
94
|
# # return true iff exec is allowed to be called, e.g. might
|
90
95
|
# # check the caller against the params
|
91
|
-
# def
|
96
|
+
# def handle
|
92
97
|
# # do the work, add errors to errors object and results to the results hash as needed
|
93
98
|
# end
|
94
99
|
# end
|
@@ -98,21 +103,14 @@ module Lev
|
|
98
103
|
def self.included(base)
|
99
104
|
base.extend(ClassMethods)
|
100
105
|
base.class_eval do
|
101
|
-
include Lev::
|
106
|
+
include Lev::Routine
|
102
107
|
end
|
103
108
|
end
|
104
109
|
|
105
|
-
def handle(options={})
|
106
|
-
in_transaction do
|
107
|
-
handle_guts(options)
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
alias_method :call, :handle
|
112
|
-
|
113
110
|
module ClassMethods
|
111
|
+
|
114
112
|
def handle(options={})
|
115
|
-
|
113
|
+
call(options)
|
116
114
|
end
|
117
115
|
|
118
116
|
def paramify(group, options={}, &block)
|
@@ -161,53 +159,57 @@ module Lev
|
|
161
159
|
end
|
162
160
|
end
|
163
161
|
|
164
|
-
def transfer_errors_from(source, param_group)
|
165
|
-
ErrorTransferer.transfer(source, self, param_group)
|
166
|
-
end
|
167
|
-
|
168
|
-
attr_accessor :errors
|
169
|
-
|
170
162
|
protected
|
171
163
|
|
172
164
|
attr_accessor :params
|
173
165
|
attr_accessor :request
|
174
166
|
attr_accessor :options
|
175
167
|
attr_accessor :caller
|
176
|
-
attr_accessor :results
|
177
168
|
|
178
|
-
|
169
|
+
# Provided for development / debugging purposes -- gives a way to pass
|
170
|
+
# information in a raised security transgression when authorized? is false
|
171
|
+
attr_accessor :auth_error_details
|
172
|
+
|
173
|
+
# This is a method required by Lev::Routine. It enforces the steps common
|
174
|
+
# to all handlers.
|
175
|
+
def exec(options)
|
179
176
|
self.params = options.delete(:params)
|
180
177
|
self.request = options.delete(:request)
|
181
178
|
self.caller = options.delete(:caller)
|
182
179
|
self.options = options
|
183
|
-
self.errors = Errors.new
|
184
|
-
self.results = {}
|
185
180
|
|
186
181
|
setup
|
187
|
-
raise Lev.configuration.security_transgression_error unless authorized?
|
182
|
+
raise Lev.configuration.security_transgression_error, auth_error_details unless authorized?
|
188
183
|
validate_paramified_params
|
189
|
-
|
190
|
-
|
191
|
-
[self.results, self.errors]
|
184
|
+
handle unless errors?
|
192
185
|
end
|
193
186
|
|
187
|
+
# Default setup implementation -- a no-op
|
194
188
|
def setup; end
|
195
189
|
|
190
|
+
# Default authorized? implementation. It returns true so that every
|
191
|
+
# handler realization has to make a conscious decision about who is authorized
|
192
|
+
# to call the handler. To help the common error of forgetting to override this
|
193
|
+
# method in a handler instance, we provide an error message when this default
|
194
|
+
# implementation is called.
|
196
195
|
def authorized?
|
197
|
-
|
196
|
+
self.auth_error_details =
|
197
|
+
"Access to handlers is prevented by default. You need to override the " +
|
198
|
+
"'authorized?' in this handler to explicitly grant access."
|
199
|
+
false
|
198
200
|
end
|
199
201
|
|
202
|
+
|
203
|
+
|
204
|
+
# Helper method to validate paramified params and to transfer any errors
|
205
|
+
# into the handler.
|
200
206
|
def validate_paramified_params
|
201
207
|
self.class.paramify_methods.each do |method|
|
202
208
|
params = send(method)
|
203
|
-
transfer_errors_from(params, params.group) if !params.valid?
|
209
|
+
transfer_errors_from(params, TermsMapper.scope(params.group)) if !params.valid?
|
204
210
|
end
|
205
211
|
end
|
206
212
|
|
207
|
-
def errors?
|
208
|
-
errors.any?
|
209
|
-
end
|
210
|
-
|
211
213
|
end
|
212
214
|
|
213
215
|
end
|