riveter 0.0.6 → 0.0.7

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