decent_exposure 1.0.0.rc3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,7 +2,11 @@ module DecentExposure
2
2
  module DefaultExposure
3
3
  def self.included(klass)
4
4
  klass.extend(DecentExposure)
5
- klass.superclass_delegating_accessor(:_default_exposure)
5
+ if klass.respond_to?(:class_attribute)
6
+ klass.class_attribute(:_default_exposure)
7
+ else
8
+ klass.superclass_delegating_accessor(:_default_exposure)
9
+ end
6
10
  klass.default_exposure do |name|
7
11
  collection = name.to_s.pluralize
8
12
  if respond_to?(collection) && collection != name.to_s && send(collection).respond_to?(:scoped)
@@ -0,0 +1,3 @@
1
+ module DecentExposure #:nodoc
2
+ VERSION = "1.0.0"
3
+ end
metadata CHANGED
@@ -1,13 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: decent_exposure
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: true
4
+ prerelease: false
5
5
  segments:
6
6
  - 1
7
7
  - 0
8
8
  - 0
9
- - rc3
10
- version: 1.0.0.rc3
9
+ version: 1.0.0
11
10
  platform: ruby
12
11
  authors:
13
12
  - Stephen Caudill
@@ -16,7 +15,7 @@ autorequire:
16
15
  bindir: bin
17
16
  cert_chain: []
18
17
 
19
- date: 2010-12-21 00:00:00 -05:00
18
+ date: 2011-01-04 00:00:00 -05:00
20
19
  default_executable:
21
20
  dependencies:
22
21
  - !ruby/object:Gem::Dependency
@@ -25,13 +24,13 @@ dependencies:
25
24
  requirement: &id001 !ruby/object:Gem::Requirement
26
25
  none: false
27
26
  requirements:
28
- - - ">="
27
+ - - ~>
29
28
  - !ruby/object:Gem::Version
30
29
  segments:
31
- - 1
30
+ - 2
32
31
  - 3
33
32
  - 0
34
- version: 1.3.0
33
+ version: 2.3.0
35
34
  type: :development
36
35
  version_requirements: *id001
37
36
  - !ruby/object:Gem::Dependency
@@ -40,7 +39,7 @@ dependencies:
40
39
  requirement: &id002 !ruby/object:Gem::Requirement
41
40
  none: false
42
41
  requirements:
43
- - - ">="
42
+ - - ~>
44
43
  - !ruby/object:Gem::Version
45
44
  segments:
46
45
  - 0
@@ -62,27 +61,34 @@ dependencies:
62
61
  version: "0"
63
62
  type: :development
64
63
  version_requirements: *id003
65
- 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 "
64
+ - !ruby/object:Gem::Dependency
65
+ name: activesupport
66
+ prerelease: false
67
+ requirement: &id004 !ruby/object:Gem::Requirement
68
+ none: false
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ segments:
73
+ - 0
74
+ version: "0"
75
+ type: :development
76
+ version_requirements: *id004
77
+ 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 "
66
78
  email: scaudill@gmail.com
67
79
  executables: []
68
80
 
69
81
  extensions: []
70
82
 
71
- extra_rdoc_files:
72
- - README.html
73
- - README.md
83
+ extra_rdoc_files: []
84
+
74
85
  files:
75
- - COPYING
76
- - README.md
77
- - VERSION
78
- - lib/decent_exposure.rb
79
86
  - lib/decent_exposure/default_exposure.rb
80
87
  - lib/decent_exposure/railtie.rb
81
- - rails/init.rb
82
- - README.html
83
- - spec/helper.rb
84
- - spec/lib/decent_exposure_spec.rb
85
- - spec/lib/rails_integration_spec.rb
88
+ - lib/decent_exposure/version.rb
89
+ - lib/decent_exposure.rb
90
+ - README.md
91
+ - COPYING
86
92
  has_rdoc: true
87
93
  homepage: http://github.com/voxdolo/decent_exposure
88
94
  licenses: []
@@ -103,13 +109,13 @@ required_ruby_version: !ruby/object:Gem::Requirement
103
109
  required_rubygems_version: !ruby/object:Gem::Requirement
104
110
  none: false
105
111
  requirements:
106
- - - ">"
112
+ - - ">="
107
113
  - !ruby/object:Gem::Version
108
114
  segments:
109
115
  - 1
110
116
  - 3
111
- - 1
112
- version: 1.3.1
117
+ - 6
118
+ version: 1.3.6
113
119
  requirements: []
114
120
 
115
121
  rubyforge_project:
@@ -117,7 +123,5 @@ rubygems_version: 1.3.7
117
123
  signing_key:
118
124
  specification_version: 3
119
125
  summary: A helper for creating declarative interfaces in controllers
