decent_exposure 1.0.2 → 2.0.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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')