lev 0.0.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.
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +52 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/lev.gemspec +27 -0
- data/lib/lev/.DS_Store +0 -0
- data/lib/lev/algorithm.rb +26 -0
- data/lib/lev/better_active_model_errors.rb +408 -0
- data/lib/lev/delegate_to_algorithm.rb +25 -0
- data/lib/lev/exceptions.rb +1 -0
- data/lib/lev/form_builder.rb +63 -0
- data/lib/lev/handle_with.rb +48 -0
- data/lib/lev/handler/.DS_Store +0 -0
- data/lib/lev/handler/error.rb +30 -0
- data/lib/lev/handler/error_transferer.rb +28 -0
- data/lib/lev/handler/error_translator.rb +20 -0
- data/lib/lev/handler/errors.rb +19 -0
- data/lib/lev/handler.rb +194 -0
- data/lib/lev/handler_helper.rb +3 -0
- data/lib/lev/routine_nesting.rb +127 -0
- data/lib/lev/transaction_isolation.rb +59 -0
- data/lib/lev/version.rb +3 -0
- data/lib/lev.rb +57 -0
- metadata +158 -0
@@ -0,0 +1,48 @@
|
|
1
|
+
|
2
|
+
module Lev
|
3
|
+
|
4
|
+
# A utility method for calling handlers from controllers. To use,
|
5
|
+
# include this in your relevant controllers (or in your ApplicationController),
|
6
|
+
# e.g.:
|
7
|
+
#
|
8
|
+
# class ApplicationController
|
9
|
+
# include Lev::HandleWith
|
10
|
+
# ...
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# Then, call it from your various controller actions, e.g.:
|
14
|
+
#
|
15
|
+
# handle_with(MyFormHandler,
|
16
|
+
# params: params,
|
17
|
+
# success: lambda { redirect_to 'show', notice: 'Success!'},
|
18
|
+
# failure: lambda { render 'new', alert: 'Error' })
|
19
|
+
#
|
20
|
+
# handle_with takes care of calling the handler and populates
|
21
|
+
# @errors and @results objects with the return values from the handler
|
22
|
+
#
|
23
|
+
# The 'success' and 'failure' lambdas are called if there aren't or are errors,
|
24
|
+
# respectively. Alternatively, if you supply a 'complete' lambda, that lambda
|
25
|
+
# will be called regardless of whether there are any errors.
|
26
|
+
#
|
27
|
+
# Specifying 'params' is optional. If you don't specify it, HandleWith will
|
28
|
+
# use the entire params hash from the request.
|
29
|
+
#
|
30
|
+
module HandleWith
|
31
|
+
def handle_with(handler, options)
|
32
|
+
options[:success] ||= lambda {}
|
33
|
+
options[:failure] ||= lambda {}
|
34
|
+
options[:params] ||= params
|
35
|
+
|
36
|
+
@results, @errors = handler.handle(current_user, options[:params])
|
37
|
+
|
38
|
+
if options[:complete].nil?
|
39
|
+
@errors.empty? ?
|
40
|
+
options[:success].call :
|
41
|
+
options[:failure].call
|
42
|
+
else
|
43
|
+
options[:complete].call
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
Binary file
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Lev::Handler
|
2
|
+
|
3
|
+
class Error
|
4
|
+
# need a type or source that can be :activerecord
|
5
|
+
# when activerecord, data should contain specific fields that
|
6
|
+
# can be used by generate_message in BetterErrors
|
7
|
+
attr_accessor :code
|
8
|
+
attr_accessor :data
|
9
|
+
attr_accessor :kind
|
10
|
+
attr_accessor :message
|
11
|
+
attr_accessor :offending_params
|
12
|
+
|
13
|
+
def initialize(args={})
|
14
|
+
raise IllegalArgument 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
|
+
|
21
|
+
self.offending_params = args[:offending_params]
|
22
|
+
self.offending_params = [self.offending_params] if !(self.offending_params.is_a? Array)
|
23
|
+
end
|
24
|
+
|
25
|
+
def translate
|
26
|
+
ErrorTranslator.translate(self)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Lev::Handler
|
2
|
+
|
3
|
+
class ErrorTransferer
|
4
|
+
|
5
|
+
def self.transfer(source, handler_target, param_group)
|
6
|
+
case source
|
7
|
+
when ActiveRecord::Base, Lev::Paramifier
|
8
|
+
source.errors.each_with_type_and_message do |attribute, type, message|
|
9
|
+
handler_target.errors.add(
|
10
|
+
code: type,
|
11
|
+
data: {
|
12
|
+
model: source,
|
13
|
+
attribute: attribute
|
14
|
+
},
|
15
|
+
kind: :activerecord,
|
16
|
+
message: message,
|
17
|
+
offending_params: [param_group].flatten << attribute
|
18
|
+
)
|
19
|
+
end
|
20
|
+
else
|
21
|
+
raise Exception
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Lev::Handler
|
2
|
+
|
3
|
+
class ErrorTranslator
|
4
|
+
|
5
|
+
def self.translate(error)
|
6
|
+
case error.kind
|
7
|
+
when :activerecord
|
8
|
+
model = error.data[:model]
|
9
|
+
attribute = error.data[:attribute]
|
10
|
+
# TODO error.message might always be populated now -- really need the other call after ||?
|
11
|
+
message = error.message || Lev::BetterActiveModelErrors.generate_message(model, attribute, error.code)
|
12
|
+
Lev::BetterActiveModelErrors.full_message(model, attribute, message)
|
13
|
+
else
|
14
|
+
error.code.to_s
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Lev::Handler
|
2
|
+
|
3
|
+
class Errors < Array
|
4
|
+
def add(args)
|
5
|
+
push(Error.new(args))
|
6
|
+
end
|
7
|
+
|
8
|
+
def [](key)
|
9
|
+
self[key]
|
10
|
+
end
|
11
|
+
|
12
|
+
# Checks to see if the provided param identifier is one of the offending
|
13
|
+
# params, e.g. has_offending_param?([:my_form, :my_text_field_name])
|
14
|
+
def has_offending_param?(param)
|
15
|
+
self.any?{|error| error.offending_params == param}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
data/lib/lev/handler.rb
ADDED
@@ -0,0 +1,194 @@
|
|
1
|
+
|
2
|
+
module Lev
|
3
|
+
|
4
|
+
class Paramifier
|
5
|
+
end
|
6
|
+
|
7
|
+
# Common methods for all handlers. Handlers are classes that are responsible
|
8
|
+
# for taking input data from a form or other widget and doing something
|
9
|
+
# with it.
|
10
|
+
#
|
11
|
+
# All handlers must:
|
12
|
+
# 2) include this module ("include Lev::Handler")
|
13
|
+
# 3) implement the 'exec' method which takes no arguments and does the
|
14
|
+
# work the handler is charged with
|
15
|
+
# 4) implement the 'authorized?' method which returns true iff the
|
16
|
+
# caller is authorized to do what the handler is charged with
|
17
|
+
#
|
18
|
+
# Handlers may:
|
19
|
+
# 1) implement the 'setup' method which runs before 'authorized?' and 'exec'.
|
20
|
+
# This method can do anything, and will likely include setting up some
|
21
|
+
# instance objects based on the params.
|
22
|
+
# 2) Call the class method "paramify" to declare, cast, and validate parts of
|
23
|
+
# the params hash. The first argument to paramify is the key in params
|
24
|
+
# 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. Examples:
|
26
|
+
#
|
27
|
+
# when the incoming params includes :search => {:type, :terms, :num_results}
|
28
|
+
# the Handler class would look like:
|
29
|
+
#
|
30
|
+
# class MyHandler
|
31
|
+
# include Lev::Handler
|
32
|
+
#
|
33
|
+
# paramify :search do
|
34
|
+
# attribute :search_type, type: String
|
35
|
+
# validates :search_type, presence: true,
|
36
|
+
# inclusion: { in: %w(Name Username Any),
|
37
|
+
# message: "is not valid" }
|
38
|
+
#
|
39
|
+
# attribute :search_terms, type: String
|
40
|
+
# validates :search_terms, presence: true
|
41
|
+
#
|
42
|
+
# attribute :num_results, type: Integer
|
43
|
+
# validates :num_results, numericality: { only_integer: true,
|
44
|
+
# greater_than_or_equal_to: 0 }
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# def exec
|
48
|
+
# # By this time, if there were any errors the handler would have
|
49
|
+
# # already populated the errors object and returned.
|
50
|
+
# #
|
51
|
+
# # Paramify makes a 'search_params' attribute available through
|
52
|
+
# # which you can access the paramified params, e.g.
|
53
|
+
# x = search_params.num_results
|
54
|
+
# ...
|
55
|
+
# end
|
56
|
+
# end
|
57
|
+
#
|
58
|
+
# All handler instance methods have the following available to them:
|
59
|
+
# 1) 'params' -- the params from the input
|
60
|
+
# 2) 'caller' -- the user submitting the input
|
61
|
+
# 3) 'errors' -- an object in which to store errors
|
62
|
+
# 4) 'results' -- a hash in which to store results for return to calling code
|
63
|
+
#
|
64
|
+
# See the documentation for Lev::RoutineNesting about other requirements and
|
65
|
+
# capabilities of handler classes.
|
66
|
+
#
|
67
|
+
# The handle methods take the caller and the params objects, which should be
|
68
|
+
# self-explanatory.
|
69
|
+
#
|
70
|
+
# Example:
|
71
|
+
#
|
72
|
+
# class MyHandler
|
73
|
+
# include Lev::Handler
|
74
|
+
# protected
|
75
|
+
# def authorized?
|
76
|
+
# # return true iff exec is allowed to be called, e.g. might
|
77
|
+
# # check the caller against the params
|
78
|
+
# def exec
|
79
|
+
# # do the work, add errors to errors object and results to the results hash as needed
|
80
|
+
# end
|
81
|
+
# end
|
82
|
+
#
|
83
|
+
module Handler
|
84
|
+
|
85
|
+
def self.included(base)
|
86
|
+
base.extend(ClassMethods)
|
87
|
+
base.class_eval do
|
88
|
+
include Lev::RoutineNesting
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def call(caller, params, options={})
|
93
|
+
in_transaction do
|
94
|
+
handle_guts(caller, params)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
module ClassMethods
|
99
|
+
def handle(caller, params, options={})
|
100
|
+
new.call(caller, params, options)
|
101
|
+
end
|
102
|
+
|
103
|
+
def paramify(group, options={}, &block)
|
104
|
+
method_name = "#{group.to_s}_params"
|
105
|
+
variable_sym = "@#{method_name}".to_sym
|
106
|
+
|
107
|
+
# Generate the dynamic ActiveAttr class given
|
108
|
+
# the paramify block; I think the caching of the class
|
109
|
+
# in paramify_classes is only necessary to maintain
|
110
|
+
# the name of the class set in the const_set statement
|
111
|
+
|
112
|
+
if paramify_classes[group].nil?
|
113
|
+
paramify_classes[group] = Class.new(Lev::Paramifier) do
|
114
|
+
include ActiveAttr::Model
|
115
|
+
cattr_accessor :group
|
116
|
+
end
|
117
|
+
paramify_classes[group].class_eval(&block)
|
118
|
+
paramify_classes[group].group = group
|
119
|
+
|
120
|
+
# Attach a name to this dynamic class
|
121
|
+
const_set("#{group.to_s.capitalize}Paramifier",
|
122
|
+
paramify_classes[group])
|
123
|
+
end
|
124
|
+
|
125
|
+
# Define the "#{group}_params" method to get the paramifier
|
126
|
+
# instance wrapping the params
|
127
|
+
define_method method_name.to_sym do
|
128
|
+
if !instance_variable_get(variable_sym)
|
129
|
+
instance_variable_set(variable_sym,
|
130
|
+
self.class.paramify_classes[group].new(params[group]))
|
131
|
+
end
|
132
|
+
instance_variable_get(variable_sym)
|
133
|
+
end
|
134
|
+
|
135
|
+
# Keep track of the accessor for the params so we can check
|
136
|
+
# errors in it later
|
137
|
+
paramify_methods.push(method_name.to_sym)
|
138
|
+
end
|
139
|
+
|
140
|
+
def paramify_methods
|
141
|
+
@paramify_methods ||= []
|
142
|
+
end
|
143
|
+
|
144
|
+
def paramify_classes
|
145
|
+
@paramify_classes ||= {}
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def transfer_errors_from(source, param_group)
|
150
|
+
ErrorTransferer.transfer(source, self, param_group)
|
151
|
+
end
|
152
|
+
|
153
|
+
attr_accessor :errors
|
154
|
+
|
155
|
+
protected
|
156
|
+
|
157
|
+
attr_accessor :params
|
158
|
+
attr_accessor :caller
|
159
|
+
attr_accessor :results
|
160
|
+
|
161
|
+
def handle_guts(caller, params)
|
162
|
+
self.params = params
|
163
|
+
self.caller = caller
|
164
|
+
self.errors = Errors.new
|
165
|
+
self.results = {}
|
166
|
+
|
167
|
+
setup
|
168
|
+
raise SecurityTransgression unless authorized?
|
169
|
+
validate_paramified_params
|
170
|
+
exec unless errors?
|
171
|
+
|
172
|
+
[self.results, self.errors]
|
173
|
+
end
|
174
|
+
|
175
|
+
def setup; end
|
176
|
+
|
177
|
+
def authorized?
|
178
|
+
false # default for safety, forces implementation in the handler
|
179
|
+
end
|
180
|
+
|
181
|
+
def validate_paramified_params
|
182
|
+
self.class.paramify_methods.each do |method|
|
183
|
+
params = send(method)
|
184
|
+
transfer_errors_from(params, params.group) if !params.valid?
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def errors?
|
189
|
+
errors.any?
|
190
|
+
end
|
191
|
+
|
192
|
+
end
|
193
|
+
|
194
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module Lev
|
2
|
+
|
3
|
+
# Manages running of routines inside other routines. In the Lev context,
|
4
|
+
# Handlers and Algorithms are routines. A routine and any routines nested
|
5
|
+
# inside of it are executed within a single transaction, or depending on the
|
6
|
+
# requirements of all the routines, no transaction at all.
|
7
|
+
#
|
8
|
+
# Classes that include this module get:
|
9
|
+
#
|
10
|
+
# 1) a "run" method for running nested routines in a standardized way.
|
11
|
+
# Routines executed through the run method get hooked into the calling
|
12
|
+
# hierarchy.
|
13
|
+
#
|
14
|
+
# 2) a "runner" accessor which points to the routine which called it. If
|
15
|
+
# runner is nil that means that no other routine called it (some other
|
16
|
+
# code did)
|
17
|
+
#
|
18
|
+
# 3) a "topmost_runner" which points to the highest routine in the calling
|
19
|
+
# hierarchy (that routine whose 'runner' is nil)
|
20
|
+
#
|
21
|
+
# Classes that include this module must:
|
22
|
+
#
|
23
|
+
# 1) supply a "call" instance method (def call(*args, &block)) that passes
|
24
|
+
# its arguments and block to whatever code inside the class does the work
|
25
|
+
# of the class
|
26
|
+
#
|
27
|
+
# Classes that include this module may:
|
28
|
+
#
|
29
|
+
# 1) Call the class-level "uses_routine" method to indicate which other
|
30
|
+
# routines will be run. Helps set isolation levels, etc. When this
|
31
|
+
# method is used, the provided routine may
|
32
|
+
#
|
33
|
+
# 2) Set a default transaction isolation level by declaring a class method
|
34
|
+
# named "default_transaction_isolation" that returns an instance of
|
35
|
+
# Lev::TransactionIsolation
|
36
|
+
#
|
37
|
+
#
|
38
|
+
module RoutineNesting
|
39
|
+
|
40
|
+
def self.included(base)
|
41
|
+
base.extend(ClassMethods)
|
42
|
+
end
|
43
|
+
|
44
|
+
module ClassMethods
|
45
|
+
|
46
|
+
# Called at a routine's class level to foretell which other routines will
|
47
|
+
# be used when this routine executes. Helpful for figuring out ahead of
|
48
|
+
# time what kind of transaction isolation level should be used.
|
49
|
+
def uses_routine(routine_class, options={})
|
50
|
+
symbol = options[:as] || routine_class.name.underscore.gsub('/','_').to_sym
|
51
|
+
|
52
|
+
raise IllegalArgument, "Routine #{routine_class} has already been registered" \
|
53
|
+
if nested_routines[symbol]
|
54
|
+
|
55
|
+
nested_routines[symbol] = routine_class
|
56
|
+
|
57
|
+
transaction_isolation.replace_if_more_isolated(routine_class.transaction_isolation)
|
58
|
+
end
|
59
|
+
|
60
|
+
def transaction_isolation
|
61
|
+
@transaction_isolation ||= default_transaction_isolation
|
62
|
+
end
|
63
|
+
|
64
|
+
def default_transaction_isolation
|
65
|
+
TransactionIsolation.no_transaction
|
66
|
+
end
|
67
|
+
|
68
|
+
def nested_routines
|
69
|
+
@nested_routines ||= {}
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
def in_transaction(options={})
|
75
|
+
if self == topmost_runner || self.class.transaction_isolation == TransactionIsolation.no_transaction
|
76
|
+
yield
|
77
|
+
else
|
78
|
+
ActiveRecord::Base.isolation_level( self.class.transaction_isolation.symbol ) do
|
79
|
+
ActiveRecord::Base.transaction { yield }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def run(other_routine, *args, &block)
|
85
|
+
if other_routine.is_a? Symbol
|
86
|
+
nested_routine = self.class.nested_routines[other_routine]
|
87
|
+
if nested_routine.nil?
|
88
|
+
raise IllegalArgument,
|
89
|
+
"Routine symbol #{other_routine} does not point to a registered routine"
|
90
|
+
end
|
91
|
+
other_routine = nested_routine
|
92
|
+
end
|
93
|
+
|
94
|
+
other_routine = other_routine.new if other_routine.is_a? Class
|
95
|
+
|
96
|
+
included_modules = other_routine.eigenclass.included_modules
|
97
|
+
|
98
|
+
raise IllegalArgument, "Can only run another nested routine" \
|
99
|
+
if !(included_modules.include? Lev::RoutineNesting)
|
100
|
+
|
101
|
+
other_routine.runner = self
|
102
|
+
other_routine.call(*args, &block)
|
103
|
+
end
|
104
|
+
|
105
|
+
attr_reader :runner
|
106
|
+
|
107
|
+
protected
|
108
|
+
|
109
|
+
attr_writer :runner
|
110
|
+
|
111
|
+
def topmost_runner
|
112
|
+
runner.nil? ? self : runner.topmost_runner
|
113
|
+
end
|
114
|
+
|
115
|
+
def runner=(runner)
|
116
|
+
@runner = runner
|
117
|
+
|
118
|
+
if topmost_runner.class.transaction_isolation.weaker_than(self.class.default_transaction_isolation)
|
119
|
+
raise IsolationMismatch,
|
120
|
+
"The routine being run has a stronger isolation requirement than " +
|
121
|
+
"the isolation being used by the routine(s) running it; call the " +
|
122
|
+
"'uses' method in the running routine's initializer"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Lev
|
2
|
+
class TransactionIsolation
|
3
|
+
|
4
|
+
def initialize(symbol)
|
5
|
+
raise IllegalArgument, "Invalid isolation symbol" if !@@symbols_to_isolation_levels.has_key?(symbol)
|
6
|
+
@symbol = symbol
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.no_transaction; new(:no_transaction); end
|
10
|
+
def self.read_uncommitted; new(:read_uncommitted); end
|
11
|
+
def self.read_committed; new(:read_committed); end
|
12
|
+
def self.repeatable_read; new(:repeatable_read); end
|
13
|
+
def self.serializable; new(:serializable); end
|
14
|
+
|
15
|
+
|
16
|
+
def replace_if_more_isolated(other_transaction_isolation)
|
17
|
+
if other_transaction_isolation.isolation_level > self.isolation_level
|
18
|
+
self.symbol = other_transaction_isolation.symbol
|
19
|
+
end
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
def weaker_than(other)
|
24
|
+
self.isolation_level < other.isolation_level
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.mysql_default
|
28
|
+
# MySQL default per https://blog.engineyard.com/2010/a-gentle-introduction-to-isolation-levels
|
29
|
+
repeatable_read
|
30
|
+
end
|
31
|
+
|
32
|
+
def ==(other)
|
33
|
+
self.symbol == other.symbol
|
34
|
+
end
|
35
|
+
|
36
|
+
def eql?(other)
|
37
|
+
self == other
|
38
|
+
end
|
39
|
+
|
40
|
+
attr_reader :symbol
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
def isolation_level
|
45
|
+
@@symbols_to_isolation_levels[symbol]
|
46
|
+
end
|
47
|
+
|
48
|
+
@@symbols_to_isolation_levels = {
|
49
|
+
no_transaction: 0,
|
50
|
+
read_uncommitted: 1,
|
51
|
+
read_committed: 2,
|
52
|
+
repeatable_read: 3,
|
53
|
+
serializable: 4
|
54
|
+
}
|
55
|
+
|
56
|
+
attr_writer :symbol
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
data/lib/lev/version.rb
ADDED
data/lib/lev.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
require "transaction_isolation"
|
2
|
+
require "transaction_retry"
|
3
|
+
require "active_attr"
|
4
|
+
|
5
|
+
require "lev/version"
|
6
|
+
require "lev/exceptions"
|
7
|
+
require "lev/routine_nesting"
|
8
|
+
require "lev/better_active_model_errors"
|
9
|
+
require "lev/handler"
|
10
|
+
require "lev/handle_with"
|
11
|
+
require "lev/handler_helper"
|
12
|
+
require "lev/handler/error"
|
13
|
+
require "lev/handler/errors"
|
14
|
+
require "lev/handler/error_transferer"
|
15
|
+
require "lev/handler/error_translator"
|
16
|
+
require "lev/form_builder"
|
17
|
+
require "lev/algorithm"
|
18
|
+
require "lev/delegate_to_algorithm"
|
19
|
+
require "lev/transaction_isolation"
|
20
|
+
|
21
|
+
|
22
|
+
module Lev
|
23
|
+
class << self
|
24
|
+
|
25
|
+
###########################################################################
|
26
|
+
#
|
27
|
+
# Configuration machinery.
|
28
|
+
#
|
29
|
+
# To configure Lev, put the following code in your applications
|
30
|
+
# initialization logic (eg. in the config/initializers in a Rails app)
|
31
|
+
#
|
32
|
+
# Lev.configure do |config|
|
33
|
+
# config.form_error_class = 'fancy_error'
|
34
|
+
# ...
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
|
38
|
+
def configure
|
39
|
+
yield configuration
|
40
|
+
end
|
41
|
+
|
42
|
+
def configuration
|
43
|
+
@configuration ||= Configuration.new
|
44
|
+
end
|
45
|
+
|
46
|
+
class Configuration
|
47
|
+
# This HTML class is added to form fields that caused errors
|
48
|
+
attr_accessor :form_error_class
|
49
|
+
|
50
|
+
def initialize
|
51
|
+
@form_error_class = 'error'
|
52
|
+
super
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|