rectify 0.0.2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE.txt +1 -1
- data/lib/rectify.rb +2 -0
- data/lib/rectify/command.rb +0 -6
- data/lib/rectify/controller.rb +34 -0
- data/lib/rectify/form.rb +1 -1
- data/lib/rectify/presenter.rb +29 -0
- data/lib/rectify/version.rb +1 -1
- data/readme.md +448 -17
- metadata +25 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4347cd2896dcb19ca5f47ac3934c433d81668ac3
|
4
|
+
data.tar.gz: 377299eed0a96ce220b70e0967ba77fbcbe4512b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0f7a0a7f6d15d47b15283bd123118af5c3d1a4ea6a996eb1e7bd206bbb10bf761b7a3cdfb5fa91df5bc50cab9593b57fa9ea4e7acdfc819a142f0ddff6e9bd54
|
7
|
+
data.tar.gz: 25af13f81a98dea2be0918b1a1710e5ef8e3df87650ce5cf3811275fe1e75e91e6af07fe5db2da293b21f5ef41a198c3fc52c78703499f82f082d00250afb7ec
|
data/LICENSE.txt
CHANGED
data/lib/rectify.rb
CHANGED
data/lib/rectify/command.rb
CHANGED
@@ -13,12 +13,6 @@ module Rectify
|
|
13
13
|
instance_eval(&block)
|
14
14
|
end
|
15
15
|
|
16
|
-
def expose(instance_variables)
|
17
|
-
instance_variables.each do |name, value|
|
18
|
-
@caller.instance_variable_set("@#{name}", value)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
16
|
def transaction(&block)
|
23
17
|
ActiveRecord::Base.transaction(&block) if block_given?
|
24
18
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Rectify
|
2
|
+
module Controller
|
3
|
+
def self.included(base_class)
|
4
|
+
base_class.helper_method(:presenter)
|
5
|
+
end
|
6
|
+
|
7
|
+
def present(presenter, options = {})
|
8
|
+
presenter_type = options.fetch(:for) { :template }
|
9
|
+
|
10
|
+
presenter.for_controller(self)
|
11
|
+
rectify_presenters[presenter_type] = presenter
|
12
|
+
end
|
13
|
+
|
14
|
+
def presenter(presenter_type = :template)
|
15
|
+
rectify_presenters[presenter_type]
|
16
|
+
end
|
17
|
+
|
18
|
+
def expose(presentation_data)
|
19
|
+
presentation_data.each do |attribute, value|
|
20
|
+
if presenter.respond_to?("#{attribute}=")
|
21
|
+
presenter.public_send("#{attribute}=", value)
|
22
|
+
else
|
23
|
+
instance_variable_set("@#{attribute}", value)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def rectify_presenters
|
31
|
+
@rectify_presenters ||= {}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/rectify/form.rb
CHANGED
@@ -0,0 +1,29 @@
|
|
1
|
+
module Rectify
|
2
|
+
class Presenter
|
3
|
+
include Virtus.model
|
4
|
+
|
5
|
+
def for_controller(controller)
|
6
|
+
@controller = controller
|
7
|
+
end
|
8
|
+
|
9
|
+
def method_missing(method_name, *args, &block)
|
10
|
+
if view_context.respond_to?(method_name)
|
11
|
+
view_context.public_send(method_name, *args, &block)
|
12
|
+
else
|
13
|
+
super
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def respond_to_missing?(method_name, include_private = false)
|
18
|
+
view_context.respond_to?(method_name, include_private)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
attr_reader :controller
|
24
|
+
|
25
|
+
def view_context
|
26
|
+
controller && controller.view_context
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/rectify/version.rb
CHANGED
data/readme.md
CHANGED
@@ -6,8 +6,8 @@ Rectify is a gem that provides some lightweight classes that will make it easier
|
|
6
6
|
to build Rails applications in a more maintainable way. It's built on top of
|
7
7
|
several other gems and adds improved APIs to make things easier.
|
8
8
|
|
9
|
-
Rectify is an extraction from a number of projects that
|
10
|
-
|
9
|
+
Rectify is an extraction from a number of projects that use these techniques and
|
10
|
+
proved to be successful.
|
11
11
|
|
12
12
|
To install, add it to your `Gemfile`:
|
13
13
|
|
@@ -23,14 +23,17 @@ bundle install
|
|
23
23
|
|
24
24
|
## Overview
|
25
25
|
|
26
|
-
Currently, Rectify consists of
|
27
|
-
|
26
|
+
Currently, Rectify consists of the following concepts:
|
27
|
+
|
28
|
+
* Form Objects
|
29
|
+
* Commands
|
30
|
+
* Presenters
|
31
|
+
|
32
|
+
You can use these separately or together to improve the structure of your Rails
|
28
33
|
applications.
|
29
34
|
|
30
35
|
The main problem that Rectify tries to solve is where your logic should go. Commonly,
|
31
|
-
business logic is either placed in the controller or the model. The opinion of Rectify
|
32
|
-
is that both of these places are incorrect and that your models in particular are
|
33
|
-
doing too much.
|
36
|
+
business logic is either placed in the controller or the model and the views are filled with too much logic. The opinion of Rectify is that these places are incorrect and that your models in particular are doing too much.
|
34
37
|
|
35
38
|
Rectify's opinion is that controllers should just be concerned with HTTP related
|
36
39
|
things and models should just be concerned with data access. The problem then
|
@@ -38,13 +41,20 @@ becomes, how and where do you place validations and other business logic.
|
|
38
41
|
|
39
42
|
Using Rectify, the Form Objects contain validations and represent the data input
|
40
43
|
of your system. Commands then take a Form Object (as well as other data) and
|
41
|
-
perform a single action which is invoked by a controller.
|
44
|
+
perform a single action which is invoked by a controller. Presenters contain the
|
45
|
+
presentation logic in a way that is easily testable and keeps your views as clean
|
46
|
+
as possible.
|
42
47
|
|
43
|
-
Here's an example
|
44
|
-
some emails, does some special auditing and integrates with a third party system:
|
48
|
+
Here's an example controller that shows details about a user and also allows a user to register an account. This creates a user, sends some emails, does some special auditing and integrates with a third party system:
|
45
49
|
|
46
50
|
```ruby
|
47
51
|
class UserController < ApplicationController
|
52
|
+
include Rectify::Controller
|
53
|
+
|
54
|
+
def show
|
55
|
+
present UserDetailsPresenter.new(:user => current_user)
|
56
|
+
end
|
57
|
+
|
48
58
|
def new
|
49
59
|
@form = RegistrationForm.new
|
50
60
|
end
|
@@ -70,9 +80,11 @@ HTTP => Controller (redirecting, rendering, etc)
|
|
70
80
|
Data Input => Form Object (validation, acceptable input)
|
71
81
|
Business Logic => Command (logic for a specific use case)
|
72
82
|
Data Access => Model (relationships, queries)
|
83
|
+
View Logic => Presenter (formatting data)
|
73
84
|
```
|
74
85
|
|
75
|
-
The next sections will give further details about using Form Objects
|
86
|
+
The next sections will give further details about using Form Objects, Commands
|
87
|
+
and Presenters.
|
76
88
|
|
77
89
|
## Form Objects
|
78
90
|
|
@@ -84,8 +96,8 @@ Here is how you define a form object:
|
|
84
96
|
|
85
97
|
```ruby
|
86
98
|
class UserForm < Rectify::Form
|
87
|
-
attribute :first_name,
|
88
|
-
attribute :last_name,
|
99
|
+
attribute :first_name, String
|
100
|
+
attribute :last_name, String
|
89
101
|
|
90
102
|
validates :first_name, :last_name, :presence => true
|
91
103
|
end
|
@@ -195,7 +207,7 @@ You can use the params hash that a Rails controller provides that contains all
|
|
195
207
|
the data in the request:
|
196
208
|
|
197
209
|
```ruby
|
198
|
-
form =
|
210
|
+
form = UserForm.from_params(params)
|
199
211
|
```
|
200
212
|
|
201
213
|
When populating from params we will populate the built in `id` attribute from the
|
@@ -211,7 +223,7 @@ params = {
|
|
211
223
|
}
|
212
224
|
}
|
213
225
|
|
214
|
-
form =
|
226
|
+
form = UserForm.from_params(params)
|
215
227
|
|
216
228
|
form.id # => 1
|
217
229
|
form.first_name # => "Andy"
|
@@ -222,6 +234,20 @@ The other thing to notice is that (thanks to Virtus), attribute values are cast
|
|
222
234
|
to the correct type. The params hash is actually all string based but when you
|
223
235
|
get values from the form, they are returned as the correct type (see `id` above).
|
224
236
|
|
237
|
+
In addition to the params hash, you may want to add additional contextual data.
|
238
|
+
This can be done by supplying a second hash to the `.from_params` method.
|
239
|
+
Elements from this hash will be available to populate form attributes as if they
|
240
|
+
were under the params key:
|
241
|
+
|
242
|
+
```ruby
|
243
|
+
form = UserForm.from_params(params, :ip_address => "1.2.3.4")
|
244
|
+
|
245
|
+
form.id # => 1
|
246
|
+
form.first_name # => "Andy"
|
247
|
+
form.last_name # => "Pike"
|
248
|
+
form.ip_address # => "1.2.3.4"
|
249
|
+
```
|
250
|
+
|
225
251
|
**Model**
|
226
252
|
|
227
253
|
The final way is to pass an ActiveModel to the form to populate it's attribute
|
@@ -251,7 +277,8 @@ Rectify includes `ActiveModel::Validations` for you so you can use all of the
|
|
251
277
|
Rails validations that you are used to within your models.
|
252
278
|
|
253
279
|
Your Form Object has a `#valid?` method that will validate the attributes of your
|
254
|
-
form as well as any nested form objects
|
280
|
+
form as well as any (deeply) nested form objects and array attributes that contain
|
281
|
+
form objects.
|
255
282
|
|
256
283
|
### Strong Parameters
|
257
284
|
|
@@ -260,6 +287,410 @@ parameters. That's because with Form Objects you do not need strong parameters.
|
|
260
287
|
You only specify attributes in your form that are allowed to be accepted. All
|
261
288
|
other data in your params hash is ignored.
|
262
289
|
|
290
|
+
Take a look at [Virtus](https://github.com/solnic/virtus) for more information
|
291
|
+
about how to build a form object.
|
292
|
+
|
263
293
|
## Commands
|
264
294
|
|
265
|
-
|
295
|
+
Commands in Rectify are based on [Wisper](https://github.com/krisleech/wisper)
|
296
|
+
which allows classes to broadcast events for publish/subscribe capabilities.
|
297
|
+
`Rectify::Command` is a lightweight class that gives an alternate API and adds some
|
298
|
+
helper methods to improve Command logic.
|
299
|
+
|
300
|
+
The reason for using the pub/sub model rather than returning a result means that
|
301
|
+
we can reduce the number of conditionals in our code as the outcome of a Command
|
302
|
+
might be more complex than just success or failure.
|
303
|
+
|
304
|
+
With regard to naming, Rectify suggests using verbs rather than nouns for Command
|
305
|
+
class names, for example `RegisterAccount`, `PlaceOrder` or `GenerateEndOfYearReport`.
|
306
|
+
Notice that we don't suffix commands with `Command` or `Service` or similar.
|
307
|
+
|
308
|
+
Here is an example Command with the structure Rectify suggests (as seen in the
|
309
|
+
overview above):
|
310
|
+
|
311
|
+
```ruby
|
312
|
+
class RegisterAccount < Rectify::Command
|
313
|
+
def initialize(form)
|
314
|
+
@form = form
|
315
|
+
end
|
316
|
+
|
317
|
+
def call
|
318
|
+
return broadcast(:invalid) if form.invalid?
|
319
|
+
|
320
|
+
transaction do
|
321
|
+
creates_user
|
322
|
+
notifiy_admins
|
323
|
+
audit_event
|
324
|
+
send_user_details_to_crm
|
325
|
+
end
|
326
|
+
|
327
|
+
broadcast(:ok)
|
328
|
+
end
|
329
|
+
|
330
|
+
private
|
331
|
+
|
332
|
+
attr_reader :form
|
333
|
+
|
334
|
+
def creates_user
|
335
|
+
# ...
|
336
|
+
end
|
337
|
+
|
338
|
+
def notifiy_admins
|
339
|
+
# ...
|
340
|
+
end
|
341
|
+
|
342
|
+
def audit_event
|
343
|
+
# ...
|
344
|
+
end
|
345
|
+
|
346
|
+
def send_user_details_to_crm
|
347
|
+
# ...
|
348
|
+
end
|
349
|
+
end
|
350
|
+
```
|
351
|
+
|
352
|
+
To invoke this Command, you would do the following:
|
353
|
+
|
354
|
+
```ruby
|
355
|
+
def create
|
356
|
+
@form = RegistrationForm.from_params(params)
|
357
|
+
|
358
|
+
RegisterAccount.call(@form) do
|
359
|
+
on(:ok) { redirect_to dashboard_path }
|
360
|
+
on(:invalid) { render :new }
|
361
|
+
on(:already_registered) { redirect_to login_path }
|
362
|
+
end
|
363
|
+
end
|
364
|
+
```
|
365
|
+
|
366
|
+
### What happens inside a Command?
|
367
|
+
|
368
|
+
When you call the `.call` class method, Rectify will instantiate a new instance
|
369
|
+
of the command and will pass the parameters to it's constructor, it will then
|
370
|
+
call the instance method `#call` on the newly created command object. The `.call`
|
371
|
+
method also allows you to supply a block where you can handle the events that may
|
372
|
+
have been broadcast from the command.
|
373
|
+
|
374
|
+
The events that your Command broadcasts can be anything, Rectify suggests `:ok`
|
375
|
+
for success and `:invalid` if the form data is not valid, but it's totally up to
|
376
|
+
you.
|
377
|
+
|
378
|
+
From here you can choose to implement your Command how you see fit. A
|
379
|
+
`Rectify::Command` only has to have the instance method `#call`.
|
380
|
+
|
381
|
+
### Writing Commands
|
382
|
+
|
383
|
+
As your application grows and Commands get more complex we recommend using the
|
384
|
+
structure above. Within the `#call` method you first check that the input data is
|
385
|
+
valid. If it is you then perform the various tasks that need to be completed.
|
386
|
+
We recommend using private methods for each step that are well named which makes
|
387
|
+
it very easy for anyone reading the code to workout what it does.
|
388
|
+
|
389
|
+
Feel free to use other classes and objects where appropriate to keep your code
|
390
|
+
well organised and maintainable.
|
391
|
+
|
392
|
+
### Events
|
393
|
+
|
394
|
+
Just as in [Wisper](https://github.com/krisleech/wisper), you fire events using
|
395
|
+
the `broadcast` method. You can use any event name you like. You can also pass
|
396
|
+
parameters to the handling block:
|
397
|
+
|
398
|
+
```ruby
|
399
|
+
# within the command:
|
400
|
+
|
401
|
+
class RegisterAccount < Rectify::Command
|
402
|
+
def call
|
403
|
+
# ...
|
404
|
+
broadcast(:ok, user)
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
# within the controller:
|
409
|
+
|
410
|
+
def create
|
411
|
+
RegisterAccount.call(@form) do
|
412
|
+
on(:ok) { |user| logger.info("#{user.first_name} created") }
|
413
|
+
end
|
414
|
+
end
|
415
|
+
```
|
416
|
+
|
417
|
+
When an event is handled, the appropriate block is called in the context of the
|
418
|
+
controller. Basically, any method call within the block is delegated back to the
|
419
|
+
controller.
|
420
|
+
|
421
|
+
You may occasionally want to expose a value within a handler block to the view.
|
422
|
+
You do this via the `expose` method within the handler block. If you want to
|
423
|
+
use `expose` then you must include the `Rectify::Controller` module in your
|
424
|
+
controller. You pass a hash of the variables you wish to expose to the view and
|
425
|
+
they will then be available. If you have set a Presenter for the view then
|
426
|
+
`expose` will try to set an attribute on that presenter. If there is no Presenter
|
427
|
+
or the Presenter doesn't have a matching attribute then `expose` will set an
|
428
|
+
instance variable of the same name. See below for more details about Presenters.
|
429
|
+
|
430
|
+
```ruby
|
431
|
+
# within the controller:
|
432
|
+
|
433
|
+
include Rectify::Controller
|
434
|
+
|
435
|
+
def create
|
436
|
+
present HomePresenter.new(:name => "Guest")
|
437
|
+
|
438
|
+
RegisterAccount.call(@form) do
|
439
|
+
on(:ok) { |user| expose(:name => user.name, :greeting => "Hello") }
|
440
|
+
end
|
441
|
+
end
|
442
|
+
```
|
443
|
+
|
444
|
+
```html
|
445
|
+
<!-- within the view: -->
|
446
|
+
|
447
|
+
<p><%= @greeting %> <%= presenter.name %></p>
|
448
|
+
# => <p>Hello Andy</p>
|
449
|
+
```
|
450
|
+
|
451
|
+
Take a look at [Wisper](https://github.com/krisleech/wisper) for more information
|
452
|
+
around how to do publish/subscribe.
|
453
|
+
|
454
|
+
## Presenters
|
455
|
+
|
456
|
+
A Presenter is a class that contains the presentational logic for your views. These
|
457
|
+
are also known as an "exhibit", "view model", "view object" or just a "view" (Rails
|
458
|
+
views are actually templates, but anyway). To avoid confusion Rectify calls these
|
459
|
+
classes Presenters.
|
460
|
+
|
461
|
+
It's often the case that you need some logic that is just for the UI. The same
|
462
|
+
question comes up, where should this logic go? You could put it directly in the
|
463
|
+
view, add it to the model or create a helper. Rectify's opinion is that all of
|
464
|
+
these are incorrect. Instead, create a Presenter for the view (or component of
|
465
|
+
the view) and place your logic here. These classes are easily testable and provide
|
466
|
+
a more object oriented approach to the problem.
|
467
|
+
|
468
|
+
To create a Presenter just derive off of `Rectify::Presenter`, add attributes as
|
469
|
+
you do for Form Objects using [Virtus](https://github.com/solnic/virtus) `attribute`
|
470
|
+
declaration. Inside a Presenter you have access to all view helper methods so
|
471
|
+
it's easy to move the presetation logic here:
|
472
|
+
|
473
|
+
```ruby
|
474
|
+
class UserDetailsPresenter < Rectify::Presenter
|
475
|
+
attribute :user, User
|
476
|
+
|
477
|
+
def edit_link
|
478
|
+
return "" unless user.admin?
|
479
|
+
|
480
|
+
link_to "Edit #{user.name}", edit_user_path(user)
|
481
|
+
end
|
482
|
+
end
|
483
|
+
```
|
484
|
+
|
485
|
+
Once you have a Presenter, you typically create it in your controller and make it
|
486
|
+
accessible to your views. There are two ways to do that. The first way is to just
|
487
|
+
treat it as a normal class:
|
488
|
+
|
489
|
+
```ruby
|
490
|
+
class UsersController < ApplicationController
|
491
|
+
def show
|
492
|
+
user = User.find(params[:id])
|
493
|
+
|
494
|
+
@presenter = UserDetailsPresenter.new(:user => user).for_controller(self)
|
495
|
+
end
|
496
|
+
end
|
497
|
+
```
|
498
|
+
|
499
|
+
You need to call `#for_controller` and pass it a controller instance which will
|
500
|
+
allow it access to the view helpers. You can then use the Presenter in your views
|
501
|
+
as you would expect:
|
502
|
+
|
503
|
+
```html
|
504
|
+
<p><%= @presenter.edit_link %></p>
|
505
|
+
```
|
506
|
+
|
507
|
+
The second way is a little cleaner as we have supplied a few helper methods to
|
508
|
+
clean up remove some of the boilerplate. You need to include the `Rectify::Controller`
|
509
|
+
module and then use the `present` helper:
|
510
|
+
|
511
|
+
```ruby
|
512
|
+
class UsersController < ApplicationController
|
513
|
+
include Rectify::Controller
|
514
|
+
|
515
|
+
def show
|
516
|
+
user = User.find(params[:id])
|
517
|
+
|
518
|
+
present UserDetailsPresenter.new(:user => user)
|
519
|
+
end
|
520
|
+
end
|
521
|
+
```
|
522
|
+
|
523
|
+
In your view, you can access this presenter using the `presenter` helper method:
|
524
|
+
|
525
|
+
```html
|
526
|
+
<p><%= presenter.edit_link %></p>
|
527
|
+
```
|
528
|
+
|
529
|
+
We recommend having a single Presenter per view but you may want to have more
|
530
|
+
than one presenter. You can use a Presenter to to hold the presentation logic
|
531
|
+
of your layout or for a component view. To do this, you can either use the first
|
532
|
+
method above or use the `present` method and add a `for` option with any key:
|
533
|
+
|
534
|
+
```ruby
|
535
|
+
class ApplicationController < ActionController::Base
|
536
|
+
include Rectify::Controller
|
537
|
+
|
538
|
+
before_action { present LayoutPresenter.new(:user => user), :for => :layout }
|
539
|
+
end
|
540
|
+
```
|
541
|
+
|
542
|
+
To access this Presenter in the view, just pass the Presenter key to the `presenter`
|
543
|
+
method like so:
|
544
|
+
|
545
|
+
```html
|
546
|
+
<p><%= presenter(:layout).login_link %></p>
|
547
|
+
```
|
548
|
+
|
549
|
+
### Updating values of a Presenter
|
550
|
+
|
551
|
+
After a presenter has been instantiated you can update it's values but just setting
|
552
|
+
their attributes:
|
553
|
+
|
554
|
+
```ruby
|
555
|
+
class UsersController < ApplicationController
|
556
|
+
include Rectify::Controller
|
557
|
+
|
558
|
+
def show
|
559
|
+
user = User.find(params[:id])
|
560
|
+
|
561
|
+
present UserDetailsPresenter.new(:user => user)
|
562
|
+
presenter.user = User.first
|
563
|
+
end
|
564
|
+
|
565
|
+
# or...
|
566
|
+
|
567
|
+
def other_action
|
568
|
+
user = User.find(params[:id])
|
569
|
+
|
570
|
+
@presenter = UserDetailsPresenter.new(:user => user).for_controller(self)
|
571
|
+
@presenter.user = User.first
|
572
|
+
end
|
573
|
+
end
|
574
|
+
```
|
575
|
+
|
576
|
+
As mentioned above in the Commands section, you can use the `expose` method (if
|
577
|
+
you include `Rectify::Controller`). You can use this anywhere in the controller
|
578
|
+
action including the Command handler block. If you have set a Presenter for the
|
579
|
+
view then `expose` will try to set an attribute on that presenter. If there is
|
580
|
+
no Presenter or the Presenter doesn't have a matching attribute then `expose`
|
581
|
+
will set an instance variable of the same name:
|
582
|
+
|
583
|
+
```ruby
|
584
|
+
class UsersController < ApplicationController
|
585
|
+
include Rectify::Controller
|
586
|
+
|
587
|
+
def show
|
588
|
+
user = User.find(params[:id])
|
589
|
+
|
590
|
+
present UserDetailsPresenter.new(:user => user)
|
591
|
+
|
592
|
+
expose(:user => User.first, :message => "Hello there!")
|
593
|
+
|
594
|
+
# presenter.user == User.first
|
595
|
+
# @message == "Hello there!"
|
596
|
+
end
|
597
|
+
end
|
598
|
+
```
|
599
|
+
|
600
|
+
### Decorators
|
601
|
+
|
602
|
+
Another option for containing your UI logic is to use a Decorator. Rectify doesn't
|
603
|
+
ship with a built in way to create a decorator but we recommend either using
|
604
|
+
[Draper](https://github.com/drapergem/draper) or you can roll your own using
|
605
|
+
`SimpleDelegator`:
|
606
|
+
|
607
|
+
```ruby
|
608
|
+
class UserDecorator < SimpleDelegator
|
609
|
+
def full_name
|
610
|
+
"#{first_name} #{last_name}"
|
611
|
+
end
|
612
|
+
end
|
613
|
+
|
614
|
+
user = User.new(:first_name => "Andy", :last_name => "Pike")
|
615
|
+
decorator = UserDecorator.new(user)
|
616
|
+
decorator.full_name # => "Andy Pike"
|
617
|
+
```
|
618
|
+
|
619
|
+
If you want to decorate a collection of objects you can do that by adding the
|
620
|
+
`for_collection` method:
|
621
|
+
|
622
|
+
```ruby
|
623
|
+
class UserDecorator < SimpleDelegator
|
624
|
+
# ...
|
625
|
+
|
626
|
+
def self.for_collection(users)
|
627
|
+
users.map { |u| new(u) }
|
628
|
+
end
|
629
|
+
end
|
630
|
+
|
631
|
+
users = UserDecorator.for_collection(User.all)
|
632
|
+
user.each do |u|
|
633
|
+
u.full_name # => Works for each user :o)
|
634
|
+
end
|
635
|
+
```
|
636
|
+
|
637
|
+
## Where do I put my files?
|
638
|
+
|
639
|
+
The next inevitable question is "Where do I put my Forms, Commands and Presenters?".
|
640
|
+
You could create `forms`, `commands` and `presenters` folders and follow the Rails Way.
|
641
|
+
Rectify suggests grouping your classes by feature rather than by pattern. For example,
|
642
|
+
create a folder called `core` (this can be anything) and within that, create a
|
643
|
+
folder for each broad feature of your application. Something like the following:
|
644
|
+
|
645
|
+
```
|
646
|
+
.
|
647
|
+
└── app
|
648
|
+
├── controllers
|
649
|
+
├── core
|
650
|
+
│ ├── billing
|
651
|
+
│ ├── fulfilment
|
652
|
+
│ ├── ordering
|
653
|
+
│ ├── reporting
|
654
|
+
│ └── security
|
655
|
+
├── models
|
656
|
+
└── views
|
657
|
+
```
|
658
|
+
|
659
|
+
Then you would place your classes in the appropriate feature folder. If you follow
|
660
|
+
this pattern remember to namespace your classes with a matching module:
|
661
|
+
|
662
|
+
```ruby
|
663
|
+
# in app/core/billing/send_invoice.rb
|
664
|
+
|
665
|
+
module Billing
|
666
|
+
class SendInvoice < Rectify::Command
|
667
|
+
# ...
|
668
|
+
end
|
669
|
+
end
|
670
|
+
```
|
671
|
+
|
672
|
+
You don't need to alter your load path as everything in the `app` folder is
|
673
|
+
loaded automatically.
|
674
|
+
|
675
|
+
## Trade offs
|
676
|
+
|
677
|
+
This style of Rails architecture is not a silver bullet for all projects. If your
|
678
|
+
app is pretty much just basic CRUD then you are unlikely to get much benefit from
|
679
|
+
this. However, if your app is more than just CRUD then you should see an
|
680
|
+
improvement in code structure and maintainability.
|
681
|
+
|
682
|
+
The downside to this approach is that there will be many more classes and files
|
683
|
+
to deal with. This can be tricky as the application gets bigger to hold the whole
|
684
|
+
system in your head. Personally I would prefer that as maintaining it will be
|
685
|
+
easier as all code around a specific user task is on one place.
|
686
|
+
|
687
|
+
Before you use these methods in your project, consider the trade off and use these
|
688
|
+
strategies where they make sense for you and your project.
|
689
|
+
|
690
|
+
## What's next?
|
691
|
+
|
692
|
+
We stated above that the models should be responsible for data access. We
|
693
|
+
may introduce a nice way to keep using the power of ActiveRecord but in a way
|
694
|
+
where your models don't end up as a big ball of queries. We're thinking about
|
695
|
+
Query Objects and a nice way to do this and we're also thinking about a nicer way
|
696
|
+
to use raw SQL.
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rectify
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andy Pike
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-02-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: virtus
|
@@ -110,6 +110,26 @@ dependencies:
|
|
110
110
|
- - ">="
|
111
111
|
- !ruby/object:Gem::Version
|
112
112
|
version: 4.2.0
|
113
|
+
- !ruby/object:Gem::Dependency
|
114
|
+
name: actionpack
|
115
|
+
requirement: !ruby/object:Gem::Requirement
|
116
|
+
requirements:
|
117
|
+
- - "~>"
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: '4.2'
|
120
|
+
- - ">="
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: 4.2.0
|
123
|
+
type: :development
|
124
|
+
prerelease: false
|
125
|
+
version_requirements: !ruby/object:Gem::Requirement
|
126
|
+
requirements:
|
127
|
+
- - "~>"
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: '4.2'
|
130
|
+
- - ">="
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: 4.2.0
|
113
133
|
- !ruby/object:Gem::Dependency
|
114
134
|
name: awesome_print
|
115
135
|
requirement: !ruby/object:Gem::Requirement
|
@@ -195,7 +215,9 @@ files:
|
|
195
215
|
- LICENSE.txt
|
196
216
|
- lib/rectify.rb
|
197
217
|
- lib/rectify/command.rb
|
218
|
+
- lib/rectify/controller.rb
|
198
219
|
- lib/rectify/form.rb
|
220
|
+
- lib/rectify/presenter.rb
|
199
221
|
- lib/rectify/save_command.rb
|
200
222
|
- lib/rectify/version.rb
|
201
223
|
- readme.md
|
@@ -219,9 +241,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
219
241
|
version: '0'
|
220
242
|
requirements: []
|
221
243
|
rubyforge_project:
|
222
|
-
rubygems_version: 2.4.
|
244
|
+
rubygems_version: 2.4.5
|
223
245
|
signing_key:
|
224
246
|
specification_version: 4
|
225
247
|
summary: Improvements for building Rails apps
|
226
248
|
test_files: []
|
227
|
-
has_rdoc:
|