yoomee-decent_exposure 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +12 -0
- data/README.md +163 -0
- data/init.rb +2 -0
- data/lib/decent_exposure/default_exposure.rb +30 -0
- data/lib/decent_exposure/railtie.rb +19 -0
- data/lib/decent_exposure/version.rb +3 -0
- data/lib/decent_exposure.rb +32 -0
- data/rails/init.rb +1 -0
- metadata +152 -0
data/COPYING
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
2
|
+
Version 2, December 2004
|
3
|
+
|
4
|
+
Everyone is permitted to copy and distribute verbatim or modified
|
5
|
+
copies of this license document, and changing it is allowed as long
|
6
|
+
as the name is changed.
|
7
|
+
|
8
|
+
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
9
|
+
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
10
|
+
|
11
|
+
0. You just DO WHAT THE FUCK YOU WANT TO.
|
12
|
+
|
data/README.md
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
`decent_exposure` helps you program to an interface, rather than an implementation in
|
2
|
+
your Rails controllers.
|
3
|
+
|
4
|
+
Sharing state via instance variables in controllers promotes close coupling with
|
5
|
+
views. `decent_exposure` gives you a declarative manner of exposing an interface to the
|
6
|
+
state that controllers contain, thereby decreasing coupling and improving your
|
7
|
+
testability and overall design. I elaborate on this approach in [A Diatribe on
|
8
|
+
Maintaining State][diatribe].
|
9
|
+
|
10
|
+
Installation
|
11
|
+
------------
|
12
|
+
|
13
|
+
gem install decent_exposure
|
14
|
+
|
15
|
+
Configure your Rails 2.X application to use it:
|
16
|
+
|
17
|
+
In `config/environment.rb`:
|
18
|
+
|
19
|
+
config.gem 'decent_exposure'
|
20
|
+
|
21
|
+
When used in Rails 3:
|
22
|
+
|
23
|
+
In `Gemfile`:
|
24
|
+
|
25
|
+
gem 'decent_exposure'
|
26
|
+
|
27
|
+
|
28
|
+
Examples
|
29
|
+
--------
|
30
|
+
|
31
|
+
### Railscast
|
32
|
+
|
33
|
+
Ryan Bates has a Railscasts episode covering `decent_exposure`. If you're just
|
34
|
+
getting started or just enjoy screencasts (Ryan's are always great), you can
|
35
|
+
check it out here: [Railscasts - Decent Exposure][railscast].
|
36
|
+
|
37
|
+
### A full example
|
38
|
+
|
39
|
+
The wiki has a full example of [converting a classic-style Rails
|
40
|
+
controller][converting].
|
41
|
+
|
42
|
+
### In your controllers
|
43
|
+
|
44
|
+
When no block is given, `expose` attempts to determine which resource you want
|
45
|
+
to acquire. When `params` contains `:category_id` or `:id`, a call to:
|
46
|
+
|
47
|
+
expose(:category)
|
48
|
+
|
49
|
+
Would result in the following `ActiveRecord#find`:
|
50
|
+
|
51
|
+
Category.find(params[:category_id]||params[:id])
|
52
|
+
|
53
|
+
As the example shows, the symbol passed is used to guess the class name of the
|
54
|
+
object (and potentially the `params` key to find it with) you want an instance
|
55
|
+
of.
|
56
|
+
|
57
|
+
Should `params` not contain an identifiable `id`, a call to:
|
58
|
+
|
59
|
+
expose(:category)
|
60
|
+
|
61
|
+
Will instead attempt to build a new instance of the object like so:
|
62
|
+
|
63
|
+
Category.new(params[:category])
|
64
|
+
|
65
|
+
If you define a collection with a pluralized name of the singular resource,
|
66
|
+
`decent_exposure` will attempt to use it to scope its calls from. Let's take the
|
67
|
+
following scenario:
|
68
|
+
|
69
|
+
class ProductsController < ApplicationController
|
70
|
+
expose(:category)
|
71
|
+
expose(:products) { category.products }
|
72
|
+
expose(:product)
|
73
|
+
end
|
74
|
+
|
75
|
+
The `product` resource would scope from the `products` collection via a
|
76
|
+
fully-expanded query equivalent to this:
|
77
|
+
|
78
|
+
Category.find(params[:category_id]).products.find(params[:id])
|
79
|
+
|
80
|
+
or (depending on the contents of the `params` hash) this:
|
81
|
+
|
82
|
+
Category.find(params[:category_id]).products.new(params[:product])
|
83
|
+
|
84
|
+
In the straightforward case, the three exposed resources above provide for
|
85
|
+
access to both the primary and ancestor resources in a way usable across all 7
|
86
|
+
actions in a typicall Rails-style RESTful controller.
|
87
|
+
|
88
|
+
#### A Note on Style
|
89
|
+
|
90
|
+
When the code has become complex enough to surpass a single line (and is not
|
91
|
+
appropriate to extract into a model method), use the `do...end` style of block:
|
92
|
+
|
93
|
+
expose(:associated_products) do
|
94
|
+
product.associated.tap do |associated_products|
|
95
|
+
present(associated_products, :with => AssociatedProductPresenter)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
### In your views
|
100
|
+
|
101
|
+
Use the product of those assignments like you would an instance variable or any
|
102
|
+
other method you might normally have access to:
|
103
|
+
|
104
|
+
= render bread_crumbs_for(category)
|
105
|
+
%h3#product_title= product.title
|
106
|
+
= render product
|
107
|
+
%h3 Associated Products
|
108
|
+
%ul
|
109
|
+
- associated_products.each do |associated_product|
|
110
|
+
%li= link_to(associated_product.title,product_path(associated_product))
|
111
|
+
|
112
|
+
### Custom defaults
|
113
|
+
|
114
|
+
`decent_exposure` provides opinionated default logic when `expose` is invoked without
|
115
|
+
a block. It's possible, however, to override this with custom default logic by
|
116
|
+
passing a block accepting a single argument to the `default_exposure` method
|
117
|
+
inside of a controller. The argument will be the string or symbol passed in to
|
118
|
+
the `expose` call.
|
119
|
+
|
120
|
+
class MyController < ApplicationController
|
121
|
+
default_exposure do |name|
|
122
|
+
ObjectCache.load(name.to_s)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
The given block will be invoked in the context of a controller instance. It is
|
127
|
+
possible to provide a custom default for a descendant class without disturbing
|
128
|
+
its ancestor classes in an inheritance heirachy.
|
129
|
+
|
130
|
+
Beware
|
131
|
+
------
|
132
|
+
|
133
|
+
This is a simple tool, which provides a solitary solution. It must be used in
|
134
|
+
conjunction with solid design approaches ("Program to an interface, not an
|
135
|
+
implementation.") and accepted best practices (e.g. Fat Model, Skinny
|
136
|
+
Controller). In itself, it won't heal a bad design. It is meant only to be a
|
137
|
+
tool to use in improving the overall design of a Ruby on Rails system and
|
138
|
+
moreover to provide a standard implementation for an emerging best practice.
|
139
|
+
|
140
|
+
Development
|
141
|
+
-----------
|
142
|
+
|
143
|
+
### Running specs
|
144
|
+
|
145
|
+
`decent_exposure` has been developed with the philosophy that Ruby developers shouldn't
|
146
|
+
force their choice in RubyGems package managers on people consuming their code.
|
147
|
+
As a side effect of that, if you attempt to run the specs on this application,
|
148
|
+
you might get `no such file to load` errors. The short answer is that you can
|
149
|
+
`export RUBYOPT='rubygems'` and be on about your way (for the long answer, see
|
150
|
+
Ryan Tomayko's [excellent treatise][treatise] on the subject).
|
151
|
+
|
152
|
+
[treatise]: http://tomayko.com/writings/require-rubygems-antipattern
|
153
|
+
[converting]: http://github.com/voxdolo/decent_exposure/wiki/Examples
|
154
|
+
[diatribe]: http://blog.voxdolo.me/a-diatribe-on-maintaining-state.html
|
155
|
+
[railscast]: http://railscasts.com/episodes/259-decent-exposure
|
156
|
+
|
157
|
+
Contributors
|
158
|
+
------------
|
159
|
+
|
160
|
+
Thanks to everyone that's helped out with `decent_exposure`! You can see a full
|
161
|
+
list here:
|
162
|
+
|
163
|
+
<http://github.com/voxdolo/decent_exposure/contributors>
|
data/init.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
module DecentExposure
|
2
|
+
module DefaultExposure
|
3
|
+
def self.included(klass)
|
4
|
+
klass.extend(DecentExposure)
|
5
|
+
if klass.respond_to?(:class_attribute)
|
6
|
+
klass.class_attribute(:_default_exposure)
|
7
|
+
else
|
8
|
+
klass.superclass_delegating_accessor(:_default_exposure)
|
9
|
+
end
|
10
|
+
if klass.respond_to?(:default_exposure)
|
11
|
+
klass.default_exposure do |name|
|
12
|
+
collection = name.to_s.pluralize
|
13
|
+
if respond_to?(collection) && collection != name.to_s && send(collection).respond_to?(:scoped)
|
14
|
+
proxy = send(collection)
|
15
|
+
else
|
16
|
+
proxy = name.to_s.classify.constantize
|
17
|
+
end
|
18
|
+
|
19
|
+
if id = params["#{name}_id"] || params[:id]
|
20
|
+
proxy.find(id).tap do |r|
|
21
|
+
r.attributes = params[name] unless request.get?
|
22
|
+
end
|
23
|
+
else
|
24
|
+
proxy.new(params[name])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'decent_exposure/default_exposure'
|
2
|
+
|
3
|
+
module DecentExposure
|
4
|
+
if defined? Rails::Railtie
|
5
|
+
class Railtie < Rails::Railtie
|
6
|
+
initializer "decent_exposure.extend_action_controller_base" do |app|
|
7
|
+
ActiveSupport.on_load(:action_controller) do
|
8
|
+
DecentExposure::Railtie.insert
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class Railtie
|
15
|
+
def self.insert
|
16
|
+
ActionController::Base.send(:include, DecentExposure::DefaultExposure)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'decent_exposure/railtie'
|
2
|
+
|
3
|
+
module DecentExposure
|
4
|
+
def inherited(klass)
|
5
|
+
closured_exposure = default_exposure
|
6
|
+
klass.class_eval do
|
7
|
+
default_exposure(&closured_exposure)
|
8
|
+
end
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_accessor :_default_exposure
|
13
|
+
|
14
|
+
def default_exposure(&block)
|
15
|
+
self._default_exposure = block if block_given?
|
16
|
+
_default_exposure
|
17
|
+
end
|
18
|
+
|
19
|
+
def expose(name, &block)
|
20
|
+
closured_exposure = default_exposure
|
21
|
+
define_method name do
|
22
|
+
@_resources ||= {}
|
23
|
+
@_resources[name] ||= if block_given?
|
24
|
+
instance_eval(&block)
|
25
|
+
else
|
26
|
+
instance_exec(name, &closured_exposure)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
helper_method name
|
30
|
+
hide_action name
|
31
|
+
end
|
32
|
+
end
|
data/rails/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Kernel.load File.join(File.dirname(__FILE__), '..', 'init.rb')
|
metadata
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: yoomee-decent_exposure
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 21
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 1.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Stephen Caudill
|
14
|
+
- Jon Larkowski
|
15
|
+
autorequire:
|
16
|
+
bindir: bin
|
17
|
+
cert_chain: []
|
18
|
+
|
19
|
+
date: 2011-11-14 00:00:00 +00:00
|
20
|
+
default_executable:
|
21
|
+
dependencies:
|
22
|
+
- !ruby/object:Gem::Dependency
|
23
|
+
name: rspec
|
24
|
+
prerelease: false
|
25
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ~>
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
hash: 27
|
31
|
+
segments:
|
32
|
+
- 2
|
33
|
+
- 5
|
34
|
+
- 0
|
35
|
+
version: 2.5.0
|
36
|
+
type: :development
|
37
|
+
version_requirements: *id001
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: mocha
|
40
|
+
prerelease: false
|
41
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ~>
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
hash: 35
|
47
|
+
segments:
|
48
|
+
- 0
|
49
|
+
- 9
|
50
|
+
- 12
|
51
|
+
version: 0.9.12
|
52
|
+
type: :development
|
53
|
+
version_requirements: *id002
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: ruby-debug19
|
56
|
+
prerelease: false
|
57
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ~>
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
hash: 63
|
63
|
+
segments:
|
64
|
+
- 0
|
65
|
+
- 11
|
66
|
+
- 6
|
67
|
+
version: 0.11.6
|
68
|
+
type: :development
|
69
|
+
version_requirements: *id003
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: actionpack
|
72
|
+
prerelease: false
|
73
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
hash: 3
|
79
|
+
segments:
|
80
|
+
- 0
|
81
|
+
version: "0"
|
82
|
+
type: :development
|
83
|
+
version_requirements: *id004
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: activesupport
|
86
|
+
prerelease: false
|
87
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
88
|
+
none: false
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
hash: 3
|
93
|
+
segments:
|
94
|
+
- 0
|
95
|
+
version: "0"
|
96
|
+
type: :development
|
97
|
+
version_requirements: *id005
|
98
|
+
description: "\n DecentExposure helps you program to an interface, rather than an\n implementation in your Rails controllers. The fact of the matter is that\n sharing state via instance variables in controllers promotes close coupling\n with views. DecentExposure gives you a declarative manner of exposing an\n interface to the state that controllers contain and thereby decreasing\n coupling and improving your testability and overall design.\n "
|
99
|
+
email: scaudill@gmail.com
|
100
|
+
executables: []
|
101
|
+
|
102
|
+
extensions: []
|
103
|
+
|
104
|
+
extra_rdoc_files: []
|
105
|
+
|
106
|
+
files:
|
107
|
+
- lib/decent_exposure/default_exposure.rb
|
108
|
+
- lib/decent_exposure/railtie.rb
|
109
|
+
- lib/decent_exposure/version.rb
|
110
|
+
- lib/decent_exposure.rb
|
111
|
+
- README.md
|
112
|
+
- COPYING
|
113
|
+
- init.rb
|
114
|
+
- rails/init.rb
|
115
|
+
has_rdoc: true
|
116
|
+
homepage: http://github.com/voxdolo/decent_exposure
|
117
|
+
licenses: []
|
118
|
+
|
119
|
+
post_install_message:
|
120
|
+
rdoc_options:
|
121
|
+
- --charset=UTF-8
|
122
|
+
require_paths:
|
123
|
+
- lib
|
124
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
125
|
+
none: false
|
126
|
+
requirements:
|
127
|
+
- - ">="
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
hash: 3
|
130
|
+
segments:
|
131
|
+
- 0
|
132
|
+
version: "0"
|
133
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
134
|
+
none: false
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
hash: 23
|
139
|
+
segments:
|
140
|
+
- 1
|
141
|
+
- 3
|
142
|
+
- 6
|
143
|
+
version: 1.3.6
|
144
|
+
requirements: []
|
145
|
+
|
146
|
+
rubyforge_project:
|
147
|
+
rubygems_version: 1.4.2
|
148
|
+
signing_key:
|
149
|
+
specification_version: 3
|
150
|
+
summary: A helper for creating declarative interfaces in controllers
|
151
|
+
test_files: []
|
152
|
+
|