lev 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|