riveter 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.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +17 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +147 -0
- data/LICENSE.txt +23 -0
- data/README.md +77 -0
- data/Rakefile +7 -0
- data/app/helpers/riveter/command_form_helper.rb +15 -0
- data/app/helpers/riveter/enquiry_form_helper.rb +16 -0
- data/app/helpers/riveter/query_filter_form_helper.rb +16 -0
- data/config/locales/forms.en.yml +25 -0
- data/config/locales/validators.en.yml +26 -0
- data/init.rb +25 -0
- data/lib/generators/erb/TODO +1 -0
- data/lib/generators/haml/command_controller/USAGE +0 -0
- data/lib/generators/haml/command_controller/command_controller_generator.rb +56 -0
- data/lib/generators/haml/command_controller/templates/new.html.haml +26 -0
- data/lib/generators/haml/enquiry_controller/USAGE +0 -0
- data/lib/generators/haml/enquiry_controller/enquiry_controller_generator.rb +56 -0
- data/lib/generators/haml/enquiry_controller/templates/index.html.haml +57 -0
- data/lib/generators/riveter/command/USAGE +0 -0
- data/lib/generators/riveter/command/command_generator.rb +55 -0
- data/lib/generators/riveter/command/templates/command.rb +40 -0
- data/lib/generators/riveter/command/templates/commands.en.yml +57 -0
- data/lib/generators/riveter/command/templates/module.rb +4 -0
- data/lib/generators/riveter/command_controller/USAGE +0 -0
- data/lib/generators/riveter/command_controller/command_controller_generator.rb +40 -0
- data/lib/generators/riveter/command_controller/templates/command_controller.rb +18 -0
- data/lib/generators/riveter/command_controller/templates/module.rb +4 -0
- data/lib/generators/riveter/enquiry/USAGE +0 -0
- data/lib/generators/riveter/enquiry/enquiry_generator.rb +29 -0
- data/lib/generators/riveter/enquiry/templates/enquiry.rb +11 -0
- data/lib/generators/riveter/enquiry/templates/module.rb +4 -0
- data/lib/generators/riveter/enquiry_controller/USAGE +0 -0
- data/lib/generators/riveter/enquiry_controller/enquiry_controller_generator.rb +34 -0
- data/lib/generators/riveter/enquiry_controller/templates/enquiry_controller.rb +9 -0
- data/lib/generators/riveter/enquiry_controller/templates/module.rb +4 -0
- data/lib/generators/riveter/enum/USAGE +41 -0
- data/lib/generators/riveter/enum/enum_generator.rb +51 -0
- data/lib/generators/riveter/enum/templates/enum.rb +17 -0
- data/lib/generators/riveter/enum/templates/enums.en.yml +35 -0
- data/lib/generators/riveter/enum/templates/module.rb +4 -0
- data/lib/generators/riveter/install/USAGE +0 -0
- data/lib/generators/riveter/install/install_generator.rb +14 -0
- data/lib/generators/riveter/presenter/USAGE +0 -0
- data/lib/generators/riveter/presenter/presenter_generator.rb +20 -0
- data/lib/generators/riveter/presenter/templates/module.rb +4 -0
- data/lib/generators/riveter/presenter/templates/presenter.rb +7 -0
- data/lib/generators/riveter/query/USAGE +22 -0
- data/lib/generators/riveter/query/query_generator.rb +20 -0
- data/lib/generators/riveter/query/templates/module.rb +4 -0
- data/lib/generators/riveter/query/templates/query.rb +31 -0
- data/lib/generators/riveter/query_filter/USAGE +0 -0
- data/lib/generators/riveter/query_filter/query_filter_generator.rb +52 -0
- data/lib/generators/riveter/query_filter/templates/module.rb +4 -0
- data/lib/generators/riveter/query_filter/templates/query_filter.rb +40 -0
- data/lib/generators/riveter/query_filter/templates/query_filters.en.yml +49 -0
- data/lib/generators/riveter/service/USAGE +0 -0
- data/lib/generators/riveter/service/service_generator.rb +20 -0
- data/lib/generators/riveter/service/templates/module.rb +4 -0
- data/lib/generators/riveter/service/templates/service.rb +12 -0
- data/lib/generators/riveter/worker/USAGE +0 -0
- data/lib/generators/riveter/worker/templates/module.rb +4 -0
- data/lib/generators/riveter/worker/templates/worker.rb +7 -0
- data/lib/generators/riveter/worker/worker_generator.rb +20 -0
- data/lib/generators/rspec/command/USAGE +0 -0
- data/lib/generators/rspec/command/command_generator.rb +11 -0
- data/lib/generators/rspec/command/templates/command_spec.rb +8 -0
- data/lib/generators/rspec/command_controller/USAGE +0 -0
- data/lib/generators/rspec/command_controller/command_controller_generator.rb +21 -0
- data/lib/generators/rspec/command_controller/templates/command_controller_spec.rb +70 -0
- data/lib/generators/rspec/enquiry/USAGE +0 -0
- data/lib/generators/rspec/enquiry/enquiry_generator.rb +11 -0
- data/lib/generators/rspec/enquiry/templates/enquiry_spec.rb +69 -0
- data/lib/generators/rspec/enquiry_controller/USAGE +0 -0
- data/lib/generators/rspec/enquiry_controller/enquiry_controller_generator.rb +16 -0
- data/lib/generators/rspec/enquiry_controller/templates/enquiry_controller_spec.rb +32 -0
- data/lib/generators/rspec/enum/USAGE +0 -0
- data/lib/generators/rspec/enum/enum_generator.rb +11 -0
- data/lib/generators/rspec/enum/templates/enum_spec.rb +8 -0
- data/lib/generators/rspec/presenter/USAGE +0 -0
- data/lib/generators/rspec/presenter/presenter_generator.rb +11 -0
- data/lib/generators/rspec/presenter/templates/presenter_spec.rb +8 -0
- data/lib/generators/rspec/query/USAGE +0 -0
- data/lib/generators/rspec/query/query_generator.rb +11 -0
- data/lib/generators/rspec/query/templates/query_spec.rb +41 -0
- data/lib/generators/rspec/query_filter/USAGE +0 -0
- data/lib/generators/rspec/query_filter/query_filter_generator.rb +11 -0
- data/lib/generators/rspec/query_filter/templates/query_filter_spec.rb +8 -0
- data/lib/generators/rspec/service/USAGE +0 -0
- data/lib/generators/rspec/service/service_generator.rb +11 -0
- data/lib/generators/rspec/service/templates/service_spec.rb +8 -0
- data/lib/generators/rspec/worker/USAGE +0 -0
- data/lib/generators/rspec/worker/templates/worker_spec.rb +8 -0
- data/lib/generators/rspec/worker/worker_generator.rb +11 -0
- data/lib/generators/test_unit/TODO +1 -0
- data/lib/riveter/associated_type_registry.rb +63 -0
- data/lib/riveter/attribute_default_values.rb +67 -0
- data/lib/riveter/attributes.rb +443 -0
- data/lib/riveter/booleaness_validator.rb +20 -0
- data/lib/riveter/command.rb +59 -0
- data/lib/riveter/command_controller.rb +93 -0
- data/lib/riveter/command_routes.rb +73 -0
- data/lib/riveter/core_extensions.rb +246 -0
- data/lib/riveter/email_validator.rb +20 -0
- data/lib/riveter/enquiry.rb +137 -0
- data/lib/riveter/enquiry_controller.rb +80 -0
- data/lib/riveter/enquiry_routes.rb +69 -0
- data/lib/riveter/enumerated.rb +107 -0
- data/lib/riveter/errors.rb +11 -0
- data/lib/riveter/form_builder_extensions.rb +21 -0
- data/lib/riveter/hash_with_dependency.rb +12 -0
- data/lib/riveter/presenter.rb +73 -0
- data/lib/riveter/query.rb +45 -0
- data/lib/riveter/query_filter.rb +22 -0
- data/lib/riveter/rails/engine.rb +6 -0
- data/lib/riveter/rails/railtie.rb +50 -0
- data/lib/riveter/service.rb +45 -0
- data/lib/riveter/spec_helper.rb +55 -0
- data/lib/riveter/tasks/.keep +0 -0
- data/lib/riveter/version.rb +3 -0
- data/lib/riveter/worker.rb +20 -0
- data/lib/riveter.rb +47 -0
- data/riveter.gemspec +40 -0
- data/spec/examples/attribute_examples.rb +57 -0
- data/spec/generators/haml/command_controller/command_controller_generator_spec.rb +34 -0
- data/spec/generators/haml/enquiry_controller/enquiry_controller_generator_spec.rb +34 -0
- data/spec/generators/riveter/command/command_generator_spec.rb +58 -0
- data/spec/generators/riveter/command_controller/command_controller_generator_spec.rb +0 -0
- data/spec/generators/riveter/enquiry/enquiry_generator_spec.rb +0 -0
- data/spec/generators/riveter/query_filter/query_filter_generator_spec.rb +58 -0
- data/spec/riveter/associated_type_registry_spec.rb +158 -0
- data/spec/riveter/attribute_default_value_spec.rb +69 -0
- data/spec/riveter/attributes_spec.rb +228 -0
- data/spec/riveter/command_controller_spec.rb +116 -0
- data/spec/riveter/command_routes_spec.rb +116 -0
- data/spec/riveter/command_spec.rb +66 -0
- data/spec/riveter/core_extensions_spec.rb +209 -0
- data/spec/riveter/enquiry_controller_spec.rb +128 -0
- data/spec/riveter/enquiry_routes_spec.rb +101 -0
- data/spec/riveter/enquiry_spec.rb +299 -0
- data/spec/riveter/enumerated_spec.rb +47 -0
- data/spec/riveter/form_builder_extensions_spec.rb +28 -0
- data/spec/riveter/presenter_spec.rb +131 -0
- data/spec/riveter/query_filter_spec.rb +19 -0
- data/spec/riveter/query_spec.rb +72 -0
- data/spec/riveter/service_spec.rb +49 -0
- data/spec/riveter/spec_helper_spec.rb +21 -0
- data/spec/riveter/worker_spec.rb +11 -0
- data/spec/spec_helper.rb +54 -0
- data/spec/support/test_associated_class.rb +2 -0
- data/spec/support/test_class_with_attributes.rb +17 -0
- data/spec/support/test_command.rb +4 -0
- data/spec/support/test_command_controller.rb +12 -0
- data/spec/support/test_command_routes.rb +3 -0
- data/spec/support/test_enquiry.rb +7 -0
- data/spec/support/test_enquiry_controller.rb +12 -0
- data/spec/support/test_enquiry_routes.rb +3 -0
- data/spec/support/test_enum.rb +8 -0
- data/spec/support/test_form_builder.rb +3 -0
- data/spec/support/test_model.rb +2 -0
- data/spec/support/test_model_with_attribute_default_values.rb +29 -0
- data/spec/support/test_presenter.rb +2 -0
- data/spec/support/test_query.rb +5 -0
- data/spec/support/test_query_filter.rb +4 -0
- data/spec/support/test_service.rb +7 -0
- data/spec/support/validate_booleaness_of_matcher.rb +17 -0
- data/spec/support/validate_timeliness_of_matcher.rb +17 -0
- data/spec/support/validator_detector.rb +48 -0
- metadata +487 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
module Riveter
|
|
2
|
+
module CommandController
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
# FIXME: provide ability to define more than one command per controller
|
|
6
|
+
|
|
7
|
+
included do
|
|
8
|
+
class << self
|
|
9
|
+
#
|
|
10
|
+
# configures the controller for the given command
|
|
11
|
+
#
|
|
12
|
+
# you must provide an `on_success` method and optionally an `on_failure` method which will
|
|
13
|
+
# receive callbacks when the 'create' action is run
|
|
14
|
+
#
|
|
15
|
+
# options supported include:
|
|
16
|
+
#
|
|
17
|
+
# :as defines the name of the form used to access the params
|
|
18
|
+
# defaults to the name of the command
|
|
19
|
+
#
|
|
20
|
+
# :new_action overrides the new action name
|
|
21
|
+
# by default it is "new"
|
|
22
|
+
#
|
|
23
|
+
# :create_action overrides the create action name
|
|
24
|
+
# by default it is "create"
|
|
25
|
+
#
|
|
26
|
+
# :attributes the list of permitted attributes for initializing the command instance
|
|
27
|
+
# defaults to the attributes defined using the `attr_*` helper methods
|
|
28
|
+
#
|
|
29
|
+
def command_controller_for(command_class, options={})
|
|
30
|
+
raise ArgumentError, "#{command_class.name} does not include #{Command.name} module or inherit from #{Command::Base.name}" unless command_class.ancestors.include?(Command)
|
|
31
|
+
|
|
32
|
+
options = {
|
|
33
|
+
:as => command_class.name.underscore.gsub(/_command$/, ''),
|
|
34
|
+
:attributes => command_class.attributes,
|
|
35
|
+
:new_action => :new,
|
|
36
|
+
:create_action => :create
|
|
37
|
+
}.merge(options)
|
|
38
|
+
|
|
39
|
+
# define instance methods
|
|
40
|
+
# which provide access to the given
|
|
41
|
+
# command class and the options
|
|
42
|
+
define_method :command_class do
|
|
43
|
+
command_class
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
define_method :command_options do
|
|
47
|
+
options
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# define the 'new' and 'create' actions
|
|
51
|
+
class_eval <<-RUBY
|
|
52
|
+
def #{options[:new_action]}
|
|
53
|
+
@command = create_command
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def #{options[:create_action]}
|
|
57
|
+
@command = create_command
|
|
58
|
+
if result = @command.submit(command_params)
|
|
59
|
+
on_success(@command, result)
|
|
60
|
+
else
|
|
61
|
+
self.respond_to?(:on_failure) ? on_failure(@command) : render(:new)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
RUBY
|
|
65
|
+
|
|
66
|
+
self.include ActionsAndSupport
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
module ActionsAndSupport
|
|
72
|
+
extend ActiveSupport::Concern
|
|
73
|
+
|
|
74
|
+
included do
|
|
75
|
+
# define the on success callback
|
|
76
|
+
# method as a placeholder to ensure
|
|
77
|
+
# implementors provide the method in their classes
|
|
78
|
+
def on_success(command, result)
|
|
79
|
+
raise NotImplementedError
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def create_command
|
|
83
|
+
command_class.new()
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def command_params
|
|
87
|
+
params.require(command_options[:as])
|
|
88
|
+
.permit(*command_options[:attributes])
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
require 'action_dispatch'
|
|
2
|
+
|
|
3
|
+
module Riveter
|
|
4
|
+
module CommandRoutes
|
|
5
|
+
|
|
6
|
+
#
|
|
7
|
+
# defines a route for the given command
|
|
8
|
+
#
|
|
9
|
+
# by convention, the word "command" is omitted
|
|
10
|
+
# from the name of the command
|
|
11
|
+
#
|
|
12
|
+
# options include:
|
|
13
|
+
#
|
|
14
|
+
# :path overrides the path used.
|
|
15
|
+
# by default it is "<command>_command".
|
|
16
|
+
# E.g. for :create_portfolio the path is 'create_portfolio'
|
|
17
|
+
#
|
|
18
|
+
# :controller overrides the controller used.
|
|
19
|
+
# by default it is "<command>_command_controller"
|
|
20
|
+
#
|
|
21
|
+
# :as overrides the name of the route generated.
|
|
22
|
+
# by default it is "<command>_command"
|
|
23
|
+
#
|
|
24
|
+
# :new_action overrides the new action name
|
|
25
|
+
# by default it is "new"
|
|
26
|
+
#
|
|
27
|
+
# :create_action overrides the create action name
|
|
28
|
+
# by default it is "create"
|
|
29
|
+
#
|
|
30
|
+
# E.g.
|
|
31
|
+
#
|
|
32
|
+
# command :create_portfolio
|
|
33
|
+
#
|
|
34
|
+
# creates a route 'create_portfolio' named `create_portfolio_command` to the
|
|
35
|
+
# `CreatePortfolioCommandController` controller for the 'new' and 'create' actions
|
|
36
|
+
#
|
|
37
|
+
# command :create_portfolio, :as => 'new_portfolio'
|
|
38
|
+
#
|
|
39
|
+
# creates a route 'create_portfolio' named `new_portfolio` to the
|
|
40
|
+
# `CreatePortfolioCommandController` controller for the 'new' and 'create' actions
|
|
41
|
+
#
|
|
42
|
+
# command :create_portfolio, :path => '/define_portfolio'
|
|
43
|
+
#
|
|
44
|
+
# creates a route 'define_portfolio' named `create_portfolio_command` to the
|
|
45
|
+
# `CreatePortfolioCommandController` controller for the 'new' and 'create' actions
|
|
46
|
+
#
|
|
47
|
+
# command :create_portfolio, :controller => 'my_portfolio_controller'
|
|
48
|
+
#
|
|
49
|
+
# creates a route 'create_portfolio' named `create_portfolio_command` to the
|
|
50
|
+
# `MyPortfolioController` controller for the 'new' and 'create' actions
|
|
51
|
+
#
|
|
52
|
+
def command(command, options={})
|
|
53
|
+
raise ArgumentError, 'Expects a symbol for the command.' unless command.is_a?(Symbol)
|
|
54
|
+
|
|
55
|
+
path = options.delete(:path) || command.to_s
|
|
56
|
+
new_action = options.delete(:new_action) || :new
|
|
57
|
+
create_action = options.delete(:create_action) || :create
|
|
58
|
+
|
|
59
|
+
options = {
|
|
60
|
+
:controller => :"#{command}_command",
|
|
61
|
+
:as => :"#{command}_command"
|
|
62
|
+
}.merge(options)
|
|
63
|
+
|
|
64
|
+
# define GET 'new' route
|
|
65
|
+
get path, options.merge(:action => new_action)
|
|
66
|
+
|
|
67
|
+
# define POST 'create' route
|
|
68
|
+
post path, options.merge(:action => create_action, :as => nil)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
ActionDispatch::Routing::Mapper.send :include, Riveter::CommandRoutes
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
module Riveter
|
|
2
|
+
module CoreExtensions
|
|
3
|
+
module BooleanSupport
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
Booleans = ['yes', 'on', 'true', 'y', '1', 1, true]
|
|
7
|
+
|
|
8
|
+
def boolean?
|
|
9
|
+
self.is_a?(TrueClass) || self.is_a?(FalseClass)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def to_b
|
|
13
|
+
Booleans.include?(self.to_s.downcase)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
module DateExtensions
|
|
18
|
+
extend ActiveSupport::Concern
|
|
19
|
+
|
|
20
|
+
def to_utc_ticks
|
|
21
|
+
Time.utc(self.year, self.month, self.day).to_i * 1000
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
module ClassMethods
|
|
25
|
+
def system_start_date
|
|
26
|
+
self.new(1970, 1, 1)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def from_utc_ticks(ticks)
|
|
30
|
+
(DateTime.strptime ticks.to_s, "%Q").to_date
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
module ArrayExtensions
|
|
36
|
+
extend ActiveSupport::Concern
|
|
37
|
+
|
|
38
|
+
def cumulative_sum
|
|
39
|
+
sum = 0
|
|
40
|
+
self.map {|x| sum += x}
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# pure genious
|
|
44
|
+
def nil_sum(identity = nil, &block)
|
|
45
|
+
if block_given?
|
|
46
|
+
self.inject(identity) {|sum, x|
|
|
47
|
+
((sum || 0) + (yield(x) || 0)) || sum
|
|
48
|
+
}
|
|
49
|
+
else
|
|
50
|
+
self.inject(identity) {|sum, x|
|
|
51
|
+
((sum || 0) + (x || 0)) || sum
|
|
52
|
+
}
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# average of a set of numbers
|
|
57
|
+
def average
|
|
58
|
+
self.sum / self.length.to_f
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# variance of a set of numbers
|
|
62
|
+
def variance
|
|
63
|
+
avg = self.average
|
|
64
|
+
self.inject(0.0) {|acc, value| acc + ((value - avg)**2) } / (self.length.to_f - 1)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# standard deviation of an array of numbers
|
|
68
|
+
def standard_deviation
|
|
69
|
+
Math.sqrt(self.variance)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# returns a hash of the items, indexed by the given items attribute
|
|
73
|
+
def to_hash_for(&block)
|
|
74
|
+
if block_given?
|
|
75
|
+
self.inject({}) do |hash, record|
|
|
76
|
+
hash[yield(record)] = record
|
|
77
|
+
hash
|
|
78
|
+
end
|
|
79
|
+
else
|
|
80
|
+
self.inject({}) do |hash, record|
|
|
81
|
+
hash[record] = record
|
|
82
|
+
hash
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# rounds each items to the specified number of places
|
|
88
|
+
def round(places)
|
|
89
|
+
self.collect {|x| x.round(places) }
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# make compatible with an ActiveRelation
|
|
93
|
+
def find_each_with_order(&block)
|
|
94
|
+
self.each &block if block_given?
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
alias_method :find_each, :find_each_with_order
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
module HashExtensions
|
|
101
|
+
#
|
|
102
|
+
# = Hash Recursive Merge
|
|
103
|
+
#
|
|
104
|
+
# Merges a Ruby Hash recursively, Also known as deep merge.
|
|
105
|
+
# Recursive version of Hash#merge and Hash#merge!.
|
|
106
|
+
#
|
|
107
|
+
# Category:: Ruby
|
|
108
|
+
# Package:: Hash
|
|
109
|
+
# Author:: Simone Carletti <weppos@weppos.net>
|
|
110
|
+
# Copyright:: 2007-2008 The Authors
|
|
111
|
+
# License:: MIT License
|
|
112
|
+
# Link:: http://www.simonecarletti.com/
|
|
113
|
+
# Source:: http://gist.github.com/gists/6391/
|
|
114
|
+
#
|
|
115
|
+
# Adds the contents of +other_hash+ to +hsh+,
|
|
116
|
+
# merging entries in +hsh+ with duplicate keys with those from +other_hash+.
|
|
117
|
+
#
|
|
118
|
+
# Compared with Hash#merge!, this method supports nested hashes.
|
|
119
|
+
# When both +hsh+ and +other_hash+ contains an entry with the same key,
|
|
120
|
+
# it merges and returns the values from both arrays.
|
|
121
|
+
#
|
|
122
|
+
# h1 = {"a" => 100, "b" => 200, "c" => {"c1" => 12, "c2" => 14}}
|
|
123
|
+
# h2 = {"b" => 254, "c" => 300, "c" => {"c1" => 16, "c3" => 94}}
|
|
124
|
+
# h1.rmerge!(h2) #=> {"a" => 100, "b" => 254, "c" => {"c1" => 16, "c2" => 14, "c3" => 94}}
|
|
125
|
+
#
|
|
126
|
+
# Simply using Hash#merge! would return
|
|
127
|
+
#
|
|
128
|
+
# h1.merge!(h2) #=> {"a" => 100, "b" = >254, "c" => {"c1" => 16, "c3" => 94}}
|
|
129
|
+
#
|
|
130
|
+
def rmerge!(other_hash)
|
|
131
|
+
merge!(other_hash) do |key, oldval, newval|
|
|
132
|
+
oldval.class == self.class ? oldval.rmerge!(newval) : newval
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
#
|
|
137
|
+
# Recursive version of Hash#merge
|
|
138
|
+
#
|
|
139
|
+
# Compared with Hash#merge!, this method supports nested hashes.
|
|
140
|
+
# When both +hsh+ and +other_hash+ contains an entry with the same key,
|
|
141
|
+
# it merges and returns the values from both arrays.
|
|
142
|
+
#
|
|
143
|
+
# Compared with Hash#merge, this method provides a different approch
|
|
144
|
+
# for merging nasted hashes.
|
|
145
|
+
# If the value of a given key is an Hash and both +other_hash+ abd +hsh
|
|
146
|
+
# includes the same key, the value is merged instead replaced with
|
|
147
|
+
# +other_hash+ value.
|
|
148
|
+
#
|
|
149
|
+
# h1 = {"a" => 100, "b" => 200, "c" => {"c1" => 12, "c2" => 14}}
|
|
150
|
+
# h2 = {"b" => 254, "c" => 300, "c" => {"c1" => 16, "c3" => 94}}
|
|
151
|
+
# h1.rmerge(h2) #=> {"a" => 100, "b" => 254, "c" => {"c1" => 16, "c2" => 14, "c3" => 94}}
|
|
152
|
+
#
|
|
153
|
+
# Simply using Hash#merge would return
|
|
154
|
+
#
|
|
155
|
+
# h1.merge(h2) #=> {"a" => 100, "b" = >254, "c" => {"c1" => 16, "c3" => 94}}
|
|
156
|
+
#
|
|
157
|
+
def rmerge(other_hash)
|
|
158
|
+
r = {}
|
|
159
|
+
merge(other_hash) do |key, oldval, newval|
|
|
160
|
+
r[key] = oldval.class == self.class ? oldval.rmerge(newval) : newval
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
module ChainedQuerySupport
|
|
166
|
+
extend ActiveSupport::Concern
|
|
167
|
+
|
|
168
|
+
# returns a new relation, which is the result of filtering the current
|
|
169
|
+
# relation according to the conditions in the arguments if the given
|
|
170
|
+
# condition is met
|
|
171
|
+
def where?(condition, *args)
|
|
172
|
+
condition ? self.where(*args) : self
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
module BatchFinderSupport
|
|
177
|
+
extend ActiveSupport::Concern
|
|
178
|
+
|
|
179
|
+
# finds each record in batches while preserving
|
|
180
|
+
# the specified order of the relation
|
|
181
|
+
def find_each_with_order(options={})
|
|
182
|
+
return to_enum(__method__, options) unless block_given?
|
|
183
|
+
find_in_batches_with_order(options) do |records|
|
|
184
|
+
records.each { |record| yield record }
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# finds each record in batches while preserving
|
|
189
|
+
# the specified order of the relation
|
|
190
|
+
# NOTE: any limit() on the query is overridden with the batch size
|
|
191
|
+
def find_in_batches_with_order(options={})
|
|
192
|
+
return to_enum(__method__, options) unless block_given?
|
|
193
|
+
options.assert_valid_keys(:batch_size)
|
|
194
|
+
|
|
195
|
+
relation = self
|
|
196
|
+
|
|
197
|
+
start = 0
|
|
198
|
+
batch_size = options.delete(:batch_size) || 1000
|
|
199
|
+
|
|
200
|
+
relation = relation.limit(batch_size)
|
|
201
|
+
records = relation.offset(start).to_a
|
|
202
|
+
|
|
203
|
+
while records.any?
|
|
204
|
+
records_size = records.size
|
|
205
|
+
|
|
206
|
+
yield records
|
|
207
|
+
|
|
208
|
+
break if records_size < batch_size
|
|
209
|
+
|
|
210
|
+
# get the next batch
|
|
211
|
+
start += batch_size
|
|
212
|
+
records = relation.offset(start + 1).to_a
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# mixin extensions to respective classes
|
|
220
|
+
|
|
221
|
+
class Object
|
|
222
|
+
include Riveter::CoreExtensions::BooleanSupport
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
class Date
|
|
226
|
+
include Riveter::CoreExtensions::DateExtensions
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
class Array
|
|
230
|
+
include Riveter::CoreExtensions::ArrayExtensions
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
class Hash
|
|
234
|
+
include Riveter::CoreExtensions::HashExtensions
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
class ActiveRecord::Relation
|
|
238
|
+
include Riveter::CoreExtensions::ChainedQuerySupport
|
|
239
|
+
include Riveter::CoreExtensions::BatchFinderSupport
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
module ActiveRecord
|
|
243
|
+
module Querying
|
|
244
|
+
delegate :find_each_with_order, :find_in_batches_with_order, :to => :scoped
|
|
245
|
+
end
|
|
246
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Riveter
|
|
2
|
+
class EmailValidator < ActiveModel::EachValidator
|
|
3
|
+
def initialize(options)
|
|
4
|
+
@allow_nil, @allow_blank = options.delete(:allow_nil), options.delete(:allow_blank)
|
|
5
|
+
super
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def validate_each(record, attribute, value)
|
|
9
|
+
return if (@allow_nil && value.nil?) || (@allow_blank && value.blank?)
|
|
10
|
+
|
|
11
|
+
unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
|
|
12
|
+
record.errors.add(attribute, :email, :value => value)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# add compatibility with ActiveModel validates method which
|
|
19
|
+
# matches option keys to their validator class
|
|
20
|
+
ActiveModel::Validations::EmailValidator = Riveter::EmailValidator
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
module Riveter
|
|
2
|
+
module Enquiry
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
included do
|
|
6
|
+
extend ActiveModel::Naming
|
|
7
|
+
extend ActiveModel::Translation
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
alias_method :enquiry_name, :model_name
|
|
11
|
+
|
|
12
|
+
def i18n_scope
|
|
13
|
+
:enquiries
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
module ClassMethods
|
|
19
|
+
|
|
20
|
+
def filter_with(query_filter_klass=nil, &block)
|
|
21
|
+
raise "Filter already defined." if self.respond_to?(:query_filter_class)
|
|
22
|
+
|
|
23
|
+
unless query_filter_klass
|
|
24
|
+
raise ArgumentError, "Missing block" unless block_given?
|
|
25
|
+
|
|
26
|
+
# define an anomymous class derived from QueryFilter
|
|
27
|
+
# which invokes the block given in the classes context
|
|
28
|
+
query_filter_klass = Class.new(QueryFilter::Base)
|
|
29
|
+
query_filter_klass.class_eval &block
|
|
30
|
+
|
|
31
|
+
# for anonymous classes, need to override the
|
|
32
|
+
# model_name method so that forms etc can work
|
|
33
|
+
enquiry_name = self.enquiry_name
|
|
34
|
+
query_filter_klass.class_eval do
|
|
35
|
+
define_singleton_method :model_name do
|
|
36
|
+
enquiry_name
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
define_singleton_method :query_filter_class do
|
|
42
|
+
query_filter_klass
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
define_method :query_filter_class do
|
|
46
|
+
query_filter_klass
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
define_singleton_method :query_filter_attributes do
|
|
50
|
+
query_filter_klass.attributes
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def query_with(query_klass=nil, &block)
|
|
55
|
+
raise "Query already defined." if self.respond_to?(:query_class)
|
|
56
|
+
|
|
57
|
+
unless query_klass
|
|
58
|
+
raise ArgumentError, "Missing block" unless block_given?
|
|
59
|
+
|
|
60
|
+
# define an anomymous class derived from Query
|
|
61
|
+
# which delegates to the block given
|
|
62
|
+
query_klass = Class.new(Query::Base)
|
|
63
|
+
query_klass.class_eval do
|
|
64
|
+
define_method :build_relation, &block
|
|
65
|
+
protected :build_relation
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
define_singleton_method :query_class do
|
|
70
|
+
query_klass
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
define_method :query_class do
|
|
74
|
+
query_klass
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def initialize
|
|
80
|
+
# sanity check
|
|
81
|
+
base_message = "#{self.class.name} enquiry not configured correctly."
|
|
82
|
+
raise "#{base_message} No query filter specified. Use the `filter_with` method to provide the query filter to use." unless self.class.respond_to?(:query_filter_class)
|
|
83
|
+
raise "#{base_message} No query specified. Use the `query_with` method to provide the query to use." unless self.class.respond_to?(:query_class)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def query_filter
|
|
87
|
+
@query_filter ||= query_filter_class.new()
|
|
88
|
+
end
|
|
89
|
+
alias_method :filter, :query_filter
|
|
90
|
+
|
|
91
|
+
def query_filter_attributes
|
|
92
|
+
query_filter_class.attributes
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def submit(*args)
|
|
96
|
+
params = args.extract_options!
|
|
97
|
+
|
|
98
|
+
# filter and clean params before applying
|
|
99
|
+
@query_filter = create_query_filter(params)
|
|
100
|
+
|
|
101
|
+
# perform validations, and proceed if valid
|
|
102
|
+
return false unless @query_filter.valid?
|
|
103
|
+
|
|
104
|
+
# all good...
|
|
105
|
+
@query = create_query(@query_filter)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
attr_reader :query
|
|
109
|
+
|
|
110
|
+
def find_each(&block)
|
|
111
|
+
self.query && self.query.find_each(&block)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def has_data?
|
|
115
|
+
self.query && self.query.has_data?
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def query_results
|
|
119
|
+
self.query ? self.query.relation : nil
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
protected
|
|
123
|
+
|
|
124
|
+
def create_query_filter(params)
|
|
125
|
+
query_filter_class.new(params)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def create_query(query_filter)
|
|
129
|
+
query_class.new(query_filter)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# helper class
|
|
133
|
+
class Base
|
|
134
|
+
include Enquiry
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
module Riveter
|
|
2
|
+
module EnquiryController
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
# FIXME: provide ability to define more than one enquiry per controller
|
|
6
|
+
|
|
7
|
+
included do
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
#
|
|
11
|
+
# configures the controller for the given enquiry
|
|
12
|
+
#
|
|
13
|
+
# optionally define `on_success` and `on_failure` callback methods to
|
|
14
|
+
# provide additional controller logic as required
|
|
15
|
+
#
|
|
16
|
+
# options supported include:
|
|
17
|
+
#
|
|
18
|
+
# :as defines the name of the form used to access the params
|
|
19
|
+
# defaults to the name of the enquiry
|
|
20
|
+
#
|
|
21
|
+
# :action overrides the action name.
|
|
22
|
+
# by default is is ":index"
|
|
23
|
+
#
|
|
24
|
+
# :attributes the list of permitted attributes for initializing the enquiry instance
|
|
25
|
+
# defaults to the attributes defined using the `attr_*` helper methods
|
|
26
|
+
#
|
|
27
|
+
def enquiry_controller_for(enquiry_class, options={})
|
|
28
|
+
raise ArgumentError, "#{enquiry_class.name} does not include #{Enquiry.name} module or inherit from #{Enquiry::Base.name}" unless enquiry_class.ancestors.include?(Enquiry)
|
|
29
|
+
|
|
30
|
+
options = {
|
|
31
|
+
:as => enquiry_class.name.underscore.gsub(/_enquiry$/, ''),
|
|
32
|
+
:attributes => enquiry_class.query_filter_attributes,
|
|
33
|
+
:action => :index
|
|
34
|
+
}.merge(options)
|
|
35
|
+
|
|
36
|
+
action_method = options[:action]
|
|
37
|
+
|
|
38
|
+
# define instance methods
|
|
39
|
+
# which provide access to the given
|
|
40
|
+
# enquiry class and the options
|
|
41
|
+
define_method :enquiry_class do
|
|
42
|
+
enquiry_class
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
define_method :enquiry_options do
|
|
46
|
+
options
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# define the 'index' action
|
|
50
|
+
class_eval <<-RUBY
|
|
51
|
+
def #{action_method}
|
|
52
|
+
@enquiry = enquiry_class.new()
|
|
53
|
+
if @enquiry.submit(enquiry_params)
|
|
54
|
+
on_success(@enquiry) if self.respond_to?(:on_success)
|
|
55
|
+
else
|
|
56
|
+
on_failure(@enquiry) if self.respond_to?(:on_failure)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
RUBY
|
|
60
|
+
|
|
61
|
+
self.include ActionsAndSupport
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
module ActionsAndSupport
|
|
67
|
+
extend ActiveSupport::Concern
|
|
68
|
+
|
|
69
|
+
included do
|
|
70
|
+
# define the strong parameters method
|
|
71
|
+
def enquiry_params
|
|
72
|
+
params.key?(:reset) ? {} :
|
|
73
|
+
params.fetch(enquiry_options[:as], {})
|
|
74
|
+
.permit(*enquiry_options[:attributes])
|
|
75
|
+
.merge(:page => params.fetch(:page, 1))
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|