frill 0.1.8 → 0.1.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/VERSION +1 -1
  2. data/lib/frill/rails.rb +20 -0
  3. data/readme.markdown +114 -77
  4. metadata +11 -11
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.8
1
+ 0.1.9
@@ -1,5 +1,25 @@
1
+ module Frill
2
+ module Auto
3
+ protected
4
+
5
+ def view_assigns
6
+ new_hash = {}
7
+
8
+ super.each do |key,value|
9
+ new_hash[key] = frill value
10
+ end
11
+
12
+ new_hash
13
+ end
14
+ end
15
+ end
16
+
1
17
  module ActionController
2
18
  class Base
19
+ def self.auto_frill
20
+ self.send :include, Frill::Auto
21
+ end
22
+
3
23
  helper_method :frill
4
24
 
5
25
  private
@@ -23,66 +23,110 @@ $ rails g frill Timestamp --test-framework=rspec
23
23
  create spec/frills/timestamp_frill_spec.rb
24
24
  ```
25
25
 
26
- ## Usage
26
+ ## Refactoring timestamp helpers with decorators
27
27
 
28
- (For the purposes of this tutorial, I'm going to assume you're using
29
- `frill` inside a Rails app. Checkout the `Usage outside Rails` section
30
- below if you're not using Rails.)
28
+ Your product manager writes the following story for you:
31
29
 
32
- Imagine you're creating a web application that includes both a
33
- JSON API and an HTML frontend, and you've decided to always present a
34
- timestamp as YEAR/MONTH/DAY. Furthermore, when presented in HTML, you
35
- always want your timestamps wrapped in `<b>` tags.
30
+ ```cucumber
31
+ Feature: Consistent Timestamp Presentation
32
+ As a user
33
+ I want "created at" timestamps presented in a uniform way on the site
34
+ So that I can easily discover the age of content on the site
36
35
 
37
- This is a perfect fit for the GoF decorator pattern. Start by generating a `Timestamp` frill:
36
+ Scenario: Presenting timestamps
37
+ When I navigate to a page that displays a created_at timestamp
38
+ Then I should see that timestamp marked up as bold and formatted as follows: YYYY/MM/DD
39
+ ```
38
40
 
39
- ```sh
40
- $ rails g frill Timestamp --test-framework=rspec
41
- create app/frills/timestamp_frill.rb
42
- invoke rspec
43
- create spec/frills/timestamp_frill_spec.rb
41
+ You see this and roll your eyes. You're thinking about all of the places that you show `created_at`
42
+ timestamps on the site. Regardless you roll up your sleeves and start by writing the following helper and partial:
43
+
44
+ ```ruby
45
+ module ApplicationHelper
46
+ def format_timestamp(t)
47
+ render partial: "shared/timestamp", locals: { time: t.strftime "%Y/%m/%d" }
48
+ end
49
+ end
50
+ ```
51
+
52
+ ```erb
53
+ <b><%=time%></b>
54
+ ```
55
+
56
+ You then begin the tedious task of tracking down all of the places you render timestamps on the site and wrapping them with `format_timestamp` helper calls:
57
+
58
+ ```erb
59
+ ...
60
+ Written on <%=format_timestamp @article.created_at %>
44
61
  ```
45
62
 
46
- You can safely leave off the `--test-framework=rspec` portion if you've configured rspec as your default framework (or
47
- if you're not using rspec at all).
63
+ You hate this approach.
64
+
65
+ 1. It's tedious
66
+ 1. It's procedural
67
+ 1. Developers have to remember to manually wrap timestamps with your `format_timestamp` helper. Developers suck at remembering things like that. FACEPALM
68
+
69
+ After you deliver the story, your product owner says "Great! But what about the format of timestamps in the JSON api? Here's another story."
70
+
71
+ ```cucumber
72
+ Feature: Consistent Timestamp Presentation in the API
73
+ As an API consumer
74
+ I want "created at" timestamps presented in the API in uniform way
75
+ So that I can easily discover the age of data I consume
76
+
77
+ Scenario: Presenting timestamps
78
+ When I retrieve content with a "created_at" timestamp via the JSON API
79
+ Then I should see that timestamp formatted as follows: YYYY/MM/DD
80
+ ```
81
+
82
+ You attempt to salvage the helper, updating it with concerns for the JSON format:
83
+
84
+ ```ruby
85
+ module ApplicationHelper
86
+ def format_timestamp(t)
87
+ time = t.strftime "%Y/%m/%d"
88
+
89
+ if request.format.html?
90
+ render partial: "shared/timestamp", locals: { time: time }
91
+ elsif request.format.json?
92
+ time
93
+ end
94
+ end
95
+ end
96
+ ```
97
+
98
+ And now you begin the tedious track of updating all of the JSON views with the helper:
99
+
100
+ ```ruby
101
+ json.created_at format_timestamp(@article.created_at)
102
+ ```
103
+
104
+ At this point, you're banging your head against a table.
105
+
106
+ ### Enter Frill
48
107
 
