riveter 0.0.6 → 0.0.7

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9bfaea301720bade36e0378d57aadda384fa61a1
4
- data.tar.gz: 19f28abaef62981f1a906b384b824a1adabd5848
3
+ metadata.gz: 9ebc95b71c75212a93969b10807e5ed3e1d1748c
4
+ data.tar.gz: fa04871bf94a6a4a3fabd6144f161da7662ac802
5
5
  SHA512:
6
- metadata.gz: e1c175fbea035404cb7196f5f5a7688ab6bc542d351e8218ac78fe35dd240258f351c0d84055d8e1b8bd7bb28c830b67ecccefe962b54de123d102beee6a5dc5
7
- data.tar.gz: 9fe76bfa914ad8d3c86ab89beb1220b3935d1a932a14c463ec3d28f1da081d74612b987bb4e646f24b05f2a4a56e614790eab35743f8983cd5345c790537a514
6
+ metadata.gz: 654cb072df071d5b1ce69f10b85047750d514ee2d104245aae1ce0fc151ba7b2ef77110b226d9c811acd8f1f7073c2c149d3c1af430e30759a722562baa30183
7
+ data.tar.gz: 94a50c282014f605438bed1304124041ae735db5bd494e5d586380588673a6b2291a0b70d9df98e65affb173e11c7a6b3d23915604d43b25f664227c1b70f874
data/Gemfile.lock CHANGED
@@ -1,13 +1,13 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- riveter (0.0.6)
5
- actionpack
6
- activemodel
7
- activerecord
8
- activesupport
4
+ riveter (0.0.7)
5
+ actionpack (~> 4.0.0)
6
+ activemodel (~> 4.0.0)
7
+ activerecord (~> 4.0.0)
8
+ activesupport (~> 4.0.0)
9
9
  railties (~> 4.0.0)
10
- validates_timeliness
10
+ validates_timeliness (~> 3.0.0)
11
11
 
12
12
  GEM
13
13
  remote: https://rubygems.org/
@@ -55,7 +55,7 @@ GEM
55
55
  thor
56
56
  debugger-linecache (1.2.0)
57
57
  diff-lcs (1.2.5)
58
- docile (1.1.4)
58
+ docile (1.1.5)
59
59
  erubis (2.7.0)
60
60
  haml (4.0.5)
61
61
  tilt
@@ -72,8 +72,8 @@ GEM
72
72
  method_source (0.8.2)
73
73
  mime-types (1.25.1)
74
74
  minitest (4.7.5)
75
- multi_json (1.10.0)
76
- polyglot (0.3.4)
75
+ multi_json (1.10.1)
76
+ polyglot (0.3.5)
77
77
  pry (0.9.12.6)
78
78
  coderay (~> 1.0)
79
79
  method_source (~> 0.8)
@@ -97,7 +97,7 @@ GEM
97
97
  activesupport (= 4.0.5)
98
98
  rake (>= 0.8.7)
99
99
  thor (>= 0.18.1, < 2.0)
100
- rake (10.3.1)
100
+ rake (10.3.2)
101
101
  rest-client (1.6.7)
102
102
  mime-types (>= 1.16)
103
103
  rspec-core (2.14.8)
@@ -120,7 +120,7 @@ GEM
120
120
  simplecov-html (~> 0.8.0)
121
121
  simplecov-html (0.8.0)
122
122
  slop (3.5.0)
123
- sprockets (2.11.0)
123
+ sprockets (2.12.1)
124
124
  hike (~> 1.2)
125
125
  multi_json (~> 1.0)
126
126
  rack (~> 1.0)
@@ -132,7 +132,7 @@ GEM
132
132
  term-ansicolor (1.3.0)
133
133
  tins (~> 1.0)
134
134
  thor (0.19.1)
135
- thread_safe (0.3.3)
135
+ thread_safe (0.3.4)
136
136
  tilt (1.4.1)
137
137
  timeliness (0.3.7)
138
138
  tins (1.3.0)
@@ -147,14 +147,14 @@ PLATFORMS
147
147
  ruby
148
148
 
149
149
  DEPENDENCIES
150
- ammeter
151
- bundler (~> 1.5)
152
- coveralls
153
- haml-rails
154
- pry
155
- pry-byebug
156
- rails (~> 4.0.5)
157
- rake
150
+ ammeter (~> 1.0.0)
151
+ bundler (~> 1.6.0)
152
+ coveralls (~> 0.7.0)
153
+ haml-rails (~> 0.5.0)
154
+ pry (~> 0.9.0)
155
+ pry-byebug (~> 1.3.0)
156
+ rails (~> 4.0.0)
157
+ rake (~> 10.3.0)
158
158
  riveter!
