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 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,2 @@
1
+ require 'decent_exposure/railtie'
2
+ DecentExposure::Railtie.insert
@@ -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,3 @@
1
+ module DecentExposure #:nodoc
2
+ VERSION = "1.0.1"
3
+ 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
+