49
- Now open up `app/frills/timestamp_frill.rb` and format those timestamps:
108
+ Let's refactor this using the decorator pattern. First, revert all of your changes. Next, add the `frill` gem to your Gemfile, run `bundle`, then generate a frill: `rails g frill TimestampFrill`:
50
109
 
51
110
  ```ruby
52
111
  module TimestampFrill
53
112
  include Frill
54
113
 
55
114
  def self.frill? object, context
56
- object.respond_to?(:created_at) && object.respond_to?(:updated_at)
115
+ object.respond_to?(:created_at)
57
116
  end
58
117
 
59
118
  def created_at
60
- format_time super
61
- end
62
-
63
- def updated_at
64
- format_time super
65
- end
66
-
67
- private
68
-
69
- def format_time(t)
70
- t.strftime "%Y/%m/%d"
119
+ super.strftime "%Y/%m/%d"
71
120
  end
72
121
  end
73
122
  ```
74
123
 
75
- The first method `self.frill?` tells `Frill` what kind of objects this
76
- decorator is applicable to. In our case, it's any object that have timestamps.
124
+ The `frill?` method tells `Frill` when to extend an object with this module. Then we redefine the `created_at` method,
125
+ calling super and then formatting the date returned with `strftime`.
77
126
 
78
- Next, let's create an `HtmlTimestampFrill` module:
127
+ Simple enough.
79
128
 
80
- ```sh
81
- $ rails g frill HtmlTimestamp --test-framework=rspec
82
- create app/frills/html_timestamp_frill.rb
83
- invoke rspec
84
- create spec/frills/html_timestamp_frill_spec.rb
85
- ```
129
+ Next, generate another frill for presenting timestamps via HTML (`rails g frill HtmlTimestampFrill`):
86
130
 
87
131
  ```ruby
88
132
  module HtmlTimestampFrill
@@ -90,37 +134,24 @@ module HtmlTimestampFrill
90
134
  after TimestampFrill
91
135
 
92
136
  def self.frill? object, context
93
- object.respond_to?(:created_at) &&
94
- object.respond_to?(:updated_at) &&
95
- context.request.format.html?
137
+ object.respond_to?(:created_at) && context.request.format.html?
96
138
  end
97
139
 
98
140
  def created_at
99
- format_time_for_html super
100
- end
101
-
102
- def updated_at
103
- format_time_for_html super
104
- end
105
-
106
- private
107
- def format_time_for_html t
108
- h.content_tag :b, t
141
+ h.render partial: "shared/timestamp", locals: { time: super }
109
142
  end
110
143
  end
111
144
  ```
112
145
 
113
- Two things to note: the `HtmlTimestampFrill` is only applicable to
114
- objects that have timestamps _when presented in "html"_. Also, we
115
- tell `Frill` to decorate `after` `TimestampFrill` is applied (so that
116
- `super` in `created_at` returns our `TimestampFrill` response).
146
+ There's two important things to note:
117
147
 
118
- Note that you can also specify decoration dependencies with `before` instead of `after`.
148
+ 1. This frill comes after `TimestampFrill`. That tells `Frill` that it should only attempt to extend an object with this module after attempting to extend it with `TimestampFrill`.
149
+ 1. The `frill?` method only returns true if it's an HTML request, meaning this frill won't be extended onto objects for your JSON api.
119
150
 
120
- Next, in our controller, we need to decorate our objects with frills:
151
+ Lastly, opt objects into frilling inside your controllers:
121
152
 
