bird_on_it 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +180 -0
- data/lib/bird_on_it.rb +1 -2
- data/lib/bird_on_it/decorator.rb +2 -0
- data/lib/bird_on_it/version.rb +1 -1
- data/test/bird_on_it_test.rb +28 -24
- data/test/dummy/app/assets/stylesheets/scaffold.css +56 -0
- data/test/dummy/app/controllers/tote_bags_controller.rb +62 -0
- data/test/dummy/app/decorators/tote_bag_decorator.rb +23 -0
- data/test/dummy/app/models/tote_bag.rb +5 -0
- data/test/dummy/app/views/tote_bags/_form.html.erb +29 -0
- data/test/dummy/app/views/tote_bags/edit.html.erb +6 -0
- data/test/dummy/app/views/tote_bags/index.html.erb +31 -0
- data/test/dummy/app/views/tote_bags/new.html.erb +5 -0
- data/test/dummy/app/views/tote_bags/show.html.erb +20 -0
- data/test/dummy/config/routes.rb +2 -0
- data/test/dummy/db/development.sqlite3 +0 -0
- data/test/dummy/db/migrate/20140314082207_create_tote_bags.rb +11 -0
- data/test/dummy/db/schema.rb +24 -0
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/dummy/log/development.log +2477 -0
- data/test/dummy/log/test.log +41 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/13fe41fee1fe35b49d145bcc06610705 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/2f5173deea6c795b8fdde723bb4b63af +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/357970feca3ac29060c1e3861e2c0953 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/371bf96e99717688ed7313a0c53f4212 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/510da110ae528e2d22533be39ff696c5 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/580936cd6180b0b516e323ab3b9290bc +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/6fc757c2c8329244ca95d6909865bbc2 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/a77bf3dfc15bd59697d7d51fa6a8ebaa +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/ab3c84b0c7e5ccef7125dd305453cb65 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/c530934da9c9116acfdee38204d3cf73 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/ce526a9f65d6aa96f21611970cabe56c +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/cffd775d018f68ce5dba1ee0d951a994 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/d771ace226fc8215a3572e0aa35bb0d6 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/f7cbd26ba1d28d48de824f0e94586655 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/fa809407dd24d48afa01dc34b11c60b6 +0 -0
- metadata +57 -7
- data/README.rdoc +0 -3
- data/test/dummy/app/decorators/canvas_bag_decorator.rb +0 -7
- data/test/dummy/app/models/canvas_bag.rb +0 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 52177a68952ac06a60c8fcfebd8b6b93b5d3bc14
|
4
|
+
data.tar.gz: 5c5b3cc9e324e96b571dc5096e24c3f57dfb443e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3cf19d242f8313349af3ae73ad56fe4ec8be6f3dccef05ce885808b4e7c54f303f3d9572bc29d9222e93aebc75c03ff9e88604dfd3d10d248b6f658c9d91b17b
|
7
|
+
data.tar.gz: be11ec856d3b5d96e7df336733fe4ecf35b782512ec5ae9887c1303b2ee886c4b4a83c1556f6138af670f8429d2355137a439ce6b433ac763b4e4238f443e81b
|
data/README.md
ADDED
@@ -0,0 +1,180 @@
|
|
1
|
+
# Bird On It: Simple decorators for your Rails models
|
2
|
+
|
3
|
+
Model need decorating in your Rails view? Put a bird on it!
|
4
|
+
|
5
|
+
## What is Bird On It?
|
6
|
+
|
7
|
+
_Bird On It_ is a simple way to decorate models in your Rails application.
|
8
|
+
|
9
|
+
Decorators allow you to move view- and presentation-related logic out of your active record models and view helpers and into decorator classes. Doing this cleans up your view code and reduces the number of responsibilities your active record objects have.
|
10
|
+
|
11
|
+
Here's how it works. Let's say you have a ToteBag model, with colour, size and straps attributes. In your view, you want to describe whether or not the bag has straps. You also want to add some css classes, depending on what properties the bag has. You put the first of these methods on the model, and the second in a helper, like so:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
# app/models/tote_bag.rb
|
15
|
+
class ToteBag < ActiveRecord::Base
|
16
|
+
def straps_description
|
17
|
+
if straps?
|
18
|
+
'This bag has straps'
|
19
|
+
else
|
20
|
+
'This bag does not have straps'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
```
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
# app/helpers/tote_bags_helper.rb
|
28
|
+
module ToteBagHelper
|
29
|
+
def tote_bag_css_classes(tote_bag)
|
30
|
+
tote_bag_css_classes = %w{tote-bag}
|
31
|
+
tote_bag_css_classes << 'straps' if tote_bag.straps?
|
32
|
+
tote_bag_css_classes.join(' ')
|
33
|
+
end
|
34
|
+
end
|
35
|
+
```
|
36
|
+
|
37
|
+
In your view, you would use both the helper and model:
|
38
|
+
|
39
|
+
```erb
|
40
|
+
<div class="<%= tote_bag_css_classes(@tote_bag) %>">
|
41
|
+
<h2>Straps:</h2>
|
42
|
+
<p><%= @tote_bag.straps_description %></p>
|
43
|
+
</div>
|
44
|
+
```
|
45
|
+
|
46
|
+
Instead of the above, however, using a tote bag decorator allows both these methods to exist in the one place. The decorator sends any method that it doesn't itself respond to the decorated object. Using _Bird On It_:
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
# app/models/tote_bag.rb
|
50
|
+
class ToteBag < ActiveRecord::Base
|
51
|
+
include BirdOnIt
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
# app/decorators/tote_bag_decorator.rb
|
57
|
+
class ToteBagDecorator
|
58
|
+
include BirdOnIt::Decorator
|
59
|
+
|
60
|
+
def straps_description
|
61
|
+
if object.straps?
|
62
|
+
'This bag has straps'
|
63
|
+
else
|
64
|
+
'This bag does not have straps'
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def css_classes
|
69
|
+
css_classes = %w{tote-bag}
|
70
|
+
css_classes << 'straps' if object.straps?
|
71
|
+
css_classes.join(' ')
|
72
|
+
end
|
73
|
+
end
|
74
|
+
```
|
75
|
+
|
76
|
+
In your view:
|
77
|
+
|
78
|
+
```erb
|
79
|
+
<div class="<%= @tote_bag.css_classes %>">
|
80
|
+
<h2>Straps:</h2>
|
81
|
+
<p><%= @tote_bag.straps_description %></p>
|
82
|
+
</div>
|
83
|
+
```
|
84
|
+
|
85
|
+
Now your view-related code can reside in the decorator, eliminating the need for helper methods and giving your tote bag class one less responsibility.
|
86
|
+
|
87
|
+
## Installation
|
88
|
+
|
89
|
+
Add _Bird On It_ to your Gemfile
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
gem 'bird_on_it'
|
93
|
+
```
|
94
|
+
|
95
|
+
Run `bundle install`.
|
96
|
+
|
97
|
+
## Usage
|
98
|
+
|
99
|
+
### Model
|
100
|
+
|
101
|
+
To decorate a model, first include _Bird On It_:
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
# app/models/post.rb
|
105
|
+
class Post < ActiveRecord::Base
|
106
|
+
include BirdOnIt
|
107
|
+
end
|
108
|
+
```
|
109
|
+
|
110
|
+
Then create a decorator either in `app/views/decorators` or `app/models` with the name matching your model and include `BirdOnIt::Decorator`.
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
# app/decorators/post_decorator.rb
|
114
|
+
class PostDecorator < ActiveRecord::Base
|
115
|
+
include BirdOnIt::Decorator
|
116
|
+
|
117
|
+
def display_title
|
118
|
+
object.title.humanize
|
119
|
+
end
|
120
|
+
end
|
121
|
+
```
|
122
|
+
|
123
|
+
In the decorator, your decorated object is available via the `object` variable. If you prefer, you can also access it simply by calling methods to which your decorator does not respond:
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
def display_title
|
127
|
+
title.humanize
|
128
|
+
end
|
129
|
+
```
|
130
|
+
|
131
|
+
### Controller
|
132
|
+
|
133
|
+
_Bird On It_ adds a `decorate` method you can call on your objects in your controller before they are passed to the view.
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
# app/controllers/posts_controller.rb
|
137
|
+
def show
|
138
|
+
@post = Post.find(params[:id]).decorate
|
139
|
+
end
|
140
|
+
```
|
141
|
+
There is also a `decorate_collection` class method for decorating collections.
|
142
|
+
|
143
|
+
```ruby
|
144
|
+
# app/controllers/posts_controller.rb
|
145
|
+
def index
|
146
|
+
@posts = Post.decorate_collection(Post.all)
|
147
|
+
end
|
148
|
+
```
|
149
|
+
|
150
|
+
### View
|
151
|
+
|
152
|
+
Use your decorated objects in the view as usual.
|
153
|
+
|
154
|
+
```erb
|
155
|
+
<div class="post">
|
156
|
+
<h1><%= @post.display_title %></h2>
|
157
|
+
<p><%= @post.body %></p>
|
158
|
+
</div>
|
159
|
+
```
|
160
|
+
|
161
|
+
Some Rails helpers, such as the edit_path helper, do not work well with decorated objects. If you encounter one of these, you can work around this by passing the helper the decorated object, instead of the decorator. For example:
|
162
|
+
|
163
|
+
```ruby
|
164
|
+
<%= link_to 'Edit', edit_post_path(@post.object) %>
|
165
|
+
```
|
166
|
+
|
167
|
+
Other features, such as linking to the show page, or using the object with `form_for`, should continue to work as usual.
|
168
|
+
|
169
|
+
```ruby
|
170
|
+
<%= link_to 'Show', @post %>
|
171
|
+
```
|
172
|
+
|
173
|
+
```ruby
|
174
|
+
<%= form_for(@post) do |f| %>
|
175
|
+
<%= f.text_field :material %>
|
176
|
+
<%= f.submit %>
|
177
|
+
<% end %>
|
178
|
+
```
|
179
|
+
|
180
|
+
This project uses MIT-LICENSE.
|
data/lib/bird_on_it.rb
CHANGED
@@ -11,8 +11,7 @@ module BirdOnIt
|
|
11
11
|
|
12
12
|
module ClassMethods
|
13
13
|
def decorate_collection(collection)
|
14
|
-
collection
|
15
|
-
collection.map { |item| decorator_class.new(item) }
|
14
|
+
Array(collection).map { |item| decorator_class.new(item) }
|
16
15
|
end
|
17
16
|
|
18
17
|
def decorator_class
|
data/lib/bird_on_it/decorator.rb
CHANGED
data/lib/bird_on_it/version.rb
CHANGED
data/test/bird_on_it_test.rb
CHANGED
@@ -1,56 +1,60 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
3
|
describe BirdOnIt do
|
4
|
-
class
|
4
|
+
class ToteBag
|
5
5
|
include BirdOnIt
|
6
6
|
|
7
|
-
def
|
8
|
-
'
|
7
|
+
def color
|
8
|
+
'red'
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
12
|
-
class
|
12
|
+
class ToteBagDecorator
|
13
13
|
include BirdOnIt::Decorator
|
14
14
|
|
15
|
-
def
|
16
|
-
|
15
|
+
def color_description
|
16
|
+
if object.color
|
17
|
+
"This bag is #{color}"
|
18
|
+
else
|
19
|
+
"Unknown color"
|
20
|
+
end
|
17
21
|
end
|
18
22
|
end
|
19
23
|
|
20
|
-
let(:
|
21
|
-
let(:
|
22
|
-
|
23
|
-
describe "when included" do
|
24
|
-
it "adds a class method #decorator_class that returns the decorator class" do
|
25
|
-
Decoratable.decorator_class.must_equal DecoratableDecorator
|
26
|
-
end
|
24
|
+
let(:tote_bag) { ToteBag.new }
|
25
|
+
let(:tote_bag_decorator) { tote_bag.decorate }
|
27
26
|
|
27
|
+
describe "when BirdOnIt is included" do
|
28
28
|
it "adds a method #decorate that returns a decorator" do
|
29
|
-
|
29
|
+
tote_bag.decorate.class.must_equal ToteBagDecorator
|
30
30
|
end
|
31
31
|
|
32
32
|
it "adds a class method #decorate_collection that decorates a collection" do
|
33
|
-
collection =
|
34
|
-
collection.map(&:class).must_equal [
|
33
|
+
collection = ToteBag.decorate_collection [ToteBag.new, ToteBag.new]
|
34
|
+
collection.map(&:class).must_equal [ToteBagDecorator, ToteBagDecorator]
|
35
|
+
end
|
36
|
+
|
37
|
+
it "adds a class method #decorator_class that returns the decorator class" do
|
38
|
+
ToteBag.decorator_class.must_equal ToteBagDecorator
|
35
39
|
end
|
36
40
|
end
|
37
41
|
|
38
|
-
describe
|
42
|
+
describe ToteBagDecorator do
|
39
43
|
it "responds to its own instance methods" do
|
40
|
-
|
41
|
-
|
44
|
+
tote_bag_decorator.respond_to?(:color_description).must_equal true
|
45
|
+
tote_bag_decorator.color_description.must_equal 'This bag is red'
|
42
46
|
end
|
43
47
|
|
44
|
-
it "
|
45
|
-
|
48
|
+
it "sends missing methods to its decorated object" do
|
49
|
+
tote_bag_decorator.color.must_equal 'red'
|
46
50
|
end
|
47
51
|
|
48
|
-
it "
|
49
|
-
|
52
|
+
it "responds to the methods of the decorated object" do
|
53
|
+
tote_bag_decorator.respond_to?(:color).must_equal true
|
50
54
|
end
|
51
55
|
|
52
56
|
it "has an object attribute that is the decorated object" do
|
53
|
-
|
57
|
+
tote_bag_decorator.object.must_equal tote_bag
|
54
58
|
end
|
55
59
|
end
|
56
60
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
body { background-color: #fff; color: #333; }
|
2
|
+
|
3
|
+
body, p, ol, ul, td {
|
4
|
+
font-family: verdana, arial, helvetica, sans-serif;
|
5
|
+
font-size: 13px;
|
6
|
+
line-height: 18px;
|
7
|
+
}
|
8
|
+
|
9
|
+
pre {
|
10
|
+
background-color: #eee;
|
11
|
+
padding: 10px;
|
12
|
+
font-size: 11px;
|
13
|
+
}
|
14
|
+
|
15
|
+
a { color: #000; }
|
16
|
+
a:visited { color: #666; }
|
17
|
+
a:hover { color: #fff; background-color:#000; }
|
18
|
+
|
19
|
+
div.field, div.actions {
|
20
|
+
margin-bottom: 10px;
|
21
|
+
}
|
22
|
+
|
23
|
+
#notice {
|
24
|
+
color: green;
|
25
|
+
}
|
26
|
+
|
27
|
+
.field_with_errors {
|
28
|
+
padding: 2px;
|
29
|
+
background-color: red;
|
30
|
+
display: table;
|
31
|
+
}
|
32
|
+
|
33
|
+
#error_explanation {
|
34
|
+
width: 450px;
|
35
|
+
border: 2px solid red;
|
36
|
+
padding: 7px;
|
37
|
+
padding-bottom: 0;
|
38
|
+
margin-bottom: 20px;
|
39
|
+
background-color: #f0f0f0;
|
40
|
+
}
|
41
|
+
|
42
|
+
#error_explanation h2 {
|
43
|
+
text-align: left;
|
44
|
+
font-weight: bold;
|
45
|
+
padding: 5px 5px 5px 15px;
|
46
|
+
font-size: 12px;
|
47
|
+
margin: -7px;
|
48
|
+
margin-bottom: 0px;
|
49
|
+
background-color: #c00;
|
50
|
+
color: #fff;
|
51
|
+
}
|
52
|
+
|
53
|
+
#error_explanation ul li {
|
54
|
+
font-size: 12px;
|
55
|
+
list-style: square;
|
56
|
+
}
|
@@ -0,0 +1,62 @@
|
|
1
|
+
class ToteBagsController < ApplicationController
|
2
|
+
respond_to :html
|
3
|
+
|
4
|
+
# GET /tote_bags
|
5
|
+
def index
|
6
|
+
@tote_bags = ToteBag.decorate_collection(ToteBag.all)
|
7
|
+
respond_with @tote_bags
|
8
|
+
end
|
9
|
+
|
10
|
+
# GET /tote_bags/1
|
11
|
+
def show
|
12
|
+
@tote_bag = ToteBag.find(params[:id]).decorate
|
13
|
+
respond_with @tote_bag
|
14
|
+
end
|
15
|
+
|
16
|
+
# GET /tote_bags/new
|
17
|
+
def new
|
18
|
+
@tote_bag = ToteBag.new
|
19
|
+
respond_with @tote_bag
|
20
|
+
end
|
21
|
+
|
22
|
+
# GET /tote_bags/1/edit
|
23
|
+
def edit
|
24
|
+
@tote_bag = ToteBag.find(params[:id]).decorate
|
25
|
+
respond_with @tote_bag
|
26
|
+
end
|
27
|
+
|
28
|
+
# POST /tote_bags
|
29
|
+
def create
|
30
|
+
@tote_bag = ToteBag.new(tote_bag_params)
|
31
|
+
|
32
|
+
if @tote_bag.save
|
33
|
+
redirect_to @tote_bag, notice: 'Tote bag was successfully created.'
|
34
|
+
else
|
35
|
+
render :new
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# PATCH/PUT /tote_bags/1
|
40
|
+
def update
|
41
|
+
@tote_bag = ToteBag.find(params[:id])
|
42
|
+
|
43
|
+
if @tote_bag.update(tote_bag_params)
|
44
|
+
redirect_to @tote_bag, notice: 'Tote bag was successfully updated.'
|
45
|
+
else
|
46
|
+
render :edit
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# DELETE /tote_bags/1
|
51
|
+
def destroy
|
52
|
+
@tote_bag = ToteBag.find(params[:id])
|
53
|
+
@tote_bag.destroy
|
54
|
+
redirect_to tote_bags_url, notice: 'Tote bag was successfully destroyed.'
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def tote_bag_params
|
60
|
+
params.require(:tote_bag).permit(:colour, :material, :straps)
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class ToteBagDecorator
|
2
|
+
include BirdOnIt::Decorator
|
3
|
+
|
4
|
+
def css_classes
|
5
|
+
css_classes = %w{tote-bag}
|
6
|
+
|
7
|
+
css_classes << 'straps' if object.straps?
|
8
|
+
|
9
|
+
css_classes.join(' ')
|
10
|
+
end
|
11
|
+
|
12
|
+
def colour_description
|
13
|
+
object.colour.present? ? object.colour : 'Has no colour'
|
14
|
+
end
|
15
|
+
|
16
|
+
def material_description
|
17
|
+
object.material.present? ? object.material : 'Unknown material'
|
18
|
+
end
|
19
|
+
|
20
|
+
def straps_description
|
21
|
+
object.straps? ? 'Has straps' : 'Strapless'
|
22
|
+
end
|
23
|
+
end
|