decent_exposure 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +12 -0
- data/README.md +106 -0
- data/VERSION +1 -0
- data/lib/decent_exposure.rb +21 -0
- data/rails/init.rb +7 -0
- data/spec/helper.rb +2 -0
- data/spec/lib/decent_exposure_spec.rb +95 -0
- metadata +70 -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,106 @@
|
|
1
|
+
DecentExposure
|
2
|
+
==============
|
3
|
+
|
4
|
+
_Copying over instance variables is bad, mmm-kay?_
|
5
|
+
|
6
|
+
DecentExposure helps you program to an interface, rather than an implementation in
|
7
|
+
your Rails controllers.
|
8
|
+
|
9
|
+
Sharing state via instance variables in controllers promotes close coupling with
|
10
|
+
views. DecentExposure gives you a declarative manner of exposing an interface to the
|
11
|
+
state that controllers contain, thereby decreasing coupling and improving your
|
12
|
+
testability and overall design.
|
13
|
+
|
14
|
+
Installation
|
15
|
+
------------
|
16
|
+
|
17
|
+
gem install decent_exposure
|
18
|
+
|
19
|
+
Configure your application to use it:
|
20
|
+
|
21
|
+
In `config/environment.rb`:
|
22
|
+
|
23
|
+
config.gem 'decent_exposure'
|
24
|
+
|
25
|
+
The Particulars
|
26
|
+
---------------
|
27
|
+
|
28
|
+
`expose` creates a method with the given name, evaluates the provided block (or
|
29
|
+
intuits a value when no block is passed) and memoizes the result. This method is
|
30
|
+
then declared as a `helper_method` so that views may have access to it and is
|
31
|
+
made unroutable as an action.
|
32
|
+
|
33
|
+
Examples
|
34
|
+
--------
|
35
|
+
|
36
|
+
### In your controllers
|
37
|
+
|
38
|
+
When no block is given, `expose` attempts to intuit which resource you want to
|
39
|
+
acquire:
|
40
|
+
|
41
|
+
# Category.find(params[:category_id] || params[:id])
|
42
|
+
expose(:category)
|
43
|
+
|
44
|
+
As the example shows, the symbol passed is used to guess the class name of the
|
45
|
+
object you want an instance of. Almost every controller has one of these. In the
|
46
|
+
RESTful controller paradigm, you might use this in `#show`, `#edit`, `#update`
|
47
|
+
or `#destroy`.
|
48
|
+
|
49
|
+
In the slightly more complicated scenario, you need to find an instance of an
|
50
|
+
object which doesn't map cleanly to `Object#find`:
|
51
|
+
|
52
|
+
expose(:product){ category.products.find(params[:id]) }
|
53
|
+
|
54
|
+
In the RESTful controller paradigm, you'll again find yourself using this in
|
55
|
+
`#show`, `#edit`, `#update` or `#destroy`.
|
56
|
+
|
57
|
+
When the code has become complex enough to surpass a single line (and is not
|
58
|
+
appropriate to extract into a model method), use the `do...end` style of block:
|
59
|
+
|
60
|
+
expose(:associated_products) do
|
61
|
+
product.associated.tap do |associated_products|
|
62
|
+
present(associated_products, :with => AssociatedProductPresenter)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
### In your views
|
67
|
+
|
68
|
+
Use the product of those assignments like you would an instance variable or any
|
69
|
+
other method you might normally have access to:
|
70
|
+
|
71
|
+
= render bread_crumbs_for(category)
|
72
|
+
%h3#product_title= product.title
|
73
|
+
= render product
|
74
|
+
%h3 Associated Products
|
75
|
+
%ul
|
76
|
+
- associated_products.each do |associated_product|
|
77
|
+
%li= link_to(associated_product.title,product_path(associated_product))
|
78
|
+
|
79
|
+
Beware
|
80
|
+
------
|
81
|
+
|
82
|
+
This is an exceptionally simple tool, which provides a solitary solution. It
|
83
|
+
must be used in conjunction with solid design approaches ("Program to an
|
84
|
+
interface, not an implementation.") and accepted best practices (e.g. Fat Model,
|
85
|
+
Skinny Controller). In itself, it won't heal a bad design. It is meant only to
|
86
|
+
be a tool to use in improving the overall design of a Ruby on Rails system and
|
87
|
+
moreover to provide a standard implementation for an emerging best practice.
|
88
|
+
|
89
|
+
Development
|
90
|
+
-----------
|
91
|
+
|
92
|
+
### Running specs
|
93
|
+
|
94
|
+
`DecentExposure` has been developed with the philosophy that Ruby developers shouldn't
|
95
|
+
force their choice in RubyGems package managers on people consuming their code.
|
96
|
+
As a side effect of that, if you attempt to run the specs on this application,
|
97
|
+
you might get `no such file to load` errors. The short answer is that you can
|
98
|
+
`export RUBYOPT='rubygems'` and be on about your way (for the long answer, see
|
99
|
+
Ryan Tomayko's [excellent
|
100
|
+
treatise](http://tomayko.com/writings/require-rubygems-antipattern) on the
|
101
|
+
subject).
|
102
|
+
|
103
|
+
### Documentation TODO
|
104
|
+
|
105
|
+
* walk-through of an actual implementation (using an existing, popular OSS Rails
|
106
|
+
app as an example refactor).
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.2.0
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module DecentExposure
|
2
|
+
def expose(name, &block)
|
3
|
+
define_method name do
|
4
|
+
@_resources ||= {}
|
5
|
+
@_resources[name] ||= if block_given?
|
6
|
+
instance_eval(&block)
|
7
|
+
else
|
8
|
+
_class_for(name).find(params["#{name}_id"] || params['id'])
|
9
|
+
end
|
10
|
+
end
|
11
|
+
helper_method name
|
12
|
+
hide_action name
|
13
|
+
end
|
14
|
+
|
15
|
+
alias let expose
|
16
|
+
|
17
|
+
private
|
18
|
+
def _class_for(name)
|
19
|
+
name.to_s.classify.constantize
|
20
|
+
end
|
21
|
+
end
|
data/rails/init.rb
ADDED
data/spec/helper.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', 'helper')
|
2
|
+
|
3
|
+
class Quacker
|
4
|
+
extend DecentExposure
|
5
|
+
def self.helper_method(*args); end
|
6
|
+
def self.hide_action(*args); end
|
7
|
+
def self.find(*args); end
|
8
|
+
def memoizable(*args); args; end
|
9
|
+
def params; {'proxy_id' => 42}; end
|
10
|
+
expose(:proxy)
|
11
|
+
expose(:quack){ memoizable('quack!') }
|
12
|
+
end
|
13
|
+
|
14
|
+
describe DecentExposure do
|
15
|
+
context "classes extending DecentExposure" do
|
16
|
+
it "respond to :expose" do
|
17
|
+
Quacker.respond_to?(:expose).should be_true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context "#expose" do
|
22
|
+
let(:instance){ Quacker.new }
|
23
|
+
|
24
|
+
it "creates a method with the given name" do
|
25
|
+
Quacker.new.methods.map{|m| m.to_s}.should include('quack')
|
26
|
+
end
|
27
|
+
|
28
|
+
it "prevents the method from being a callable action" do
|
29
|
+
Quacker.expects(:hide_action).with(:blerg)
|
30
|
+
Quacker.class_eval do
|
31
|
+
expose(:blerg){ 'ehm' }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
it "declares the method as a helper method" do
|
36
|
+
Quacker.stubs(:hide_action)
|
37
|
+
Quacker.expects(:helper_method).with(:blarg)
|
38
|
+
Quacker.class_eval do
|
39
|
+
expose(:blarg){ 'uhm' }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
it "returns the value of the method" do
|
44
|
+
instance.quack.should == %w(quack!)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "memoizes the value of the created method" do
|
48
|
+
instance.expects(:memoizable).once.returns('value')
|
49
|
+
instance.quack
|
50
|
+
instance.quack
|
51
|
+
end
|
52
|
+
|
53
|
+
context "when no block is given" do
|
54
|
+
before do
|
55
|
+
instance.stubs(:_class_for).returns(Quacker)
|
56
|
+
end
|
57
|
+
it "attempts to guess the class of the resource to expose" do
|
58
|
+
instance.expects(:_class_for).with(:proxy).returns(Quacker)
|
59
|
+
instance.proxy
|
60
|
+
end
|
61
|
+
it "calls find with {resource}_id on the resources class" do
|
62
|
+
Quacker.expects(:find).with(42)
|
63
|
+
instance.proxy
|
64
|
+
end
|
65
|
+
context "and there is no {resource}_id" do
|
66
|
+
before do
|
67
|
+
Quacker.class_eval do
|
68
|
+
def params; {'id' => 24}; end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
it "calls find with params[:id] on the resources class" do
|
72
|
+
Quacker.expects(:find).with(24)
|
73
|
+
instance.proxy
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe '#_class_for' do
|
80
|
+
let(:name){ 'quacker' }
|
81
|
+
let(:classified_name){ 'Quacker' }
|
82
|
+
before do
|
83
|
+
name.stubs(:to_s => name, :classify => classified_name)
|
84
|
+
classified_name.stubs(:constantize => Quacker)
|
85
|
+
end
|
86
|
+
it 'retrieves a string representation of the class name' do
|
87
|
+
name.expects(:classify).returns(classified_name)
|
88
|
+
Quacker.send(:_class_for,name)
|
89
|
+
end
|
90
|
+
it 'returns the string representation of the name as a constant' do
|
91
|
+
classified_name.expects(:constantize)
|
92
|
+
Quacker.send(:_class_for,name)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
metadata
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: decent_exposure
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Stephen Caudill
|
8
|
+
- Jon Larkowski
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2010-01-31 00:00:00 -05:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: rspec
|
18
|
+
type: :development
|
19
|
+
version_requirement:
|
20
|
+
version_requirements: !ruby/object:Gem::Requirement
|
21
|
+
requirements:
|
22
|
+
- - ">="
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: 1.2.9
|
25
|
+
version:
|
26
|
+
description: "\n DecentExposure helps you program to an interface, rather than an implementation\n in your Rails controllers. The fact of the matter is that sharing state\n via instance variables in controllers promotes close coupling with views.\n DecentExposure gives you a declarative manner of exposing an interface to the\n state that controllers contain and thereby decreasing coupling and\n improving your testability and overall design.\n "
|
27
|
+
email: scaudill@gmail.com
|
28
|
+
executables: []
|
29
|
+
|
30
|
+
extensions: []
|
31
|
+
|
32
|
+
extra_rdoc_files:
|
33
|
+
- README.md
|
34
|
+
files:
|
35
|
+
- COPYING
|
36
|
+
- README.md
|
37
|
+
- VERSION
|
38
|
+
- lib/decent_exposure.rb
|
39
|
+
- rails/init.rb
|
40
|
+
has_rdoc: true
|
41
|
+
homepage: http://github.com/voxdolo/decent_exposure
|
42
|
+
licenses: []
|
43
|
+
|
44
|
+
post_install_message:
|
45
|
+
rdoc_options:
|
46
|
+
- --charset=UTF-8
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: "0"
|
54
|
+
version:
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: "0"
|
60
|
+
version:
|
61
|
+
requirements: []
|
62
|
+
|
63
|
+
rubyforge_project:
|
64
|
+
rubygems_version: 1.3.5
|
65
|
+
signing_key:
|
66
|
+
specification_version: 3
|
67
|
+
summary: A helper for creating declarative interfaces in controllers
|
68
|
+
test_files:
|
69
|
+
- spec/helper.rb
|
70
|
+
- spec/lib/decent_exposure_spec.rb
|