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.
Files changed (174) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.rspec +2 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +17 -0
  7. data/Gemfile +4 -0
  8. data/Gemfile.lock +147 -0
  9. data/LICENSE.txt +23 -0
  10. data/README.md +77 -0
  11. data/Rakefile +7 -0
  12. data/app/helpers/riveter/command_form_helper.rb +15 -0
  13. data/app/helpers/riveter/enquiry_form_helper.rb +16 -0
  14. data/app/helpers/riveter/query_filter_form_helper.rb +16 -0
  15. data/config/locales/forms.en.yml +25 -0
  16. data/config/locales/validators.en.yml +26 -0
  17. data/init.rb +25 -0
  18. data/lib/generators/erb/TODO +1 -0
  19. data/lib/generators/haml/command_controller/USAGE +0 -0
  20. data/lib/generators/haml/command_controller/command_controller_generator.rb +56 -0
  21. data/lib/generators/haml/command_controller/templates/new.html.haml +26 -0
  22. data/lib/generators/haml/enquiry_controller/USAGE +0 -0
  23. data/lib/generators/haml/enquiry_controller/enquiry_controller_generator.rb +56 -0
  24. data/lib/generators/haml/enquiry_controller/templates/index.html.haml +57 -0
  25. data/lib/generators/riveter/command/USAGE +0 -0
  26. data/lib/generators/riveter/command/command_generator.rb +55 -0
  27. data/lib/generators/riveter/command/templates/command.rb +40 -0
  28. data/lib/generators/riveter/command/templates/commands.en.yml +57 -0
  29. data/lib/generators/riveter/command/templates/module.rb +4 -0
  30. data/lib/generators/riveter/command_controller/USAGE +0 -0
  31. data/lib/generators/riveter/command_controller/command_controller_generator.rb +40 -0
  32. data/lib/generators/riveter/command_controller/templates/command_controller.rb +18 -0
  33. data/lib/generators/riveter/command_controller/templates/module.rb +4 -0
  34. data/lib/generators/riveter/enquiry/USAGE +0 -0
  35. data/lib/generators/riveter/enquiry/enquiry_generator.rb +29 -0
  36. data/lib/generators/riveter/enquiry/templates/enquiry.rb +11 -0
  37. data/lib/generators/riveter/enquiry/templates/module.rb +4 -0
  38. data/lib/generators/riveter/enquiry_controller/USAGE +0 -0
  39. data/lib/generators/riveter/enquiry_controller/enquiry_controller_generator.rb +34 -0
  40. data/lib/generators/riveter/enquiry_controller/templates/enquiry_controller.rb +9 -0
  41. data/lib/generators/riveter/enquiry_controller/templates/module.rb +4 -0
  42. data/lib/generators/riveter/enum/USAGE +41 -0
  43. data/lib/generators/riveter/enum/enum_generator.rb +51 -0
  44. data/lib/generators/riveter/enum/templates/enum.rb +17 -0
  45. data/lib/generators/riveter/enum/templates/enums.en.yml +35 -0
  46. data/lib/generators/riveter/enum/templates/module.rb +4 -0
  47. data/lib/generators/riveter/install/USAGE +0 -0
  48. data/lib/generators/riveter/install/install_generator.rb +14 -0
  49. data/lib/generators/riveter/presenter/USAGE +0 -0
  50. data/lib/generators/riveter/presenter/presenter_generator.rb +20 -0
  51. data/lib/generators/riveter/presenter/templates/module.rb +4 -0
  52. data/lib/generators/riveter/presenter/templates/presenter.rb +7 -0
  53. data/lib/generators/riveter/query/USAGE +22 -0
  54. data/lib/generators/riveter/query/query_generator.rb +20 -0
  55. data/lib/generators/riveter/query/templates/module.rb +4 -0
  56. data/lib/generators/riveter/query/templates/query.rb +31 -0
  57. data/lib/generators/riveter/query_filter/USAGE +0 -0
  58. data/lib/generators/riveter/query_filter/query_filter_generator.rb +52 -0
  59. data/lib/generators/riveter/query_filter/templates/module.rb +4 -0
  60. data/lib/generators/riveter/query_filter/templates/query_filter.rb +40 -0
  61. data/lib/generators/riveter/query_filter/templates/query_filters.en.yml +49 -0
  62. data/lib/generators/riveter/service/USAGE +0 -0
  63. data/lib/generators/riveter/service/service_generator.rb +20 -0
  64. data/lib/generators/riveter/service/templates/module.rb +4 -0
  65. data/lib/generators/riveter/service/templates/service.rb +12 -0
  66. data/lib/generators/riveter/worker/USAGE +0 -0
  67. data/lib/generators/riveter/worker/templates/module.rb +4 -0
  68. data/lib/generators/riveter/worker/templates/worker.rb +7 -0
  69. data/lib/generators/riveter/worker/worker_generator.rb +20 -0
  70. data/lib/generators/rspec/command/USAGE +0 -0
  71. data/lib/generators/rspec/command/command_generator.rb +11 -0
  72. data/lib/generators/rspec/command/templates/command_spec.rb +8 -0
  73. data/lib/generators/rspec/command_controller/USAGE +0 -0
  74. data/lib/generators/rspec/command_controller/command_controller_generator.rb +21 -0
  75. data/lib/generators/rspec/command_controller/templates/command_controller_spec.rb +70 -0
  76. data/lib/generators/rspec/enquiry/USAGE +0 -0
  77. data/lib/generators/rspec/enquiry/enquiry_generator.rb +11 -0
  78. data/lib/generators/rspec/enquiry/templates/enquiry_spec.rb +69 -0
  79. data/lib/generators/rspec/enquiry_controller/USAGE +0 -0
  80. data/lib/generators/rspec/enquiry_controller/enquiry_controller_generator.rb +16 -0
  81. data/lib/generators/rspec/enquiry_controller/templates/enquiry_controller_spec.rb +32 -0
  82. data/lib/generators/rspec/enum/USAGE +0 -0
  83. data/lib/generators/rspec/enum/enum_generator.rb +11 -0
  84. data/lib/generators/rspec/enum/templates/enum_spec.rb +8 -0
  85. data/lib/generators/rspec/presenter/USAGE +0 -0
  86. data/lib/generators/rspec/presenter/presenter_generator.rb +11 -0
  87. data/lib/generators/rspec/presenter/templates/presenter_spec.rb +8 -0
  88. data/lib/generators/rspec/query/USAGE +0 -0
  89. data/lib/generators/rspec/query/query_generator.rb +11 -0
  90. data/lib/generators/rspec/query/templates/query_spec.rb +41 -0
  91. data/lib/generators/rspec/query_filter/USAGE +0 -0
  92. data/lib/generators/rspec/query_filter/query_filter_generator.rb +11 -0
  93. data/lib/generators/rspec/query_filter/templates/query_filter_spec.rb +8 -0
  94. data/lib/generators/rspec/service/USAGE +0 -0
  95. data/lib/generators/rspec/service/service_generator.rb +11 -0
  96. data/lib/generators/rspec/service/templates/service_spec.rb +8 -0
  97. data/lib/generators/rspec/worker/USAGE +0 -0
  98. data/lib/generators/rspec/worker/templates/worker_spec.rb +8 -0
  99. data/lib/generators/rspec/worker/worker_generator.rb +11 -0
  100. data/lib/generators/test_unit/TODO +1 -0
  101. data/lib/riveter/associated_type_registry.rb +63 -0
  102. data/lib/riveter/attribute_default_values.rb +67 -0
  103. data/lib/riveter/attributes.rb +443 -0
  104. data/lib/riveter/booleaness_validator.rb +20 -0
  105. data/lib/riveter/command.rb +59 -0
  106. data/lib/riveter/command_controller.rb +93 -0
  107. data/lib/riveter/command_routes.rb +73 -0
  108. data/lib/riveter/core_extensions.rb +246 -0
  109. data/lib/riveter/email_validator.rb +20 -0
  110. data/lib/riveter/enquiry.rb +137 -0
  111. data/lib/riveter/enquiry_controller.rb +80 -0
  112. data/lib/riveter/enquiry_routes.rb +69 -0
  113. data/lib/riveter/enumerated.rb +107 -0
  114. data/lib/riveter/errors.rb +11 -0
  115. data/lib/riveter/form_builder_extensions.rb +21 -0
  116. data/lib/riveter/hash_with_dependency.rb +12 -0
  117. data/lib/riveter/presenter.rb +73 -0
  118. data/lib/riveter/query.rb +45 -0
  119. data/lib/riveter/query_filter.rb +22 -0
  120. data/lib/riveter/rails/engine.rb +6 -0
  121. data/lib/riveter/rails/railtie.rb +50 -0
  122. data/lib/riveter/service.rb +45 -0
  123. data/lib/riveter/spec_helper.rb +55 -0
  124. data/lib/riveter/tasks/.keep +0 -0
  125. data/lib/riveter/version.rb +3 -0
  126. data/lib/riveter/worker.rb +20 -0
  127. data/lib/riveter.rb +47 -0
  128. data/riveter.gemspec +40 -0
  129. data/spec/examples/attribute_examples.rb +57 -0
  130. data/spec/generators/haml/command_controller/command_controller_generator_spec.rb +34 -0
  131. data/spec/generators/haml/enquiry_controller/enquiry_controller_generator_spec.rb +34 -0
  132. data/spec/generators/riveter/command/command_generator_spec.rb +58 -0
  133. data/spec/generators/riveter/command_controller/command_controller_generator_spec.rb +0 -0
  134. data/spec/generators/riveter/enquiry/enquiry_generator_spec.rb +0 -0
  135. data/spec/generators/riveter/query_filter/query_filter_generator_spec.rb +58 -0
  136. data/spec/riveter/associated_type_registry_spec.rb +158 -0
  137. data/spec/riveter/attribute_default_value_spec.rb +69 -0
  138. data/spec/riveter/attributes_spec.rb +228 -0
  139. data/spec/riveter/command_controller_spec.rb +116 -0
  140. data/spec/riveter/command_routes_spec.rb +116 -0
  141. data/spec/riveter/command_spec.rb +66 -0
  142. data/spec/riveter/core_extensions_spec.rb +209 -0
  143. data/spec/riveter/enquiry_controller_spec.rb +128 -0
  144. data/spec/riveter/enquiry_routes_spec.rb +101 -0
  145. data/spec/riveter/enquiry_spec.rb +299 -0
  146. data/spec/riveter/enumerated_spec.rb +47 -0
  147. data/spec/riveter/form_builder_extensions_spec.rb +28 -0
  148. data/spec/riveter/presenter_spec.rb +131 -0
  149. data/spec/riveter/query_filter_spec.rb +19 -0
  150. data/spec/riveter/query_spec.rb +72 -0
  151. data/spec/riveter/service_spec.rb +49 -0
  152. data/spec/riveter/spec_helper_spec.rb +21 -0
  153. data/spec/riveter/worker_spec.rb +11 -0
  154. data/spec/spec_helper.rb +54 -0
  155. data/spec/support/test_associated_class.rb +2 -0
  156. data/spec/support/test_class_with_attributes.rb +17 -0
  157. data/spec/support/test_command.rb +4 -0
  158. data/spec/support/test_command_controller.rb +12 -0
  159. data/spec/support/test_command_routes.rb +3 -0
  160. data/spec/support/test_enquiry.rb +7 -0
  161. data/spec/support/test_enquiry_controller.rb +12 -0
  162. data/spec/support/test_enquiry_routes.rb +3 -0
  163. data/spec/support/test_enum.rb +8 -0
  164. data/spec/support/test_form_builder.rb +3 -0
  165. data/spec/support/test_model.rb +2 -0
  166. data/spec/support/test_model_with_attribute_default_values.rb +29 -0
  167. data/spec/support/test_presenter.rb +2 -0
  168. data/spec/support/test_query.rb +5 -0
  169. data/spec/support/test_query_filter.rb +4 -0
  170. data/spec/support/test_service.rb +7 -0
  171. data/spec/support/validate_booleaness_of_matcher.rb +17 -0
  172. data/spec/support/validate_timeliness_of_matcher.rb +17 -0
  173. data/spec/support/validator_detector.rb +48 -0
  174. 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