122
153
  ```ruby
123
- class PostsController < ApplicationController
154
+ class ArticlesController < ApplicationController
124
155
  respond_to :json, :html
125
156
 
126
157
  def show
@@ -130,19 +161,37 @@ class PostsController < ApplicationController
130
161
  end
131
162
  ```
132
163
 
133
- Notice that we've wrapped our article in a `frill`.
164
+ And that's it. You don't have to update any of your views. Why? When you call the `frill` method inside your controller and pass it an object (or a collection of objects),
165
+ frill will attempt to extend the object with any applicable frills (i.e., frills that return `true` for the `frill?` method when passed the object and the request context).
134
166
 
135
- In your html view, you simply call `@article.created_at`:
167
+ That way, you can simple render your `created_at` attributes without any helpers, and they will automatically present themselves appropriately for their context (e.g., HTML v. JSON requests).
136
168
 
137
- ```erb
138
- <%= @article.created_at %>
169
+ Note that if prefer, you can configure your controllers to automatically frill all objects for presentation by calling the `auto_frill` method inside your `ApplicationController`, instead of manually having to opt them it via the `frill` method:
170
+
171
+ ```ruby
172
+ class ApplicationController < ActionController::Base
173
+ auto_frill
174
+ end
139
175
  ```
140
176
 
141
- The same goes for your JSON view.
177
+ Now, you could remove the `frill` from your `ArticlesController`:
178
+
179
+ ```ruby
180
+ class ArticlesController < ApplicationController
181
+ respond_to :json, :html
182
+
183
+ def show
184
+ @article = Article.find(params[:id])
185
+ respond_with @article
186
+ end
187
+ end
188
+ ```
189
+
190
+ Now, any instance variables you create in your controllers will be automatically frilled before handed off to your views.
142
191
 
143
192
  ### 'frill' decorates individual objects _and_ collections
144
193
 
145
- The `frill` helper will decorate both collections and associations. You can use it both within your controller
194
+ As I've hinted at, the `frill` helper will decorate both single objects and collections of objects. You can use it both within your controller
146
195
  and within your views.
147
196
 
148
197
  For example, inside a controller:
@@ -169,7 +218,7 @@ Or, in a view:
169
218
 
170
219
  There are really just two integrations in a Rails app: the `frill`
171
220
  method inside of your controller, plus the ability to call
172
- `ActionView::Helper` methods inside of your module methods.
221
+ helper methods inside of your module methods.
173
222
 
174
223
  To kickoff the decoration of an object outside of a Rails application,
175
224
  simply call `Frill.decorate`:
@@ -182,15 +231,3 @@ Frill.decorate my_object, my_context
182
231
 
183
232
  * Ben Moss
184
233
  * Nicholas Greenfield
185
-
186
- ## License
187
-
188
- (The MIT License)
189
-
190
- Copyright © 2012 Matt Parker
191
-
192
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ‘Software’), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
193
-
194
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
195
-
196
- THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: frill
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.8
4
+ version: 0.1.9
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -13,7 +13,7 @@ date: 2012-08-25 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
16
- requirement: &70351976271960 !ruby/object:Gem::Requirement
16
+ requirement: &70096022400640 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 3.2.2
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *70351976271960
24
+ version_requirements: *70096022400640
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rspec
27
- requirement: &70351976271340 !ruby/object:Gem::Requirement
27
+ requirement: &70096022399720 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *70351976271340
35
+ version_requirements: *70096022399720
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rspec-rails
38
- requirement: &70351976270520 !ruby/object:Gem::Requirement
38
+ requirement: &70096022399120 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *70351976270520
46
+ version_requirements: *70096022399120
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: capybara
49
- requirement: &70351976270000 !ruby/object:Gem::Requirement
49
+ requirement: &70096022398500 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: '0'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *70351976270000
57
+ version_requirements: *70096022398500
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: pry
60
- requirement: &70351976269480 !ruby/object:Gem::Requirement
60
+ requirement: &70096022397820 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ! '>='
@@ -65,7 +65,7 @@ dependencies:
65
65
  version: '0'
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *70351976269480
68
+ version_requirements: *70096022397820
69
69
  description:
70
70
  email: moonmaster9000@gmail.com
71
71
  executables: []