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,11 @@
1
+ module Rspec
2
+ module Generators
3
+ class WorkerGenerator < ::Rails::Generators::NamedBase
4
+ source_root File.expand_path('../templates', __FILE__)
5
+
6
+ def create_worker_spec
7
+ template 'worker_spec.rb', File.join('spec/workers', class_path, "#{file_name}_worker_spec.rb")
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1 @@
1
+ Create generators and templates for test unit implementation
@@ -0,0 +1,63 @@
1
+ module Riveter
2
+ module AssociatedTypeRegistry
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ container = self
7
+ type_registry = TypeRegistry.new()
8
+
9
+ define_singleton_method :container do
10
+ container
11
+ end
12
+
13
+ define_singleton_method :registry do
14
+ type_registry
15
+ end
16
+ end
17
+
18
+ module ClassMethods
19
+ def key_for(type)
20
+ type.is_a?(Symbol) ? type : type.name.underscore.to_sym
21
+ end
22
+
23
+ def register_type(type, associated_type, options={})
24
+ options = {
25
+ :key => key_for(associated_type)
26
+ }.merge(options)
27
+
28
+ # prevent duplicate registrations
29
+ type_list = container.registry[options[:key]]
30
+ type_list << type unless type_list.include?(type)
31
+ end
32
+
33
+ def resolve!(type)
34
+ registered = self.resolve(type)
35
+ raise UnregisteredType.new(type) unless registered
36
+ registered
37
+ end
38
+
39
+ def resolve(type)
40
+ self.resolve_all(type).first
41
+ end
42
+
43
+ def resolve_all(type)
44
+ key = key_for(type)
45
+ container.registry.key?(key) ? container.registry[key] : []
46
+ end
47
+ end
48
+ end
49
+
50
+ class TypeRegistry < Hash
51
+ def initialize
52
+ super {|h, k| h[k] = []}
53
+ end
54
+ end
55
+
56
+ class UnregisteredType < Exception
57
+ attr_reader :type
58
+
59
+ def initialize(type)
60
+ @type = type
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,67 @@
1
+ module Riveter
2
+ module AttributeDefaultValues
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ #
7
+ # NB:
8
+ # defines the container within the scope of this class (or module)
9
+ # so that the list of attributes with defaults aren't shared across models
10
+ #
11
+ class << self
12
+ def attribute_defaults
13
+ @attribute_defaults ||= {}
14
+ end
15
+ end
16
+
17
+ # setup after_initialize callback to set the defaults for listed attributes
18
+ after_initialize :set_defaults
19
+ end
20
+
21
+ module ClassMethods
22
+ #
23
+ # defines a default value for the given attribute
24
+ #
25
+ # E.g. default_value_for :active, true
26
+ #
27
+ # default_value_for :active do
28
+ # # ... some logic ...
29
+ # end
30
+ #
31
+ def default_value_for(attribute, value=nil, &block)
32
+ self.attribute_defaults[attribute] = (block_given? ? block : value)
33
+ end
34
+
35
+ #
36
+ # defines default values for one or more attributes
37
+ # specify defaults using a hash, where the key is the attribute name
38
+ # and the value is the value or a proc
39
+ #
40
+ # E.g. default_values :confirmed => true,
41
+ # :processed => false,
42
+ # :some_other => lambda { ... some logic ... }
43
+ #
44
+ def default_values(*args)
45
+ defaults = args.first.is_a?(Hash) ?
46
+ args.first :
47
+ { args.first => args.last }
48
+
49
+ defaults.each do |attribute, value_or_proc|
50
+ value_or_proc.respond_to?(:call) ?
51
+ default_value_for(attribute, &value_or_proc) :
52
+ default_value_for(attribute, value_or_proc)
53
+ end
54
+ end
55
+ end
56
+
57
+ def set_defaults
58
+ return if self.respond_to?(:persisted?) && self.persisted?
59
+ self.class.attribute_defaults.each do |attribute, value_or_proc|
60
+ value = value_or_proc.respond_to?(:call) ?
61
+ (value_or_proc.call(self) rescue value_or_proc.call) :
62
+ value_or_proc
63
+ send("#{attribute}=", value) if send(attribute).blank?
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,443 @@
1
+ module Riveter
2
+ module Attributes
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+
7
+ include ActiveModel::Conversion
8
+ include ActiveModel::Validations
9
+ include ActiveModel::Validations::Callbacks
10
+ include ActiveModel::ForbiddenAttributesProtection
11
+
12
+ # define attributes within the class scope
13
+ # so that they aren't shared between including classes
14
+
15
+ mattr_accessor :_attributes_container, :instance_writer => false
16
+ self._attributes_container = Hash.new {|hash, key| hash[key] = {}.with_indifferent_access }
17
+
18
+ def self._attributes
19
+ self._attributes_container[self]
20
+ end
21
+
22
+ def self.attributes
23
+ self._attributes.keys
24
+ end
25
+ end
26
+
27
+ module ClassMethods
28
+ def attr_string(name, options={})
29
+ converter = Converters.converter_for(:string)
30
+
31
+ define_method name do
32
+ converter.call instance_variable_get("@#{name}")
33
+ end
34
+
35
+ attr_writer name
36
+
37
+ add_attr(name, :string, converter, options)
38
+ end
39
+
40
+ alias_method :attr_text, :attr_string
41
+
42
+ def attr_integer(name, options={})
43
+ options = {
44
+ :validate => true
45
+ }.merge(options)
46
+
47
+ required = options[:required] == true
48
+ converter = Converters.converter_for(:integer)
49
+
50
+ define_method name do
51
+ converter.call instance_variable_get("@#{name}")
52
+ end
53
+
54
+ validates name,
55
+ :allow_blank => !required,
56
+ :allow_nil => !required,
57
+ :numericality => { :only => :integer } if options[:validate]
58
+
59
+ attr_writer name
60
+
61
+ add_attr(name, :integer, converter, options)
62
+ end
63
+
64
+ def attr_decimal(name, options={})
65
+ options = {
66
+ :validate => true
67
+ }.merge(options)
68
+
69
+ required = options[:required] == true
70
+ converter = Converters.converter_for(:decimal)
71
+
72
+ define_method name do
73
+ converter.call instance_variable_get("@#{name}")
74
+ end
75
+
76
+ validates name,
77
+ :allow_blank => !required,
78
+ :allow_nil => !required,
79
+ :numericality => true if options[:validate]
80
+
81
+ attr_writer name
82
+
83
+ add_attr(name, :decimal, converter, options)
84
+ end
85
+
86
+ def attr_date(name, options={})
87
+ options = {
88
+ :validate => true
89
+ }.merge(options)
90
+
91
+ required = options[:required] == true
92
+ converter = Converters.converter_for(:date)
93
+
94
+ define_method name do
95
+ converter.call instance_variable_get("@#{name}")
96
+ end
97
+
98
+ validates name,
99
+ :allow_blank => !required,
100
+ :allow_nil => !required,
101
+ :timeliness => { :type => :date } if options[:validate]
102
+
103
+ attr_writer name
104
+
105
+ add_attr(name, :date, converter, options)
106
+ end
107
+
108
+ def attr_time(name, options={})
109
+ options = {
110
+ :validate => true
111
+ }.merge(options)
112
+
113
+ required = options[:required] == true
114
+ converter = Converters.converter_for(:time)
115
+
116
+ define_method name do
117
+ converter.call instance_variable_get("@#{name}")
118
+ end
119
+
120
+ validates name,
121
+ :allow_blank => !required,
122
+ :allow_nil => !required,
123
+ :timeliness => { :type => :time } if options[:validate]
124
+
125
+ attr_writer name
126
+
127
+ add_attr(name, :time, converter, options)
128
+ end
129
+
130
+ alias :attr_datetime :attr_time
131
+
132
+ def attr_boolean(name, options={})
133
+ options = {
134
+ :validate => true
135
+ }.merge(options)
136
+
137
+ required = options[:required] == true
138
+ converter = Converters.converter_for(:boolean)
139
+
140
+ define_method name do
141
+ converter.call instance_variable_get("@#{name}")
142
+ end
143
+
144
+ alias_method "#{name}?", name
145
+
146
+ validates name,
147
+ :allow_blank => !required,
148
+ :allow_nil => !required,
149
+ :booleaness => true if options[:validate]
150
+
151
+ attr_writer name
152
+
153
+ add_attr(name, :boolean, converter, options)
154
+ end
155
+
156
+ def attr_enum(name, enum, options={})
157
+ options = {
158
+ :enum => enum,
159
+ :validate => true
160
+ }.merge(options)
161
+
162
+ required = options[:required] == true
163
+ converter = Converters.converter_for(:enum, options)
164
+
165
+ define_method name do
166
+ converter.call instance_variable_get("@#{name}")
167
+ end
168
+
169
+ # helpers
170
+ # TODO: would be nicer to emulate an association
171
+
172
+ define_singleton_method "#{name}_enum" do
173
+ enum
174
+ end
175
+
176
+ define_singleton_method name.to_s.pluralize do
177
+ enum.collection
178
+ end
179
+
180
+ validates name,
181
+ :allow_blank => !required,
182
+ :allow_nil => !required,
183
+ :inclusion => { :in => enum.all } if options[:validate]
184
+
185
+ attr_writer name
186
+
187
+ add_attr(name, :enum, converter, options)
188
+ end
189
+
190
+ def attr_array(name, options={}, &block)
191
+ options = {
192
+ :data_type => :integer,
193
+ :validate => true
194
+ }.merge(options)
195
+ data_type = options.delete(:data_type)
196
+
197
+ converter = block_given? ? block : Converters.converter_for(data_type)
198
+
199
+ define_method name do
200
+ (instance_variable_get("@#{name}") || []).map {|item| converter.call(item) }
201
+ end
202
+
203
+ attr_writer name
204
+
205
+ add_attr(name, :array, converter, options)
206
+ end
207
+
208
+ def attr_hash(name, options={})
209
+ converter = Converters.converter_for(:hash, options)
210
+
211
+ # FIXME:
212
+ # * need converter for values?
213
+ # * assumes the hash values are contiguous?
214
+ #
215
+
216
+ attr_accessor name
217
+
218
+ add_attr(name, :hash, converter, options)
219
+ end
220
+
221
+ def attr_model(name, model, options={})
222
+ options = {
223
+ :model => model
224
+ }.merge(options)
225
+ converter = Converters.converter_for(:model, options)
226
+
227
+ # FIXME:
228
+ # * need converter for values?
229
+ # * define methods to get the actual model instance?
230
+ # * assign model instance directly?
231
+ #
232
+
233
+ # helpers
234
+ define_singleton_method "#{name}_model" do
235
+ model
236
+ end
237
+
238
+ define_method name do
239
+ converter.call instance_variable_get("@#{name}")
240
+ end
241
+
242
+ attr_writer name
243
+
244
+ add_attr(name, :model, converter, options)
245
+ end
246
+
247
+ def attr_object(name, options={})
248
+ converter = Converters.converter_for(:object, options)
249
+
250
+ define_method name do
251
+ converter.call instance_variable_get("@#{name}")
252
+ end
253
+
254
+ attr_writer name
255
+
256
+ add_attr(name, :object, converter, options)
257
+ end
258
+
259
+ private
260
+
261
+ def add_attr(name, type, converter, options={})
262
+ self._attributes[name] = attribute_info = AttributeInfo.new(name, type, converter, options)
263
+ validates name, :presence => true if attribute_info.required?
264
+ attribute_info
265
+ end
266
+ end
267
+
268
+ class AttributeInfo < Struct.new(:name, :type, :converter, :options)
269
+
270
+ def required?
271
+ @required ||= (options[:required] == true)
272
+ end
273
+
274
+ def default
275
+ @default ||= options[:default]
276
+ end
277
+
278
+ def default?
279
+ !self.default.nil?
280
+ end
281
+
282
+ end
283
+
284
+ attr_reader :options
285
+
286
+ def initialize(params=nil, options={})
287
+ # assign default values
288
+ self.class._attributes.each do |name, attribute_info|
289
+ next unless attribute_info.default?
290
+ value = attribute_info.default
291
+ send("#{name}=", value.respond_to?(:call) ? value.call : value)
292
+ end
293
+
294
+ @options = options.freeze
295
+
296
+ # filter and clean params before applying
297
+ apply_params(
298
+ clean_params(
299
+ filter_params(
300
+ sanitize_for_mass_assignment(params)
301
+ )
302
+ )
303
+ ) unless params.nil?
304
+ end
305
+
306
+ def attributes(options={})
307
+ self.class._attributes.inject({}) do |list, (key, attribute_info)|
308
+ list[key] = self.send(attribute_info.name)
309
+ list
310
+ end
311
+ end
312
+
313
+ alias_method :to_params, :attributes
314
+
315
+ # forms use this to determine the HTTP verb
316
+ def persisted?
317
+ false
318
+ end
319
+
320
+ # forms use this for getting column meta data
321
+ def column_for_attribute(column)
322
+ attribute_info = self.class._attributes[column]
323
+ OpenStruct.new(
324
+ :name => attribute_info.name,
325
+ :type => attribute_info.type,
326
+ :null => !attribute_info.required?,
327
+ :default => attribute_info.default
328
+ )
329
+ end
330
+
331
+ protected
332
+
333
+ # only include defined attributes
334
+ def filter_params(params)
335
+ attributes = self.class._attributes
336
+ params.keep_if {|name, value|
337
+ attributes.key?(name)
338
+ }
339
+ end
340
+
341
+ # remove blank/nil attributes
342
+ def clean_params(params)
343
+ params.reject { |key, value|
344
+ reject_value?(value)
345
+ }
346
+ end
347
+
348
+ # given sanitized params, assign values to instance
349
+ def apply_params(params)
350
+ params.each do |attribute, value|
351
+ assign_attribute(attribute, value)
352
+ end
353
+ end
354
+
355
+ private
356
+
357
+ def reject_value?(value)
358
+ case value
359
+ when Array
360
+ value.reject! {|v| reject_value?(v) }
361
+ false
362
+ when Hash
363
+ value.reject! {|k, v| reject_value?(v) }
364
+ false
365
+ else
366
+ value.blank? unless value.is_a?(FalseClass)
367
+ end
368
+ end
369
+
370
+ def assign_attribute(attribute, value)
371
+ public_send("#{attribute}=", value)
372
+
373
+ rescue
374
+ if respond_to?("#{attribute}=")
375
+ raise
376
+ else
377
+ raise UnknownAttributeError.new(self, attribute)
378
+ end
379
+ end
380
+
381
+ module Converters
382
+
383
+ # supply a built-in converter for the given type
384
+ def self.converter_for(data_type, options={})
385
+ case data_type
386
+ when :string
387
+ lambda {|v| v.to_s }
388
+
389
+ when :boolean
390
+ lambda {|v| v.to_b }
391
+
392
+ when :integer
393
+ lambda {|v| Integer(v) rescue v }
394
+
395
+ when :decimal
396
+ lambda {|v| Float(v) rescue v }
397
+
398
+ when :hash
399
+ lambda {|v| v.to_hash rescue v }
400
+
401
+ when :date
402
+ lambda {|v| Date.parse(v) rescue v }
403
+
404
+ when :time
405
+ lambda {|v| Time.parse(v) rescue v }
406
+
407
+ when :enum
408
+ lambda {|enum, v|
409
+ if v.blank? || v.nil?
410
+ nil
411
+ else
412
+ # FIXME: assuming enum values are integers!
413
+ v = Integer(v) rescue v
414
+ (v.is_a?(String) || v.is_a?(Symbol)) ? enum.value_for(v) : v.to_i
415
+ end
416
+ }.curry[options[:enum]]
417
+
418
+ # FIXME:
419
+ # when :hash
420
+ # lambda {|v| v }
421
+
422
+ when :model
423
+ # FIXME:
424
+ lambda {|model, v|
425
+ # assuming v is an ID
426
+ # model.find(v)
427
+ # FIXME
428
+ v
429
+ }.curry[options[:model]]
430
+
431
+ else
432
+ lambda {|v| v }
433
+ end
434
+ end
435
+
436
+ end
437
+
438
+ # helper class
439
+ class Base
440
+ include Attributes
441
+ end
442
+ end
443
+ end
@@ -0,0 +1,20 @@
1
+ module Riveter
2
+ class BooleanessValidator < 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 [true, false].include?(value)
12
+ record.errors.add(attribute, :booleaness, :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::BooleanessValidator = Riveter::BooleanessValidator
@@ -0,0 +1,59 @@
1
+ module Riveter
2
+ module Command
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ include Riveter::Attributes
7
+
8
+ class << self
9
+ alias_method :command_name, :model_name
10
+
11
+ def i18n_scope
12
+ :commands
13
+ end
14
+
15
+ def success_notice
16
+ I18n.translate(
17
+ :success,
18
+ :scope => [i18n_scope, :notices, command_name.i18n_key],
19
+ :default => "Successfully executed #{command_name.human}."
20
+ )
21
+ end
22
+ end
23
+
24
+ alias_method :can_perform?, :valid?
25
+ end
26
+
27
+ def submit(*args)
28
+ params = args.extract_options!
29
+
30
+ # filter and clean params before applying
31
+ apply_params(
32
+ clean_params(
33
+ filter_params(params)
34
+ )
35
+ )
36
+
37
+ # perform validations, and proceed if valid
38
+ return false unless self.can_perform?
39
+
40
+ # all good, perform the action
41
+ self.perform *args
42
+ end
43
+
44
+ protected
45
+
46
+ def perform(*args)
47
+ # resolve for the registered service for this command
48
+ service_class = Service::Base.resolve!(self.class)
49
+ # create an instance and invoke perform
50
+ service = service_class.new()
51
+ service.perform(self, *args)
52
+ end
53
+
54
+ # helper class
55
+ class Base
56
+ include Command
57
+ end
58
+ end
59
+ end