159
- rspec-rails
159
+ rspec-rails (~> 2.14.0)
160
160
  shoulda-matchers (~> 2.6.1)
data/README.md CHANGED
@@ -6,19 +6,238 @@
6
6
  [![Coverage Status](https://coveralls.io/repos/virtualstaticvoid/riveter/badge.png)](https://coveralls.io/r/virtualstaticvoid/riveter)
7
7
  [![Dependency Status](https://gemnasium.com/virtualstaticvoid/riveter.svg)](https://gemnasium.com/virtualstaticvoid/riveter)
8
8
 
9
- Provides several useful patterns, packaged in a gem, for use in Rails.
10
- Includes generators to help you improve consistency in your applications.
11
-
12
- * Enumerated
13
- * Query
14
- * QueryFilter
15
- * Enquiry
16
- * EnquiryController
17
- * Command
18
- * CommandController
19
- * Service
20
- * Presenter
21
- * Worker
9
+ Provides several useful patterns, packaged in a gem, for use in Rails and other web based applications, including generators to help improve
10
+ consistency in your applications.
11
+
12
+ ## Motivation
13
+ In an effort to refactor large Rails applications, a number of patterns emerged, which this gem seeks to formalize. Encapsulating
14
+ each pattern helps provide consistency and promotes standardization of implementation in a team of developers. Also, having generators
15
+ to create all the necessary classes and specs helps developers follow the standards more easily and prevents unnecessary coding and
16
+ ensures the focus remains on the business problem being solved instead.
17
+
18
+ Some of these patterns have been discussed at length within the Ruby and Rails communities, with many people making cases for
19
+ and against some of these patterns. The important thing to bear in mind is that there is no "one" solution for everything, nor
20
+ "one" way to solve a problem, so these patterns will _not_ always be applicable. Please use them where it makes sense to do so, and
21
+ the solution to the problem you are trying to solve becomes clearer by doing so.
22
+
23
+ ## Patterns Included
24
+ The following patterns are included, and an explaination of each follows:
25
+
26
+ ### Enumerated
27
+ Rails 4+ now has support for enumerated attributes on models, however the `Riverter::Enumerated` module is a slightly different take on
28
+ the idea but can still be used on conjunction with the default functionality.
29
+
30
+ You define an enumerated type by creating a module, adding the desired members as constants and then include the `Riveter::Enumerated` module.
31
+
32
+ E.g. Module containing enumeration members:
33
+ ```ruby
34
+ module FooStatusEnum
35
+ Bar = 1
36
+ Baz = 2
37
+
38
+ include Riveter::Enumerated
39
+ end
40
+ ```
41
+
42
+ The `FooStatusEnum` module will now have the following methods exposed:
43
+
44
+ * `::All` - a constant which behaves like an `Array` for enumerating the members. E.g. `FooBarStatusEnum::All #=> [FooBarStatusEnum::Member1, FooBarStatusEnum::Member2]`
45
+ * `human` - if a locale file is provided, gives the human name for the enumeration. E.g. `FooStatusEnum.human #=> "Foo Status"`
46
+ * `names` - lists all the member names. E.g. `FooStatusEnum.names #=> [Bar, Baz]`
47
+ * `values` - lists all the member values. E.g. `FooStatusEnum.values #=> [1, 2]`
48
+ * `collection` - provides a collection of the members for use in form inputs.
49
+
50
+ And when enumerating over the members using `FooStatusEnum::All`, each member will have the following methods:
51
+
52
+ * `name` - the member name
53
+ * `human` - if a locale file is provided, gives the human name for the member
54
+ * `value` - the member value
55
+
56
+ ### QueryFilter
57
+ A common requirement in a Rails application is to collect criteria from the user and then prepairing a query using those criteria
58
+ within the `where` clauses. The query filter pattern encapsulates the criteria attributes so that they can be converted from params
59
+ and validated prior to building up the query.
60
+
61
+ Create a class which inherits from `Riveter::QueryFilter::Base` and then define the attributes, their default values and validations as needed.
62
+
63
+ E.g. Query filter example class
64
+ ```ruby
65
+ class FooQueryFilter < Riveter::QueryFilter::Base
66
+ attr_string :bar_like, :required => true
67
+ attr_boolean :baz, :default => true
68
+ attr_date :qux, :default => { Time.now }
69
+ end
70
+ ```
71
+
72
+ There are a number of `attr_*` methods as follows:
73
+
74
+ * `attr_string`
75
+ * `attr_integer`
76
+ * `attr_decimal`
77
+ * `attr_date`
78
+ * `attr_date_range`
79
+ * `attr_time`
80
+ * `attr_boolean`
81
+ * `attr_enum`
82
+ * `attr_array`
83
+ * `attr_hash`
84
+ * `attr_model`
85
+ * `attr_object`
86
+
87
+ In your controller, create an instance of the query filter as if it were a model. It can be used within your views easily as there is
88
+ a view helper method, `query_filter_form_for`, which makes it easy to build HTML forms for the specified attributes.
89
+ If you have `simple_form` installed, it will behave like a `simple_form_for`, otherwise the standard Rails `form_for` is used.
90
+
91
+ E.g. Controller example
92
+ ```ruby
93
+ class FooController < ApplicationController
94
+
95
+ def new_search
96
+ @query_filter = FooQueryFilter.new()
97
+ end
98
+
99
+ def search
100
+ @query_filter = FooQueryFilter.new(foo_query_filter_params)
101
+ respond_to do |format|
102
+ if @query_filter.valid?
103
+ # E.g. your query logic here
104
+ @list = BarModel.where(:bar => @query_filter.bar_like)
105
+ .where(:baz => @query_filter.baz)
106
+ .where(:qux => @query_filter.qux)
107
+ format.html
108
+ else
109
+ format.html {render :action => :new_search}
110
+ end
111
+ end
112
+ end
113
+
114
+ private
115
+ def foo_query_filter_params
116
+ params.require(:foo_query_filter).permit(:bar_like, :baz, :qux)
117
+ end
118
+ end
119
+ ```
120
+
121
+ ### Query
122
+ Given that the query filter is encapsulated, it follows that the query, which is built using the query filter, should be encapsulated too.
123
+ Also, considering the controller example code above, it would be better to encapsulate the building of the query into it's own class, instead
124
+ of coding it in the controller, especially if the criteria is applied conditionally to the query.
125
+
126
+ By abstracting the query filter and query as separate classes, it is easier to test each component individually, and there are greater
127
+ possibility for reuse of either class in other scenarios.
128
+
129
+ Create a class which inherits from `Riveter::Query::Base` class and implement the `build_relation` method to define the query.
130
+
131
+ E.g. The query class
132
+ ```ruby
133
+ class FooQuery < Riveter::Query::Base
134
+ def build_relation(filter)
135
+ query = FooModel.all
136
+
137
+ # apply criteria to the query conditionally...
138
+ if filter.bar_like.present?
139
+ query = query.where(:bar => filter.bar_like)
140
+ end
141
+
142
+ ...
143
+
144
+ query
145
+ end
146
+ end
147
+ ```
148
+
149
+ The `FooQuery` with now have the following methods, which help in rendering the results in your views:
150
+
151
+ * `has_data?` - given the relation provided, yields true to indicate whether there is result data
152
+ * `relation` - the built relation
153
+ * `find_each` - this method is used to enumerate over the result data in the most efficient way
154
+
155
+ ### Enquiry
156
+ Since the query filter and query encapsulate filtering and querying, and as they are defined individually, it follows that
157
+ there should be a way to bring them together, and thus make them easier to work with in controllers and views.
158
+
159
+ An enquiry is defined by specifying the query filter and query to use. Provide a class which inherits from `Riveter::Enquiry::Base` and
160
+ specify which query filter and query to use with the `filter_with` and `query_with` methods respectively.
161
+
162
+ E.g. A simple enquiry class
163
+ ```ruby
164
+ class FooEnquiry < Riveter::Enquiry::Base
165
+ filter_with FooQueryFilter
166
+ query_with FooQuery
167
+ end
168
+ ```
169
+
170
+ In your controller, create an instance of the enquiry as if it were a model. It can be used within your views as there is
171
+ a view helper method, `enquiry_form_for`, which makes it easy to build HTML forms for the specified attributes of the query filter.
172
+ If you have `simple_form` installed, it will behave like a `simple_form_for`, otherwise the standard Rails `form_for` is used.
173
+
174
+ Then on submission of the form, call the `submit` method passing in the form parameters, and then enumerate over the resultant data
175
+ using the `find_each` method.
176
+
177
+ E.g. An example enquiry controller
178
+ ```ruby
179
+ class FooEnquiryController < ApplicationController
180
+ def index
181
+ @enquiry = FooEnquiry.new()
182
+ respond_to do |format|
183
+ unless @enquiry.submit(enquiry_params)
184
+ flash[:notice] = 'Invalid enquiry criteria, please correct and try again.'
185
+ end
186
+ format.html
187
+ end
188
+ end
189
+
190
+ private
191
+ def enquiry_params
192
+ params
193
+ .require(:foo_enquiry)
194
+ .permit(:bar_like, :baz, :qux)
195
+ .merge(:page => params.fetch(:page, 1))
196
+ end
197
+ end
198
+ ```
199
+
200
+ And the corresponding view, in HAML using `simple_form_for` to build the criteria inputs and Kaminari for pagination:
201
+ ```haml
202
+ .criteria
203
+ = enquiry_form_for(@enquiry) do |f|
204
+ = f.input :bar_like
205
+ = f.input :baz
206
+ = f.input :qux
207
+
208
+ .results
209
+ %table
210
+ %tr
211
+ %th Foo
212
+ %th Bar
213
+ %th Baz
214
+ - unless @enquiry.has_data?
215
+ %tr
216
+ %td(colspan=3)
217
+ No data found for enquiry...
218
+ - else
219
+ - @enquiry.find_each do |result|
220
+ %tr
221
+ %td= result.bar
222
+ %td= result.bax
223
+ %td= result.qux
224
+ = paginate_enquiry(@enquiry)
225
+ ```
226
+
227
+ ### EnquiryController
228
+ _TDB_
229
+
230
+ ### Command
231
+ _TDB_
232
+
233
+ ### CommandController
234
+ _TDB_
235
+
236
+ ### Service
237
+ _TDB_
238
+
239
+ ### Presenter
240
+ _TDB_
22
241
 
23
242
  ## Installation
24
243
 
@@ -44,9 +263,7 @@ To get the list of available generators, execute:
44
263
 
45
264
  The generator names are prefixed with `riveter`.
46
265
 
47
- E.g.
48
-
49
- To generate an enquiry controller, query filter, query, views and associated specs, execute:
266
+ E.g. To generate an enquiry controller, query filter, query, views and associated specs, execute:
50
267
 
51
268
  $ rails generate riveter:enquiry SomeEnquiryName filter1:string filter2:integer:required
52
269
 
@@ -12,5 +12,9 @@ module Riveter
12
12
  simple_form_for(enquiry.query_filter, options, &block) :
13
13
  form_for(enquiry.query_filter, options, &block)
14
14
  end
15
+
16
+ def paginate_enquiry(enquiry, options={}, &block)
17
+ paginate(enquiry.query, options, &block) if respond_to?(:paginate)
18
+ end
15
19
  end
16
20
  end
@@ -24,3 +24,8 @@ en:
24
24
  messages:
25
25
  booleaness: is not valid
26
26
  email: is not valid
27
+ invalid_date: is not a valid date
28
+ invalid_time: is not a valid time
29
+ date_range: is not a valid date range
30
+ date_range_date_from: "must be before %{date_to}"
31
+ date_range_date_to: "must be after %{date_from}"
@@ -25,6 +25,7 @@ class <%= class_name %>Command < Riveter::Command::Base
25
25
  # attr_integer
26
26
  # attr_decimal
27
27
  # attr_date
28
+ # attr_date_range
28
29
  # attr_time
29
30
  # attr_boolean
30
31
  # attr_enum
@@ -25,6 +25,7 @@ class <%= class_name %>QueryFilter < Riveter::QueryFilter::Base
25
25
  # attr_integer
26
26
  # attr_decimal
27
27
  # attr_date
28
+ # attr_date_range
28
29
  # attr_time
29
30
  # attr_boolean
30
31
  # attr_enum
@@ -37,4 +38,4 @@ class <%= class_name %>QueryFilter < Riveter::QueryFilter::Base
37
38
  <% end -%>
38
39
 
39
40
  end
40
- <% end -%>
41
+ <% end -%>
data/lib/riveter.rb CHANGED
@@ -12,8 +12,11 @@ module Riveter
12
12
  autoload :AttributeDefaultValues, 'riveter/attribute_default_values'
13
13
  autoload :AssociatedTypeRegistry, 'riveter/associated_type_registry'
14
14
  autoload :Attributes, 'riveter/attributes'
15
+ autoload :BooleanessValidator, 'riveter/booleaness_validator'
15
16
  autoload :Command, 'riveter/command'
16
17
  autoload :CommandController, 'riveter/command_controller'
18
+ autoload :DateRangeValidator, 'riveter/date_range_validator'
19
+ autoload :EmailValidator, 'riveter/email_validator'
17
20
  autoload :Enquiry, 'riveter/enquiry'
18
21
  autoload :EnquiryController, 'riveter/enquiry_controller'
19
22
  autoload :Enumerated, 'riveter/enumerated'
@@ -28,6 +31,7 @@ end
28
31
  module ActiveModel
29
32
  module Validations
30
33
  autoload :BooleanessValidator, 'riveter/booleaness_validator'
34
+ autoload :DateRangeValidator, 'riveter/date_range_validator'
31
35
  autoload :EmailValidator, 'riveter/email_validator'
32
36
  end
33
37
  end
@@ -40,6 +44,4 @@ require 'riveter/rails/railtie' if defined?(::Rails::Railtie)
40
44
  require 'riveter/rails/engine' if defined?(::Rails)
41
45
 
42
46
  # include locale in load path
43
- Dir[File.join(File.expand_path('../../config', __FILE__), 'locales', '*.yml')].each do |file|
44
- I18n.load_path << file
45
- end
47
+ I18n.load_path += Dir.glob(File.expand_path('../../config/locales/*.yml', __FILE__))
@@ -54,6 +54,62 @@ module Riveter
54
54
 
55
55
  alias :attr_datetime :attr_time
56
56
 
57
+ def attr_date_range(name, options={}, &block)
58
+ options = {
59
+ :validate => true
60
+ }.merge(options)
61
+
62
+ required = (true == options.delete(:required))
63
+
64
+ # expecting the default value to be a date range
65
+ # so extract out the first and last parts for the from and to
66
+ # otherwise, it may just be a date or nil
67
+ default = options.delete(:default)
68
+ default = default.is_a?(Range) ? default : (default..default)
69
+ defaults = {
70
+ :from => default.first,
71
+ :to => default.last
72
+ }
73
+
74
+ converter = block_given? ? block : Converters.converter_for(:date)
75
+
76
+ # return from and to as range
77
+ define_method name do
78
+ # can't have range starting or ending with nil, so return nil
79
+ send(:"#{name}_from")..send(:"#{name}_to") rescue nil
80
+ end
81
+
82
+ define_method "#{name}=" do |value|
83
+ value ||= nil..nil
84
+ range = value.is_a?(Range) ? value : value..value
85
+ send(:"#{name}_from=", range.first)
86
+ send(:"#{name}_to=", range.last)
87
+ end
88
+
89
+ validates name,
90
+ :allow_nil => !required,
91
+ :date_range => true if options[:validate]
92
+
93
+ add_attr(name, :date_range)
94
+
95
+ # break down into parts
96
+ [:from, :to].each do |part|
97
+ attr_reader_with_converter :"#{name}_#{part}", converter
98
+
99
+ define_method :"#{name}_#{part}?" do
100
+ send(:"#{name}_#{part}").present?
101
+ end
102
+
103
+ attr_writer :"#{name}_#{part}"
104
+
105
+ validates :"#{name}_#{part}",
106
+ :allow_nil => !required,
107
+ :timeliness => { :type => :date } if options[:validate]
108
+
109
+ add_attr(:"#{name}_#{part}", :date, converter, options.merge(:default => defaults[part]))
110
+ end
111
+ end
112
+
57
113
  def attr_boolean(name, options={}, &block)
58
114
  options = {
59
115
  :validate => true
@@ -65,11 +121,6 @@ module Riveter
65
121
  attr_reader_with_converter name, converter
66
122
  alias_method "#{name}?", name
67
123
 
68
- validates name,
69
- :allow_blank => !required,
70
- :allow_nil => !required,
71
- :booleaness => true if options[:validate]
72
-
73
124
  attr_writer name
74
125
 
75
126
  add_attr(name, :boolean, converter, options)
@@ -100,7 +151,7 @@ module Riveter
100
151
  validates name,
101
152
  :allow_blank => !required,
102
153
  :allow_nil => !required,
103
- :inclusion => { :in => enum.all } if options[:validate]
154
+ :inclusion => { :in => enum.values } if options[:validate]
104
155
 
105
156
  attr_writer name
106
157
 
@@ -244,7 +295,7 @@ module Riveter
244
295
  add_attr(name, type, converter, options)
245
296
  end
246
297
 
247
- def add_attr(name, type, converter, options={})
298
+ def add_attr(name, type, converter=nil, options={})
248
299
  self._attributes[name] = attribute_info = AttributeInfo.new(name, type, converter, options)
249
300
  validates name, :presence => true if attribute_info.required?
250
301
  attribute_info