120
- test_files:
121
- - spec/helper.rb
122
- - spec/lib/decent_exposure_spec.rb
123
- - spec/lib/rails_integration_spec.rb
126
+ test_files: []
127
+
@@ -1,119 +0,0 @@
1
- <html>
2
- <head>
3
- <style type='text/css'>
4
- body{ width:50em; }
5
- pre{ margin-left:2em; }
6
- </style>
7
- </head>
8
- <body>
9
- <h1 id='decentexposure'>DecentExposure</h1>
10
-
11
- <p>DecentExposure helps you program to an interface, rather than an implementation in your Rails controllers.</p>
12
-
13
- <p>Sharing state via instance variables in controllers promotes close coupling with views. DecentExposure gives you a declarative manner of exposing an interface to the state that controllers contain, thereby decreasing coupling and improving your testability and overall design.</p>
14
-
15
- <h2 id='installation'>Installation</h2>
16
-
17
- <pre><code>gem install decent_exposure</code></pre>
18
-
19
- <p>Configure your Rails 2.X application to use it:</p>
20
-
21
- <p>In <code>config/environment.rb</code>:</p>
22
-
23
- <pre><code>config.gem &#39;decent_exposure&#39;</code></pre>
24
-
25
- <p>When used in Rails 3:</p>
26
-
27
- <p>In <code>Gemfile</code>:</p>
28
-
29
- <pre><code>gem &#39;decent_exposure&#39;</code></pre>
30
-
31
- <h2 id='examples'>Examples</h2>
32
-
33
- <h3 id='a_full_example'>A full example</h3>
34
-
35
- <p>The wiki has a full example of <a href='http://github.com/voxdolo/decent_exposure/wiki/Examples'>converting a classic-style Rails controller</a>.</p>
36
-
37
- <h3 id='in_your_controllers'>In your controllers</h3>
38
-
39
- <p>When no block is given, <code>expose</code> attempts to determine which resource you want to acquire. When <code>params</code> contains <code>:category_id</code> or <code>:id</code>, a call to:</p>
40
-
41
- <pre><code>expose(:category)</code></pre>
42
-
43
- <p>Would result in the following <code>ActiveRecord#find</code>:</p>
44
-
45
- <pre><code>Category.find(params[:category_id]||params[:id])</code></pre>
46
-
47
- <p>As the example shows, the symbol passed is used to guess the class name of the object (and potentially the <code>params</code> key to find it with) you want an instance of.</p>
48
-
49
- <p>Should <code>params</code> not contain an identifiable <code>id</code>, a call to:</p>
50
-
51
- <pre><code>expose(:category)</code></pre>
52
-
53
- <p>Will instead attempt to build a new instance of the object like so:</p>
54
-
55
- <pre><code>Category.new(params[:category])</code></pre>
56
-
57
- <p>If you define a collection with a pluralized name of the singular resource, <code>decent_exposure</code> will attempt to use it to scope its calls from. Let&#8217;s take the following scenario:</p>
58
-
59
- <pre><code>class ProductsController &lt; ApplicationController
60
- expose(:category)
61
- expose(:products) { category.products }
62
- expose(:product)
63
- end</code></pre>
64
-
65
- <p>The <code>product</code> resource would scope from the <code>products</code> collection via a fully-expanded query equivalent to this:</p>
66
-
67
- <pre><code>Category.find(params[:category_id]).products.find(params[:id])</code></pre>
68
-
69
- <p>or (depending on the contents of the <code>params</code> hash) this:</p>
70
-
71
- <pre><code>Category.find(params[:category_id]).products.new(params[:product])</code></pre>
72
-
73
- <p>In the straightforward case, the three exposed resources above provide for access to both the primary and ancestor resources in a way usable across all 7 actions in a typicall Rails-style RESTful controller.</p>
74
-
75
- <h4 id='a_note_on_style'>A Note on Style</h4>
76
-
77
- <p>When the code has become complex enough to surpass a single line (and is not appropriate to extract into a model method), use the <code>do...end</code> style of block:</p>
78
-
79
- <pre><code>expose(:associated_products) do
80
- product.associated.tap do |associated_products|
81
- present(associated_products, :with =&gt; AssociatedProductPresenter)
82
- end
83
- end</code></pre>
84
-
85
- <h3 id='in_your_views'>In your views</h3>
86
-
87
- <p>Use the product of those assignments like you would an instance variable or any other method you might normally have access to:</p>
88
-
89
- <pre><code>= render bread_crumbs_for(category)
90
- %h3#product_title= product.title
91
- = render product
92
- %h3 Associated Products
93
- %ul
94
- - associated_products.each do |associated_product|
95
- %li= link_to(associated_product.title,product_path(associated_product))</code></pre>
96
-
97
- <h3 id='custom_defaults'>Custom defaults</h3>
98
-
99
- <p>DecentExposure provides opinionated default logic when <code>expose</code> is invoked without a block. It&#8217;s possible, however, to override this with custom default logic by passing a block accepting a single argument to the <code>default_exposure</code> method inside of a controller. The argument will be the string or symbol passed in to the <code>expose</code> call.</p>
100
-
101
- <pre><code>class MyController &lt; ApplicationController
102
- default_exposure do |name|
103
- ObjectCache.load(name.to_s)
104
- end
105
- end</code></pre>
106
-
107
- <p>The given block will be invoked in the context of a controller instance. It is possible to provide a custom default for a descendant class without disturbing its ancestor classes in an inheritance heirachy.</p>
108
-
109
- <h2 id='beware'>Beware</h2>
110
-
111
- <p>This is a simple tool, which provides a solitary solution. It must be used in conjunction with solid design approaches (&#8220;Program to an interface, not an implementation.&#8221;) and accepted best practices (e.g. Fat Model, Skinny Controller). In itself, it won&#8217;t heal a bad design. It is meant only to be a tool to use in improving the overall design of a Ruby on Rails system and moreover to provide a standard implementation for an emerging best practice.</p>
112
-
113
- <h2 id='development'>Development</h2>
114
-
115
- <h3 id='running_specs'>Running specs</h3>
116
-
117
- <p><code>DecentExposure</code> has been developed with the philosophy that Ruby developers shouldn&#8217;t force their choice in RubyGems package managers on people consuming their code. As a side effect of that, if you attempt to run the specs on this application, you might get <code>no such file to load</code> errors. The short answer is that you can <code>export RUBYOPT=&#39;rubygems&#39;</code> and be on about your way (for the long answer, see Ryan Tomayko&#8217;s <a href='http://tomayko.com/writings/require-rubygems-antipattern'>excellent treatise</a> on the subject).</p>
118
- </body>
119
- </html>
data/VERSION DELETED
@@ -1 +0,0 @@
1
- 1.0.0.rc3
@@ -1 +0,0 @@
1
- Kernel.load File.join(File.dirname(__FILE__), '..', 'init.rb')
@@ -1,2 +0,0 @@
1
- require 'mocha'
2
- require File.join(File.dirname(__FILE__), '..', 'lib', 'decent_exposure')
@@ -1,79 +0,0 @@
1
- require 'helper'
2
-
3
- describe DecentExposure do
4
-
5
- class Controller
6
- extend DecentExposure
7
- def self.helper_method(*args); end
8
- def self.hide_action(*args); end
9
- def memoizable(arg); arg; end
10
- end
11
-
12
- context 'classes extending DecentExposure' do
13
- subject { Controller }
14
- specify { should respond_to(:expose) }
15
- specify { should respond_to(:default_exposure) }
16
- end
17
-
18
- context '.expose' do
19
- let(:controller) { Class.new(Controller){ expose(:resource) } }
20
- let(:instance) { controller.new }
21
-
22
- it 'creates a method with the given name' do
23
- instance.should respond_to(:resource)
24
- end
25
-
26
- it 'prevents the method from being a callable action' do
27
- controller.expects(:hide_action).with(:resources)
28
- controller.class_eval { expose(:resources) }
29
- end
30
-
31
- it 'declares the method as a helper method' do
32
- controller.expects(:helper_method).with(:resources)
33
- controller.class_eval { expose(:resources) }
34
- end
35
-
36
- context 'custom exposures' do
37
- before do
38
- controller.class_eval do
39
- expose(:resource) { memoizable("I'm a resource!") }
40
- end
41
- end
42
-
43
- it 'returns the result of the exposed block from the method' do
44
- instance.resource.should == "I'm a resource!"
45
- end
46
-
47
- it 'memoizes the value of the created method' do
48
- instance.expects(:memoizable).once.returns('value')
49
- 2.times { instance.resource }
50
- end
51
- end
52
-
53
- context '.default_exposure' do
54
- let(:defaulted_controller) { Class.new(Controller) }
55
- let(:instance) { defaulted_controller.new }
56
- context 'when the default_exposure is overridden' do
57
- before do
58
- defaulted_controller.class_eval do
59
- default_exposure { 'default value' }
60
- expose(:default)
61
- end
62
- end
63
- it 'uses the overridden default_exposure' do
64
- instance.default.should == 'default value'
65
- end
66
- end
67
-
68
- context 'with named arguments' do
69
- it 'makes the named arguments available' do
70
- defaulted_controller.class_eval do
71
- default_exposure {|name| "I got: '#{name}'"}
72
- expose :default
73
- end
74
- instance.default.should == "I got: 'default'"
75
- end
76
- end
77
- end
78
- end
79
- end
@@ -1,155 +0,0 @@
1
- require 'helper'
2
- require 'action_controller'
3
- require 'decent_exposure/railtie'
4
- DecentExposure::Railtie.insert
5
-
6
- class Resource
7
- def self.scoped(opts); self; end
8
- def self.find(*args); end
9
- def initialize(*args); end
10
- end
11
-
12
- class Equipment
13
- def self.scoped(opts); self; end
14
- def self.find(*args); end
15
- def initialize(*args); end
16
- end
17
-
18
- describe "Rails' integration:", DecentExposure do
19
- let(:controller) { Class.new(ActionController::Base) }
20
- let(:instance) { controller.new }
21
- let(:request) { mock(:get? => true) }
22
- let(:params) { HashWithIndifferentAccess.new(:resource_id => 42) }
23
-
24
- before do
25
- controller.expose(:resource)
26
- instance.stubs(:request).returns(request)
27
- end
28
-
29
- context '.expose' do
30
- it 'is available to ActionController::Base' do
31
- ActionController::Base.should respond_to(:expose)
32
- end
33
- end
34
-
35
- context 'within descendant controllers' do
36
- let(:resource_controller) { Class.new(ActionController::Base) }
37
- let(:instance) { resource_controller.new }
38
-
39
- before do
40
- instance.stubs(:request).returns(request)
41
- instance.stubs(:params).returns(params)
42
- resource_controller.expose :resource
43
- end
44
-
45
- it 'inherits the default_exposure' do
46
- Resource.stubs(:find).returns('resource')
47
- instance.resource.should == 'resource'
48
- end
49
-
50
- it 'allows you to override the default_exposure' do
51
- resource_controller.class_eval do
52
- default_exposure {|name| name.to_s}
53
- expose :overridden
54
- end
55
- instance.overridden.should == 'overridden'
56
- end
57
-
58
- it 'does not override the default in ancestors' do
59
- Resource.stubs(:find).returns('preserved')
60
- instance.resource.should == 'preserved'
61
- end
62
- end
63
-
64
- context '.default_exposure' do
65
- before do
66
- instance.stubs(:params).returns(params)
67
- end
68
-
69
- it 'is available to ActionController::Base' do
70
- ActionController::Base.should respond_to(:default_exposure)
71
- end
72
-
73
- context 'when no collection method exists' do
74
- it 'operates directly on the class' do
75
- Resource.should_receive(:find)
76
- instance.resource
77
- end
78
- end
79
-
80
- context 'when a collection method exists' do
81
- let(:controller){ Class.new(ActionController::Base) }
82
- let(:instance){ controller.new }
83
-
84
- before { class Person < Resource; end }
85
-
86
- context 'and the collection can be scoped' do
87
- let(:collection){ mock(:scoped => [self]) }
88
-
89
- before{ controller.expose(:person) }
90
-
91
- it 'uses the existing collection method' do
92
- instance.stubs(:people).returns(collection)
93
- collection.expects(:new)
94
- instance.person
95
- end
96
- end
97
- context 'when the collection can not be scoped' do
98
- let(:collection){ mock }
99
-
100
- before{ controller.expose(:person) }
101
-
102
- it 'falls back to the singularized constant' do
103
- instance.stubs(:people).returns(collection)
104
- Person.expects(:new)
105
- instance.person
106
- end
107
- end
108
- end
109
-
110
- context 'when either :resource_id or :id are present in params' do
111
- it "calls find with params[:resource_id] on the resource's class" do
112
- Resource.expects(:find).with(42)
113
- instance.resource
114
- end
115
-
116
- context 'when there is no :resource_id in params' do
117
- before { instance.stubs(:params).returns({:id => 73}) }
118
-
119
- it "calls find with params[:id] on the resource's class" do
120
- Resource.expects(:find).with(73)
121
- instance.resource
122
- end
123
- end
124
- end
125
- context 'when there are no ids in params' do
126
- before do
127
- instance.stubs(:params).returns({:resource => {:name => 'bob'}})
128
- end
129
-
130
- it 'calls new with params[:resouce_name]' do
131
- Resource.expects(:new).with({:name => 'bob'})
132
- instance.resource
133
- end
134
- end
135
- end
136
- end
137
- describe "Rails' integration:", DecentExposure do
138
- let(:controller) { Class.new(ActionController::Base) }
139
- let(:instance) { controller.new }
140
- let(:request) { mock(:get? => true) }
141
- let(:params) { HashWithIndifferentAccess.new(:resource_id => 42) }
142
-
143
- before do
144
- controller.expose(:equipment)
145
- instance.stubs(:request).returns(request)
146
- instance.stubs(:params).returns(params)
147
- end
148
- context 'when collection name is same as resource name' do
149
- it 'does not create a collection method' do
150
- instance.equipment
151
- instance.should respond_to(:equipment)
152
- instance.should_not respond_to(:equipments)
153
- end
154
- end
155
- end