decent_exposure 1.0.2 → 2.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,163 +1,356 @@
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)
1
+ ## Mad Decent
2
+
3
+ Rails controllers are the sweaty armpit of every rails app. This is due, in
4
+ large part, to the fact that they expose their instance variables directly to
5
+ their views. This means that your instance variables are your interface... and
6
+ that you've broken encapsulation. Instance variables are meant to be private,
7
+ for Science's sake!
8
+
9
+ What `decent_exposure` proposes is that you go from this:
10
+
11
+ ```ruby
12
+ class Controller
13
+ def new
14
+ @person = Person.new(params[:person])
15
+ end
16
+
17
+ def create
18
+ @person = Person.new(params[:person])
19
+ if @person.save
20
+ redirect_to(@person)
21
+ else
22
+ render :new
73
23
  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
24
+ end
25
+
26
+ def edit
27
+ @person = Person.find(params[:id])
28
+ end
29
+
30
+ def update
31
+ @person = Person.find(params[:id])
32
+ if @person.update_attributes(params[:person])
33
+ redirect_to(@person)
34
+ else
35
+ render :edit
97
36
  end
37
+ end
38
+ end
39
+ ```
98
40
 
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
41
+ To something like this:
113
42
 
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.
43
+ ```ruby
44
+ class Controller
45
+ expose(:person)
119
46
 
120
- class MyController < ApplicationController
121
- default_exposure do |name|
122
- ObjectCache.load(name.to_s)
123
- end
47
+ def create
48
+ if person.save
49
+ redirect_to(person)
50
+ else
51
+ render :new
124
52
  end
53
+ end
125
54
 
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>
55
+ def update
56
+ if person.save
57
+ redirect_to(person)
58
+ else
59
+ render :edit
60
+ end
61
+ end
62
+ end
63
+ ```
64
+
65
+ `decent_exposure` makes it easy to define named methods that are made available
66
+ to your views and which memoize the resultant values. It also tucks away the
67
+ details of the common fetching, initializing and updating of resources and
68
+ their parameters.
69
+
70
+ That's neat and all, but the real advantage comes when it's time to refactor
71
+ (because you've encapsulated now). What happens when you need to scope your
72
+ `Person` resource from a `Company`? Which implementation isolates those changes
73
+ better? In that particular example, `decent_exposure` goes one step farther and
74
+ will handle the scoping for you (with a smidge of configuration) while still
75
+ handling all that repetitive initialization, as we'll see next.
76
+
77
+ Even if you decide not to use `decent_exposure`, do yourself a favor and stop
78
+ using instance variables in your views. Your code will be cleaner and easier to
79
+ refactor as a result. If you want to learn more about his approach, I've
80
+ expanded on my thoughts in the article [A Diatribe on Maintaining State][1].
81
+
82
+ ## Environmental Awareness
83
+
84
+ Well, no it won't lessen your carbon footprint, but it does take a lot of
85
+ cues from what's going on around it...
86
+
87
+ `decent_exposure` will build the requested object in one of a couple of ways
88
+ depending on what the `params` make available to it. At its simplest, when an
89
+ `id` is present in the `params` hash, `decent_exposure` will attempt to find a
90
+ record. In absence of `params[:id]` `decent_exposure` will try to build a new
91
+ record.
92
+
93
+ Once the object has been obtained, it attempts to set the attributes of the
94
+ resulting object. Thus, a newly minted `person` instance will get any
95
+ attributes set that've been passed along in `params[:person]`. When you
96
+ interact with `person` in your create action, just call save on it and handle
97
+ the valid/invalid branch. Let's revisit our previous example:
98
+
99
+ ```ruby
100
+ class Controller
101
+ expose(:person)
102
+
103
+ def create
104
+ if person.save
105
+ redirect_to(person)
106
+ else
107
+ render :new
108
+ end
109
+ end
110
+ end
111
+ ```
112
+
113
+ Behind the scenes, `decent_exposure` has essentially done this:
114
+
115
+ ```ruby
116
+ person.attributes = params[:person]
117
+ ```
118
+
119
+ In Rails, this assignment is actually a merge with the current attributes and
120
+ it marks attributes as dirty as you would expect. This is why you're simply
121
+ able to call `save` on the `person` instance in the create action, rather than
122
+ the typical `update_attributes(params[:person])`.
123
+
124
+ **An Aside**
125
+
126
+ Did you notice there's no `new` action? Yeah, that's because we don't need it.
127
+ More often than not actions that respond to `GET` requests are just setting up
128
+ state. Since we've declared an interface to our state and made it available to
129
+ the view (a.k.a. the place where we actually want to access it), we just let
130
+ Rails do it's magic and render the `new` view, lazily evaluating `person` when
131
+ we actually need it.
132
+
133
+ **A Caveat**
134
+
135
+ Rails conveniently responds with a 404 if you get a record not found in the
136
+ controller. Since we don't find the object until we're in the view in this
137
+ paradigm, we get an ugly `ActionView::TemplateError` instead. If this is
138
+ problematic for you, consider using the `expose!` method to circumvent lazy
139
+ evaluation and eagerly evaluate whilst still in the controller.
140
+
141
+ ## Usage
142
+
143
+ In an effort to make the examples below a bit less magical, we'll offer a
144
+ simplified explanation for how the exposed resource would be queried for
145
+ (assuming you are using `ActiveRecord`).
146
+
147
+ ### Obtaining an instance of an object:
148
+
149
+ ```ruby
150
+ expose(:person)
151
+ ```
152
+
153
+ **Query Explanation**
154
+
155
+ <table>
156
+ <tr>
157
+ <td><code>id</code> present?</td>
158
+ <td>Query</td>
159
+ </tr>
160
+ <tr>
161
+ <td><code>true</code></td>
162
+ <td><code>Person.find(params[:id])</code></td>
163
+ </tr>
164
+ <tr>
165
+ <td><code>false</code></td>
166
+ <td><code>Person.new(params[:person])</code></td>
167
+ </tr>
168
+ </table>
169
+
170
+ ### Obtaining a collection of objects
171
+
172
+ ```ruby
173
+ expose(:people)
174
+ ```
175
+
176
+ **Query Explanation**
177
+
178
+ <table>
179
+ <tr>
180
+ <td>Query</td>
181
+ </tr>
182
+ <tr>
183
+ <td><code>Person.scoped</code></td>
184
+ </tr>
185
+ </table>
186
+
187
+ ### Scoping your object queries
188
+
189
+ Want to scope your queries to ensure object hierarchy? `decent_exposure`
190
+ automatically scopes singular forms of a resource from a plural form where
191
+ they're defined:
192
+
193
+ ```ruby
194
+ expose(:people)
195
+ expose(:person)
196
+ ```
197
+
198
+ **Query Explanation**
199
+
200
+ <table>
201
+ <tr>
202
+ <td><code>id</code> present?</td>
203
+ <td>Query</td>
204
+ </tr>
205
+ <tr>
206
+ <td><code>true</code></td>
207
+ <td><code>Person.scoped.find(params[:id])</code></td>
208
+ </tr>
209
+ <tr>
210
+ <td><code>false</code></td>
211
+ <td><code>Person.scoped.new(params[:person])</code></td>
212
+ </tr>
213
+ </table>
214
+
215
+ How about a more realistic scenario where the object hierarchy specifies
216
+ something useful, like only finding people in a given company:
217
+
218
+ ```ruby
219
+ expose(:company)
220
+ expose(:people, ancestor: :company)
221
+ expose(:person)
222
+ ```
223
+
224
+ **Query Explanation**
225
+
226
+ <table>
227
+ <tr>
228
+ <td>person <code>id</code> present?</td>
229
+ <td>Query</td>
230
+ </tr>
231
+ <tr>
232
+ <td><code>true</code></td>
233
+ <td><code>Company.find(params[:company_id]).people.find(params[:id])</code></td>
234
+ </tr>
235
+ <tr>
236
+ <td><code>false</code></td>
237
+ <td><code>Company.find(params[:company_id]).people.new(params[:person])</code></td>
238
+ </tr>
239
+ </table>
240
+
241
+ ### Further configuration
242
+
243
+ `decent_exposure` is a configurable beast. Let's take a look at some of the
244
+ things you can do:
245
+
246
+ **Specify the model name:**
247
+
248
+ ```ruby
249
+ expose(:company, model: :enterprisey_company)
250
+ ```
251
+
252
+ **Specify the parameter accessor method:**
253
+
254
+ ```ruby
255
+ expose(:company, params: :company_params)
256
+ ```
257
+
258
+ **Specify the finder method:**
259
+
260
+ ```ruby
261
+ expose(:article, finder: :find_by_slug)
262
+ ```
263
+
264
+ **Specify the parameter key to use to fetch the object:**
265
+
266
+ ```ruby
267
+ expose(:article, finder_parameter: :slug)
268
+ ```
269
+
270
+ ### Getting your hands dirty
271
+
272
+ While we try to make things as easy for you as possible, sometimes you just
273
+ need to go off the beaten path. For those times, `expose` takes a block which
274
+ it lazily evaluates and returns the result of when called. So for instance:
275
+
276
+ ```ruby
277
+ expose(:environment) { Rails.env }
278
+ ```
279
+
280
+ This block is evaluated and the memoized result is returned whenever you call
281
+ `environment`.
282
+
283
+ ### Custom strategies
284
+
285
+ For the times when custom behavior is needed for resource finding,
286
+ `decent_exposure` provides a base class for extending. For example, if
287
+ scoping a resource from `current_user` is not and option, but you'd like
288
+ to verify a resource's relationship to the `current_user`, you can use a
289
+ custom strategy like the following:
290
+
291
+ ```ruby
292
+ class VerifiableStrategy < DecentExposure::Strategy
293
+ delegate :current_user, :to => :controller
294
+
295
+ def resource
296
+ instance = model.find(params[:id])
297
+ if current_user != instance.user
298
+ raise ActiveRecord::RecordNotFound
299
+ end
300
+ instance
301
+ end
302
+ end
303
+ ```
304
+
305
+ You would then use your custom strategy in your controller:
306
+
307
+ ```ruby
308
+ expose(:post, strategy: VerifiableStrategy)
309
+ ```
310
+
311
+ The API only necessitates you to define `resource`, but provides some
312
+ common helpers to access common things, such as the `params` hash. For
313
+ everything else, you can delegate to `controller`, which is the same as
314
+ `self` in the context of a normal controller action.
315
+
316
+ ### Customizing your exposures
317
+
318
+ For most things, you'll be able to pass a few configuration options and get
319
+ the desired behavior. For changes you want to affect every call to `expose` in
320
+ a controller or controllers inheriting from it (e.g. `ApplicationController`,
321
+ if you need to change the behavior for all your controllers), you can define
322
+ an `decent_configuration` block:
323
+
324
+ ```ruby
325
+ class ApplicationController < ActionController::Base
326
+ decent_configuration do
327
+ strategy MongoidStrategy
328
+ end
329
+ end
330
+ ```
331
+
332
+ A `decent_configuration` block without a `:name` argument is considered the
333
+ "default" configuration for that controller (and it's ancestors). All things
334
+ considered, you probably only want to change the strategy in a default.
335
+ Nonetheless, you can pass any configuration option you can to an individual
336
+ exposure to the `decent_configuration` block.
337
+
338
+ If you don't want a specific configuration to affect every exposure in the
339
+ given controller, you can give it a name like so:
340
+
341
+ ```ruby
342
+ class ArticleController < ApplicationController
343
+ decent_configuration(:sluggable) do
344
+ finder :find_by_slug
345
+ finder_parameter :slug
346
+ end
347
+ end
348
+ ```
349
+
350
+ And opt into it like so:
351
+
352
+ ```ruby
353
+ expose(:article, config: :sluggable)
354
+ ```
355
+
356
+ [1]: http://blog.voxdolo.me/a-diatribe-on-maintaining-state.html
@@ -1,34 +1,5 @@
1
- require 'decent_exposure/railtie'
1
+ require 'decent_exposure/expose'
2
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.fetch(name) do
24
- @_resources[name] = if block_given?
25
- instance_eval(&block)
26
- else
27
- instance_exec(name, &closured_exposure)
28
- end
29
- end
30
- end
31
- helper_method name
32
- hide_action name
33
- end
3
+ ActiveSupport.on_load(:action_controller) do
4
+ extend DecentExposure::Expose
34
5
  end
@@ -0,0 +1,68 @@
1
+ require 'decent_exposure/strategy'
2
+ require 'active_support/core_ext/module/delegation'
3
+
4
+ module DecentExposure
5
+ class ActiveRecordStrategy < Strategy
6
+ delegate :plural?, :parameter, :to => :inflector
7
+
8
+ def collection
9
+ inflector.plural.to_sym
10
+ end
11
+
12
+ def scope
13
+ if options[:ancestor]
14
+ ancestor_scope
15
+ else
16
+ default_scope
17
+ end
18
+ end
19
+
20
+ def ancestor_scope
21
+ if plural?
22
+ controller.send(options[:ancestor]).send(inflector.plural)
23
+ else
24
+ controller.send(options[:ancestor])
25
+ end
26
+ end
27
+
28
+ def default_scope
29
+ if controller.respond_to?(collection) && !plural?
30
+ controller.send(collection)
31
+ else
32
+ model
33
+ end
34
+ end
35
+
36
+ def finder
37
+ options[:finder] || :find
38
+ end
39
+
40
+ def collection_resource
41
+ scope.scoped
42
+ end
43
+
44
+ def id
45
+ params[parameter] || params[finder_parameter]
46
+ end
47
+
48
+ def finder_parameter
49
+ options[:finder_parameter] || :id
50
+ end
51
+
52
+ def singular_resource
53
+ if id
54
+ scope.send(finder, id)
55
+ else
56
+ scope.new
57
+ end
58
+ end
59
+
60
+ def resource
61
+ if plural?
62
+ collection_resource
63
+ else
64
+ singular_resource
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,30 @@
1
+ require 'decent_exposure/active_record_strategy'
2
+
3
+ module DecentExposure
4
+ class ActiveRecordWithEagerAttributesStrategy < ActiveRecordStrategy
5
+ delegate :get?, :to => :request
6
+
7
+ def singular?
8
+ !plural?
9
+ end
10
+
11
+ def attributes
12
+ params[inflector.singular]
13
+ end
14
+
15
+ def assign_attributes?
16
+ return false unless attributes && singular?
17
+ !get? || new_record?
18
+ end
19
+
20
+ def new_record?
21
+ !id
22
+ end
23
+
24
+ def resource
25
+ super.tap do |r|
26
+ r.attributes = attributes if assign_attributes?
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,19 @@
1
+ module DecentExposure
2
+ class Configuration
3
+ def initialize(&block)
4
+ instance_exec(&block) if block_given?
5
+ end
6
+
7
+ def merge(other)
8
+ options.merge(other)
9
+ end
10
+
11
+ def options
12
+ @options ||= {}
13
+ end
14
+
15
+ def method_missing(key,value)
16
+ self.options[key] = value
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,60 @@
1
+ require 'decent_exposure/strategizer'
2
+ require 'decent_exposure/configuration'
3
+
4
+ module DecentExposure
5
+ module Expose
6
+ def self.extended(base)
7
+ base.class_eval do
8
+ class_attribute :_default_exposure
9
+ def _resources
10
+ @_resources ||= {}
11
+ end
12
+ hide_action :_resources
13
+ end
14
+ end
15
+
16
+ def _exposures
17
+ @_exposures ||= {}
18
+ end
19
+
20
+ def _decent_configurations
21
+ @_decent_configurations ||= Hash.new(Configuration.new)
22
+ end
23
+
24
+ def decent_configuration(name=:default,&block)
25
+ _decent_configurations[name] = Configuration.new(&block)
26
+ end
27
+
28
+ def default_exposure(&block)
29
+ warn "[DEPRECATION] `default_exposure` is deprecated, and will " \
30
+ "be removed in DecentExposure 2.1 without a replacement. Please " \
31
+ "use a custom strategy instead.\n" \
32
+ "#{caller.first}"
33
+ self._default_exposure = block
34
+ end
35
+
36
+ def expose!(*args, &block)
37
+ set_callback(:process_action, :before, args.first)
38
+ expose(*args, &block)
39
+ end
40
+
41
+ def expose(name, options={:default_exposure => _default_exposure}, &block)
42
+ config = options[:config] || :default
43
+ options = _decent_configurations[config].merge(options)
44
+
45
+ _exposures[name] = exposure = Strategizer.new(name, options, &block).strategy
46
+
47
+ define_method(name) do
48
+ return _resources[name] if _resources.has_key?(name)
49
+ _resources[name] = exposure.call(self)
50
+ end
51
+
52
+ define_method("#{name}=") do |value|
53
+ _resources[name] = value
54
+ end
55
+
56
+ helper_method name
57
+ hide_action name
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,17 @@
1
+ require 'decent_exposure/inflector'
2
+
3
+ module DecentExposure
4
+ class Exposure
5
+ attr_accessor :inflector, :strategy, :options
6
+
7
+ def initialize(name, strategy, options)
8
+ self.strategy = strategy
9
+ self.options = options
10
+ self.inflector = DecentExposure::Inflector.new(name)
11
+ end
12
+
13
+ def call(controller)
14
+ strategy.new(controller, inflector, options).resource
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,34 @@
1
+ require 'active_support/inflector'
2
+ require 'active_support/core_ext/string/inflections'
3
+
4
+ module DecentExposure
5
+ class Inflector
6
+ attr_reader :original
7
+ alias name original
8
+
9
+ def initialize(name)
10
+ @original = name.to_s
11
+ end
12
+
13
+ def constant
14
+ original.classify.constantize
15
+ end
16
+
17
+ def parameter
18
+ singular + "_id"
19
+ end
20
+
21
+ def singular
22
+ original.parameterize
23
+ end
24
+
25
+ def plural
26
+ original.pluralize
27
+ end
28
+ alias collection plural
29
+
30
+ def plural?
31
+ plural == original
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,60 @@
1
+ require 'decent_exposure/exposure'
2
+ require 'decent_exposure/active_record_with_eager_attributes_strategy'
3
+
4
+ module DecentExposure
5
+ class Strategizer
6
+ attr_accessor :name, :block, :default_exposure, :options, :custom_strategy_class
7
+
8
+ def initialize(name, options={})
9
+ self.name = name
10
+ self.default_exposure = options.delete(:default_exposure)
11
+ self.custom_strategy_class = options.delete(:strategy)
12
+ self.options = options
13
+ self.block = Proc.new if block_given?
14
+ end
15
+
16
+ def strategy
17
+ [block_strategy,
18
+ default_exposure_strategy,
19
+ exposure_strategy].detect(&applicable)
20
+ end
21
+
22
+ def model
23
+ options[:model] || name
24
+ end
25
+
26
+ private
27
+
28
+ def applicable
29
+ lambda { |s| s }
30
+ end
31
+
32
+ def exposure_strategy
33
+ Exposure.new(model, exposure_strategy_class, options)
34
+ end
35
+
36
+ def block_strategy
37
+ BlockStrategy.new(block) if block
38
+ end
39
+
40
+ def default_exposure_strategy
41
+ DefaultStrategy.new(name, default_exposure) if default_exposure
42
+ end
43
+
44
+ def exposure_strategy_class
45
+ custom_strategy_class || ActiveRecordWithEagerAttributesStrategy
46
+ end
47
+ end
48
+
49
+ DefaultStrategy = Struct.new(:name, :block) do
50
+ def call(controller)
51
+ controller.instance_exec(name, &block)
52
+ end
53
+ end
54
+
55
+ BlockStrategy = Struct.new(:block) do
56
+ def call(controller)
57
+ controller.instance_eval(&block)
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,37 @@
1
+ module DecentExposure
2
+ class Strategy
3
+ attr_reader :controller, :inflector, :options
4
+
5
+ def initialize(controller, inflector, options={})
6
+ @controller, @inflector, @options = controller, inflector, options
7
+ end
8
+
9
+ def name
10
+ inflector.name
11
+ end
12
+
13
+ def resource
14
+ raise 'Implement in subclass'
15
+ end
16
+
17
+ protected
18
+
19
+ def model
20
+ inflector.constant
21
+ end
22
+
23
+ def params
24
+ controller.send(params_method)
25
+ end
26
+
27
+ def request
28
+ controller.request
29
+ end
30
+
31
+ private
32
+
33
+ def params_method
34
+ options[:params] || :params
35
+ end
36
+ end
37
+ end
@@ -1,3 +1,3 @@
1
1
  module DecentExposure #:nodoc
2
- VERSION = "1.0.2"
2
+ VERSION = "2.0.0.rc1"
3
3
  end
metadata CHANGED
@@ -1,72 +1,62 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: decent_exposure
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
5
- prerelease:
4
+ version: 2.0.0.rc1
5
+ prerelease: 6
6
6
  platform: ruby
7
7
  authors:
8
8
  - Stephen Caudill
9
9
  - Jon Larkowski
10
+ - Joshua Davey
10
11
  autorequire:
11
12
  bindir: bin
12
13
  cert_chain: []
13
- date: 2012-03-08 00:00:00.000000000Z
14
+ date: 2012-07-27 00:00:00.000000000Z
14
15
  dependencies:
15
16
  - !ruby/object:Gem::Dependency
16
17
  name: rspec
17
- requirement: &70182141020280 !ruby/object:Gem::Requirement
18
+ requirement: &70110875340260 !ruby/object:Gem::Requirement
18
19
  none: false
19
20
  requirements:
20
21
  - - ~>
21
22
  - !ruby/object:Gem::Version
22
- version: 2.5.0
23
+ version: '2.7'
23
24
  type: :development
24
25
  prerelease: false
25
- version_requirements: *70182141020280
26
+ version_requirements: *70110875340260
26
27
  - !ruby/object:Gem::Dependency
27
- name: mocha
28
- requirement: &70182141019820 !ruby/object:Gem::Requirement
28
+ name: rspec-rails
29
+ requirement: &70110875338840 !ruby/object:Gem::Requirement
29
30
  none: false
30
31
  requirements:
31
32
  - - ~>
32
33
  - !ruby/object:Gem::Version
33
- version: 0.9.12
34
+ version: '2.7'
34
35
  type: :development
35
36
  prerelease: false
36
- version_requirements: *70182141019820
37
- - !ruby/object:Gem::Dependency
38
- name: ruby-debug19
39
- requirement: &70182141019360 !ruby/object:Gem::Requirement
40
- none: false
41
- requirements:
42
- - - ~>
43
- - !ruby/object:Gem::Version
44
- version: 0.11.6
45
- type: :development
46
- prerelease: false
47
- version_requirements: *70182141019360
37
+ version_requirements: *70110875338840
48
38
  - !ruby/object:Gem::Dependency
49
39
  name: actionpack
50
- requirement: &70182141018980 !ruby/object:Gem::Requirement
40
+ requirement: &70110875338380 !ruby/object:Gem::Requirement
51
41
  none: false
52
42
  requirements:
53
- - - ! '>='
43
+ - - ~>
54
44
  - !ruby/object:Gem::Version
55
- version: '0'
45
+ version: '3.1'
56
46
  type: :development
57
47
  prerelease: false
58
- version_requirements: *70182141018980
48
+ version_requirements: *70110875338380
59
49
  - !ruby/object:Gem::Dependency
60
50
  name: activesupport
61
- requirement: &70182141018520 !ruby/object:Gem::Requirement
51
+ requirement: &70110875337880 !ruby/object:Gem::Requirement
62
52
  none: false
63
53
  requirements:
64
- - - ! '>='
54
+ - - ~>
65
55
  - !ruby/object:Gem::Version
66
- version: '0'
56
+ version: '3.1'
67
57
  type: :development
68
58
  prerelease: false
69
- version_requirements: *70182141018520
59
+ version_requirements: *70110875337880
70
60
  description: ! "\n DecentExposure helps you program to an interface, rather than
71
61
  an\n implementation in your Rails controllers. The fact of the matter is that\n
72
62
  \ sharing state via instance variables in controllers promotes close coupling\n
@@ -78,14 +68,17 @@ executables: []
78
68
  extensions: []
79
69
  extra_rdoc_files: []
80
70
  files:
81
- - lib/decent_exposure/default_exposure.rb
82
- - lib/decent_exposure/railtie.rb
71
+ - lib/decent_exposure/active_record_strategy.rb
72
+ - lib/decent_exposure/active_record_with_eager_attributes_strategy.rb
73
+ - lib/decent_exposure/configuration.rb
74
+ - lib/decent_exposure/expose.rb
75
+ - lib/decent_exposure/exposure.rb
76
+ - lib/decent_exposure/inflector.rb
77
+ - lib/decent_exposure/strategizer.rb
78
+ - lib/decent_exposure/strategy.rb
83
79
  - lib/decent_exposure/version.rb
84
80
  - lib/decent_exposure.rb
85
81
  - README.md
86
- - COPYING
87
- - init.rb
88
- - rails/init.rb
89
82
  homepage: http://github.com/voxdolo/decent_exposure
90
83
  licenses: []
91
84
  post_install_message:
@@ -107,7 +100,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
107
100
  version: 1.3.6
108
101
  requirements: []
109
102
  rubyforge_project:
110
- rubygems_version: 1.8.6
103
+ rubygems_version: 1.8.10
111
104
  signing_key:
112
105
  specification_version: 3
113
106
  summary: A helper for creating declarative interfaces in controllers
data/COPYING DELETED
@@ -1,12 +0,0 @@
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/init.rb DELETED
@@ -1,2 +0,0 @@
1
- require 'decent_exposure/railtie'
2
- DecentExposure::Railtie.insert
@@ -1,28 +0,0 @@
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
- klass.default_exposure do |name|
11
- collection = name.to_s.pluralize
12
- if respond_to?(collection) && collection != name.to_s && send(collection).respond_to?(:scoped)
13
- proxy = send(collection)
14
- else
15
- proxy = name.to_s.classify.constantize
16
- end
17
-
18
- if id = params["#{name}_id"] || params[:id]
19
- proxy.find(id).tap do |r|
20
- r.attributes = params[name] unless request.get?
21
- end
22
- else
23
- proxy.new(params[name])
24
- end
25
- end
26
- end
27
- end
28
- end
@@ -1,20 +0,0 @@
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
- ActionController::Base.protected_instance_variables.push("@_resources")
18
- end
19
- end
20
- end
@@ -1 +0,0 @@
1
- Kernel.load File.join(File.dirname(__FILE__), '..', 'init.rb')