super_resources 1.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- data/.DS_Store +0 -0
- data/.gitignore +20 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +215 -0
- data/Rakefile +1 -0
- data/lib/super_resources/actions.rb +49 -0
- data/lib/super_resources/cancan.rb +15 -0
- data/lib/super_resources/controller.rb +12 -0
- data/lib/super_resources/has_scope.rb +9 -0
- data/lib/super_resources/nesting.rb +110 -0
- data/lib/super_resources/resources.rb +88 -0
- data/lib/super_resources/url_helpers.rb +61 -0
- data/lib/super_resources/version.rb +3 -0
- data/lib/super_resources.rb +11 -0
- data/spec/controllers/actions_nested_spec.rb +115 -0
- data/spec/controllers/actions_spec.rb +142 -0
- data/spec/controllers/actions_with_blocks_spec.rb +173 -0
- data/spec/controllers/paths_spec.rb +114 -0
- data/spec/controllers/redirects_spec.rb +39 -0
- data/spec/controllers/resources_adapted_spec.rb +192 -0
- data/spec/controllers/resources_nested_spec.rb +37 -0
- data/spec/controllers/resources_spec.rb +35 -0
- data/spec/dummy/.DS_Store +0 -0
- data/spec/dummy/README.rdoc +261 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/assets/javascripts/application.js +15 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/controllers/adapted_resources_controller.rb +24 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/controllers/child_resources_controller.rb +2 -0
- data/spec/dummy/app/controllers/grandparent_resources_controller.rb +13 -0
- data/spec/dummy/app/controllers/great_grandparent_resources_controller.rb +46 -0
- data/spec/dummy/app/controllers/my_adapted_resources_controller.rb +8 -0
- data/spec/dummy/app/controllers/parent_resources_controller.rb +2 -0
- data/spec/dummy/app/controllers/simple_resources_controller.rb +2 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/mailers/.gitkeep +0 -0
- data/spec/dummy/app/models/.gitkeep +0 -0
- data/spec/dummy/app/models/adapted_resource.rb +3 -0
- data/spec/dummy/app/models/child_resource.rb +5 -0
- data/spec/dummy/app/models/grandparent_resource.rb +6 -0
- data/spec/dummy/app/models/great_grandparent_resource.rb +3 -0
- data/spec/dummy/app/models/my_adapted_resource.rb +2 -0
- data/spec/dummy/app/models/parent_resource.rb +6 -0
- data/spec/dummy/app/models/simple_resource.rb +2 -0
- data/spec/dummy/app/views/application/edit.html.erb +1 -0
- data/spec/dummy/app/views/application/index.html.erb +1 -0
- data/spec/dummy/app/views/application/new.html.erb +1 -0
- data/spec/dummy/app/views/application/show.html.erb +1 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config/application.rb +64 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +37 -0
- data/spec/dummy/config/environments/production.rb +67 -0
- data/spec/dummy/config/environments/test.rb +37 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +15 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +17 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/db/migrate/20130117225232_create_resources.rb +27 -0
- data/spec/dummy/db/schema.rb +40 -0
- data/spec/dummy/lib/assets/.gitkeep +0 -0
- data/spec/dummy/log/.gitkeep +0 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +25 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/spec_helper.rb +39 -0
- data/super_resources.gemspec +21 -0
- metadata +201 -0
data/.DS_Store
ADDED
Binary file
|
data/.gitignore
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
|
19
|
+
spec/dummy/log/*.log
|
20
|
+
spec/dummy/db/*.sqlite3
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012, 2013 Habanero Software Pty Limited
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,215 @@
|
|
1
|
+
# SuperResources
|
2
|
+
|
3
|
+
SuperResources DRYs up your controller code by abstracting your controller's strandard RESTful actions and by providing
|
4
|
+
standard helpers to access the controller's target resource(s) in a consistent way across all controllers. More than that,
|
5
|
+
SuperResources exploits the application's routes to provide simplified path helpers for nested resources.
|
6
|
+
|
7
|
+
With SuperResources, in the great majority of common REST situations, you can use the same resource helpers and path helpers,
|
8
|
+
regardless of the the specific type of resource or how it is nested, even if the resource is nested under many other resources.
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Add this line to your application's Gemfile:
|
13
|
+
|
14
|
+
gem 'super_resources'
|
15
|
+
|
16
|
+
And then execute:
|
17
|
+
|
18
|
+
bundle install
|
19
|
+
|
20
|
+
Or install it yourself as:
|
21
|
+
|
22
|
+
gem install super_resources
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
To gain all the standard RESTful actions, just `include SuperResources::Controller` in your controller:
|
27
|
+
|
28
|
+
class OrganizationUnitsController < ApplicationController
|
29
|
+
include SuperResources::Controller
|
30
|
+
end
|
31
|
+
|
32
|
+
### Resource Helper Methods
|
33
|
+
|
34
|
+
SuperResources provides helper methods that you can use directly in the controller, views or helper methods:
|
35
|
+
|
36
|
+
resource
|
37
|
+
collection
|
38
|
+
|
39
|
+
For member actions, `resource` answers a single object that the RESTFUL is operating on.
|
40
|
+
For collection actions (i.e `index1), `collection` answer a scoped collection of objects.
|
41
|
+
|
42
|
+
SuperResources does away with specifically named instance variables, such as those created by standard scaffolds. For example, in the example above, SuperResources does not give you `@organization_unit` or `@organization_units` for free. You won't need them in the common cases. Using `resource` and `collection` in every controller makes for easier coding and maintenance.
|
43
|
+
|
44
|
+
### Path Helper Methods
|
45
|
+
|
46
|
+
SuperResources provides a set of vastly simplified path helpers.
|
47
|
+
|
48
|
+
collection_path #=> path to the resource's index action
|
49
|
+
resource_path(object = resource)
|
50
|
+
new_resource_path
|
51
|
+
edit_resource_path(object = resource)
|
52
|
+
|
53
|
+
Note that the helper methods that require an input argument, assumes the current resource by default, so you don't have to pass it in.
|
54
|
+
|
55
|
+
#### These Helper Methods Work Even When the Resource is Nested
|
56
|
+
|
57
|
+
Because SuperResources uses the metadata created by your routes declarations to work out whether the resource has been nested and if so, automatically uses the nesting objects to build complete paths. This relieves you of passing in an array to the path helper.
|
58
|
+
|
59
|
+
With this feature alone, SuperResources cleans up your code and makes it more reusable. For example, the same code can be used when you
|
60
|
+
have a resource that is nested inside multiple other objects. SuperResources dynamically works out the nesting that applies in each case.
|
61
|
+
|
62
|
+
## Nested Resource
|
63
|
+
|
64
|
+
Let's face it: deailing with nested resources has always been a pain. All that fiddling about, getting the path names and inputs right. Chane the nesting and you have to go through and chaneg all your path calls too. Then, if you have a resource that can be nested within multiple other resources, such as when you have an associative object and you want to navigate to it from any of it associations, the permutations become very complex.
|
65
|
+
|
66
|
+
It should be much easier, especially when you take routes into account. If I have an action that is matched to this route:
|
67
|
+
|
68
|
+
/notes/:note_id/pages/:id
|
69
|
+
|
70
|
+
then shouldn't I have paths available that can work out the nesting context, so that I don't hard code it?
|
71
|
+
|
72
|
+
SuperResources does this. For example, including SuperResources in `PagesController` will allow me to simply call,
|
73
|
+
for example, `edit_resource_path` and the code has been actioned through the above route, the nesting with a Note identified by `:note_id` will be assumed. Even better, if the same code is actioned by another route with different nesting, SuperResources will get that right too.
|
74
|
+
|
75
|
+
If you need, such as when you want to link 'outside the nest' as it were, you still have `note_page_path(note, page)` available to you.
|
76
|
+
|
77
|
+
### Parent Helper Method
|
78
|
+
|
79
|
+
Sometimes you want to use the object that is nesting your resource, such as when you want to customize a redirect. SuperResources provides
|
80
|
+
`parent` to answer that object. For example given, the following route:
|
81
|
+
|
82
|
+
/notes/:note_id/pages/:id
|
83
|
+
|
84
|
+
|
85
|
+
calling `parent` will answer a Note object with an id of `:note_id`.
|
86
|
+
|
87
|
+
For deeper nests, the immediately nested object is always answered by `parent`. For example, given this route:
|
88
|
+
|
89
|
+
/authors/:author_id/notes/:note_id/pages/:id
|
90
|
+
|
91
|
+
calling `parent` will still answer a Note object with an id of `:note_id`.
|
92
|
+
|
93
|
+
### Accessing Route Objects
|
94
|
+
|
95
|
+
Any nested route implies a component hierarchy of objects. SuperResources allows you to access these objects bye a convenient name.
|
96
|
+
|
97
|
+
For example, given this route:
|
98
|
+
|
99
|
+
/authors/:author_id/notes/:note_id/pages/:id
|
100
|
+
|
101
|
+
you can make these calls:
|
102
|
+
|
103
|
+
author #=> Author with an id of :author_id
|
104
|
+
note #=> Note with an id of :note_id
|
105
|
+
page #=> Page with an id of :id
|
106
|
+
|
107
|
+
An example of a place where you will want these, is in a layout template that presents information about the nesting objects. For example, you may want to show a page inside an author layout template. The author layout template will present information about the author. In this case, the layout will be used in the context of `PagesController`, so you can't refer to `resource` in the layout, since it will answer the page, not the author. You need some way to arbitrarily refer to the author. Being able to call `author` provides this.
|
108
|
+
|
109
|
+
## Adapting and Customising
|
110
|
+
|
111
|
+
### Resource Class
|
112
|
+
|
113
|
+
SuperResources derives the class of the target resource from the controller name. For example, `OrganizationUnitsController` operates, by default, on resources of the class `OrganizationUnit`. If you want to change this, redefine hotspot method `resource_class`, for example:
|
114
|
+
|
115
|
+
class TeamsController < ApplicationController
|
116
|
+
include SuperResources::Controller
|
117
|
+
|
118
|
+
protected
|
119
|
+
|
120
|
+
def resource_class
|
121
|
+
OrganizationUnit
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
### Resource Helper Methods
|
126
|
+
|
127
|
+
Yes, you can adapt these to suit your needs.
|
128
|
+
|
129
|
+
Both `resource` and `collection` accept a block, which SuperResources uses to stitch into its internal implementation, so tht any subsequent calls to these helpers uses your implementation.
|
130
|
+
The block for `resource` shall answer a single instance of the resource class and the block for `collection` shall answer an array of instances of the resource class.
|
131
|
+
|
132
|
+
For example, if you wanted 'TeamsController#collection' to return only those teams that the current user has joined, you would write an implementation of `collection` that calls its super, passing a block the evalautes to the right collection:
|
133
|
+
|
134
|
+
def collection
|
135
|
+
super { resource_class.joined_by(current_user) }
|
136
|
+
end
|
137
|
+
|
138
|
+
If that looks strange, bear in mind it's been designed so that you don't need to know how SuperResources internally uses your preferred implementation.
|
139
|
+
|
140
|
+
### Finder Method
|
141
|
+
|
142
|
+
Before being able to use the result of `resource`, SuperResources may need to find it. The canonical way to do this is to do:
|
143
|
+
|
144
|
+
resource_class.find(:params[:id])
|
145
|
+
|
146
|
+
While SuperResource uses the `find` method as the default, you can choose another finder method by redefining `finder_method`. For example:
|
147
|
+
|
148
|
+
class PagesController < ApplicationController
|
149
|
+
|
150
|
+
protected
|
151
|
+
|
152
|
+
def finder_method
|
153
|
+
:find_by_position
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
### Builder Method
|
158
|
+
|
159
|
+
SuperResources extracts the construction of a new resource into the `build_resource` method. If you need to create a new resource, say in a special action, use 'build_resource' so that SuperResource can also keep track of it (for example, make sure that `resource' answers the built object).
|
160
|
+
|
161
|
+
If you need to do specialized work for the build, pass the a block to `build_resource` that evaluates to an object with the state you need. For example:
|
162
|
+
|
163
|
+
build_resource do
|
164
|
+
resource_class.new do |p|
|
165
|
+
# initialize the state here
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
More commonly, you would redefine the whole method so that it always behaves the same way for its enclosing controller:
|
170
|
+
|
171
|
+
def build_resource
|
172
|
+
super do
|
173
|
+
resource_class.new do |p|
|
174
|
+
# initialize the state here
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
### Redefining Actions
|
180
|
+
|
181
|
+
Yes, you can. All actions defined by SuperResources use responders and accept parameters to pass to `respond_with`, so customizing these parameters is a common adaptation.
|
182
|
+
|
183
|
+
For example, suppose that after creating a comment, you want to redirect to an index of comments that apply to the same parent. Adapt teh action like this:
|
184
|
+
|
185
|
+
def create
|
186
|
+
super :location => polymorphic_url([ parent, :comments ])
|
187
|
+
end
|
188
|
+
|
189
|
+
Anything you can pass to `respond_with`, you can pass to he super call, including a block.
|
190
|
+
|
191
|
+
You could, of course, completely redefine an action:
|
192
|
+
|
193
|
+
def new
|
194
|
+
# knock yourself out
|
195
|
+
end
|
196
|
+
|
197
|
+
### Defining Actions
|
198
|
+
|
199
|
+
Just do it. Declare them in your controller and match them in routes. All the SuperResources helpers are still available to you.
|
200
|
+
|
201
|
+
## Acknowledgments
|
202
|
+
|
203
|
+
SuperResources would never have happened without InheritedResources [https://github.com/josevalim/inherited_resources] existing first.
|
204
|
+
We preferred the idea of abstracting and extracting RESTful actions out of all our controllers and we're not so keen on scaffolds generating
|
205
|
+
un-DRY code. We used InheritedResources in a production deployed application [meetlinkshare.com], gained some experience and decided we wanted an even DRYer tool.
|
206
|
+
|
207
|
+
The basic mechanics of SuperResources was hacked out during Rails Camp 12 in Tasmania, Australia and was subsequently applied to MeetLinkShare.
|
208
|
+
|
209
|
+
## Contributing
|
210
|
+
|
211
|
+
1. Fork it
|
212
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
213
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
214
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
215
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module SuperResources
|
2
|
+
module Actions
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
respond_to :html
|
7
|
+
end
|
8
|
+
|
9
|
+
def index(options = {}, &block)
|
10
|
+
respond_with(*(with_chain(collection) << options), &block)
|
11
|
+
end
|
12
|
+
|
13
|
+
def show(options = {}, &block)
|
14
|
+
respond_with(*(with_chain(resource) << options), &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def new(options = {}, &block)
|
18
|
+
respond_with(*(with_chain(build_resource) << options), &block)
|
19
|
+
end
|
20
|
+
|
21
|
+
def edit(options = {}, &block)
|
22
|
+
respond_with(*(with_chain(resource) << options), &block)
|
23
|
+
end
|
24
|
+
|
25
|
+
def create(options = {}, &block)
|
26
|
+
if create_resource(resource_params)
|
27
|
+
options[:location] ||= resource_url
|
28
|
+
end
|
29
|
+
|
30
|
+
respond_with(*(with_chain(resource) << options), &block)
|
31
|
+
end
|
32
|
+
|
33
|
+
def update(options = {}, &block)
|
34
|
+
if update_resource(resource_params)
|
35
|
+
options[:location] ||= resource_url
|
36
|
+
end
|
37
|
+
|
38
|
+
respond_with(*(with_chain(resource) << options), &block)
|
39
|
+
end
|
40
|
+
|
41
|
+
def destroy(options = {}, &block)
|
42
|
+
if destroy_resource
|
43
|
+
options[:location] ||= collection_url
|
44
|
+
end
|
45
|
+
|
46
|
+
respond_with(*(with_chain(resource) << options), &block)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module SuperResources
|
2
|
+
module Nesting
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
include Resources
|
5
|
+
|
6
|
+
included do
|
7
|
+
helper_method :association_chain, :with_chain, :method_missing, :respond_to?
|
8
|
+
end
|
9
|
+
|
10
|
+
def respond_to?(m, *args)
|
11
|
+
m.in?(symbols_for_association_chain) ? true : super
|
12
|
+
end
|
13
|
+
|
14
|
+
protected
|
15
|
+
|
16
|
+
def method_missing(m, *args, &block)
|
17
|
+
case
|
18
|
+
when m == resource_params_name
|
19
|
+
resource
|
20
|
+
when i = symbols_for_association_chain.index(m)
|
21
|
+
association_chain[i]
|
22
|
+
else
|
23
|
+
super
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def collection(&block)
|
28
|
+
if block_given?
|
29
|
+
@collection = yield
|
30
|
+
else
|
31
|
+
@collection ||= end_of_association_chain
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def resource(&block)
|
36
|
+
if block_given?
|
37
|
+
@resource = yield
|
38
|
+
else
|
39
|
+
@resource ||= end_of_association_chain.send(finder_method, params[:id])
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def build_resource(&block)
|
44
|
+
if block_given?
|
45
|
+
@resource = yield
|
46
|
+
else
|
47
|
+
@resource ||= end_of_association_chain.build(resource_params)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def nested?
|
52
|
+
association_chain.any?
|
53
|
+
end
|
54
|
+
|
55
|
+
def parent
|
56
|
+
association_chain.last
|
57
|
+
end
|
58
|
+
|
59
|
+
def end_of_association_chain
|
60
|
+
nested? ? parent.send(resource_collection_name) : resource_class.scoped
|
61
|
+
end
|
62
|
+
|
63
|
+
def association_chain
|
64
|
+
@association_chain ||=
|
65
|
+
symbols_for_association_chain.inject([]) do |chain, symbol|
|
66
|
+
chain << chain_link(symbol, chain.last)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def chain_link(symbol, previous_link)
|
71
|
+
link = if previous_link
|
72
|
+
previous_link.send(symbol.to_s.pluralize.to_sym)
|
73
|
+
else
|
74
|
+
symbol.to_s.classify.safe_constantize
|
75
|
+
end
|
76
|
+
|
77
|
+
link.find(params[:"#{symbol}_id"])
|
78
|
+
end
|
79
|
+
|
80
|
+
def symbols_for_association_chain
|
81
|
+
@symbols_for_association_chain ||=
|
82
|
+
route.parts \
|
83
|
+
.select { |p| p.to_s =~ %r(_id$) } \
|
84
|
+
.map { |p| p.to_s.gsub(/_id$/, '').to_sym }
|
85
|
+
@symbols_for_association_chain
|
86
|
+
end
|
87
|
+
|
88
|
+
def with_chain(object)
|
89
|
+
association_chain + [ object ]
|
90
|
+
end
|
91
|
+
|
92
|
+
def route
|
93
|
+
@route ||= begin
|
94
|
+
routes.formatter.send(:match_route, nil, path_parameters) do |route|
|
95
|
+
# TODO: don't assume the first route is good, validate!
|
96
|
+
# TODO: don't use break
|
97
|
+
break route
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def routes
|
103
|
+
request.env['action_dispatch.routes']
|
104
|
+
end
|
105
|
+
|
106
|
+
def path_parameters
|
107
|
+
request.env['action_dispatch.request.path_parameters'].symbolize_keys
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module SuperResources
|
2
|
+
module Resources
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
helper_method :collection, :collection=, :resource, :resource=, :resource_class, :parent, :nested?
|
7
|
+
end
|
8
|
+
|
9
|
+
protected
|
10
|
+
|
11
|
+
def resource_class
|
12
|
+
controller_name.classify.singularize.safe_constantize
|
13
|
+
end
|
14
|
+
|
15
|
+
def resource_instance_name
|
16
|
+
controller_name.singularize.to_sym
|
17
|
+
end
|
18
|
+
|
19
|
+
def resource_params_name
|
20
|
+
resource_instance_name
|
21
|
+
end
|
22
|
+
|
23
|
+
def resource_collection_name
|
24
|
+
controller_name.to_sym
|
25
|
+
end
|
26
|
+
|
27
|
+
def collection(&block)
|
28
|
+
if block_given?
|
29
|
+
@collection = yield
|
30
|
+
else
|
31
|
+
@collection ||= resource_class.scoped
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def collection=(c)
|
36
|
+
@collection = c
|
37
|
+
end
|
38
|
+
|
39
|
+
def resource(&block)
|
40
|
+
if block_given?
|
41
|
+
@resource = yield
|
42
|
+
else
|
43
|
+
@resource ||= resource_class.send(finder_method, params[:id])
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def resource=(r)
|
48
|
+
@resource = r
|
49
|
+
end
|
50
|
+
|
51
|
+
def finder_method
|
52
|
+
:find
|
53
|
+
end
|
54
|
+
|
55
|
+
def nested?
|
56
|
+
false
|
57
|
+
end
|
58
|
+
|
59
|
+
def parent
|
60
|
+
nil
|
61
|
+
end
|
62
|
+
|
63
|
+
def build_resource(&block)
|
64
|
+
if block_given?
|
65
|
+
@resource = yield
|
66
|
+
else
|
67
|
+
@resource ||= resource_class.new(resource_params)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def create_resource(attributes)
|
72
|
+
build_resource.attributes = attributes
|
73
|
+
resource.save
|
74
|
+
end
|
75
|
+
|
76
|
+
def update_resource(attributes)
|
77
|
+
resource.update_attributes(attributes)
|
78
|
+
end
|
79
|
+
|
80
|
+
def resource_params
|
81
|
+
params[resource_params_name] || {}
|
82
|
+
end
|
83
|
+
|
84
|
+
def destroy_resource
|
85
|
+
resource.destroy
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module SuperResources
|
2
|
+
module URLHelpers
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
helper_method :collection_path, :resource_path, :new_resource_path,
|
7
|
+
:edit_resource_path
|
8
|
+
end
|
9
|
+
|
10
|
+
protected
|
11
|
+
|
12
|
+
def full_association_chain_symbols
|
13
|
+
symbols_for_association_chain + [resource_instance_name]
|
14
|
+
end
|
15
|
+
|
16
|
+
def collection_route
|
17
|
+
chain = symbols_for_association_chain + [resource_collection_name]
|
18
|
+
:"#{chain.join('_')}_url"
|
19
|
+
end
|
20
|
+
|
21
|
+
def resource_route
|
22
|
+
:"#{full_association_chain_symbols.join('_')}_url"
|
23
|
+
end
|
24
|
+
|
25
|
+
def new_resource_route
|
26
|
+
:"new_#{resource_route}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def edit_resource_route
|
30
|
+
:"edit_#{resource_route}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def collection_url
|
34
|
+
send(collection_route, *association_chain)
|
35
|
+
end
|
36
|
+
|
37
|
+
alias_method :collection_path, :collection_url
|
38
|
+
|
39
|
+
def resource_url(object = resource)
|
40
|
+
if object.persisted?
|
41
|
+
send(resource_route, *(association_chain + [object]))
|
42
|
+
else
|
43
|
+
collection_url # probably a new action
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
alias_method :resource_path, :resource_url
|
48
|
+
|
49
|
+
def new_resource_url
|
50
|
+
send(new_resource_route, *association_chain)
|
51
|
+
end
|
52
|
+
|
53
|
+
alias_method :new_resource_path, :new_resource_url
|
54
|
+
|
55
|
+
def edit_resource_url(object = resource)
|
56
|
+
send(edit_resource_route, *(association_chain + [object]))
|
57
|
+
end
|
58
|
+
|
59
|
+
alias_method :edit_resource_path, :edit_resource_url
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module SuperResources
|
2
|
+
autoload :Actions, "super_resources/actions"
|
3
|
+
autoload :Controller, "super_resources/controller"
|
4
|
+
autoload :Nesting, "super_resources/nesting"
|
5
|
+
autoload :Resources, "super_resources/resources"
|
6
|
+
autoload :URLHelpers, "super_resources/url_helpers"
|
7
|
+
autoload :Version, "super_resources/version"
|
8
|
+
|
9
|
+
autoload :HasScope, "super_resources/has_scope"
|
10
|
+
autoload :Cancan, "super_resources/cancan"
|
11
|
+
end
|