responder_controller 0.1.0
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/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +93 -0
- data/Rakefile +46 -0
- data/VERSION +1 -0
- data/lib/responder_controller.rb +278 -0
- data/responder_controller.gemspec +58 -0
- data/spec/responder_controller_spec.rb +511 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +13 -0
- metadata +102 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Phil Smith
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
= responder_controller
|
2
|
+
|
3
|
+
Rails 3 responders wrap up as much crud controller code as possible without finding or mutating
|
4
|
+
models on your behalf. This is a sensible cut-off for framework support, but it still leaves a
|
5
|
+
fair amount of duplicate code in crud controllers. App developers are free to abstract more.
|
6
|
+
|
7
|
+
This is me abstracting more for my own apps. If it's handy for you, go nuts.
|
8
|
+
|
9
|
+
== Example
|
10
|
+
|
11
|
+
# app/models/post.rb
|
12
|
+
class Post < ActiveRecord::Base
|
13
|
+
belongs_to :user
|
14
|
+
|
15
|
+
scope :authored_by, lambda { |user_id| where(:user_id => user_id) }
|
16
|
+
scope :recent, lambda { |count| order("updated_at DESC").limit(limit.to_i) }
|
17
|
+
end
|
18
|
+
|
19
|
+
# app/controllers/posts_controller.rb
|
20
|
+
class PostsController < ApplicationController
|
21
|
+
include ResponderController
|
22
|
+
|
23
|
+
respond_to :html, :xml, :json
|
24
|
+
|
25
|
+
# restrict to just the current user's posts
|
26
|
+
scope { |posts| posts.authored_by current_user.id }
|
27
|
+
end
|
28
|
+
|
29
|
+
# Client-side
|
30
|
+
GET /posts.html # renders Post.authored_by(your_id)
|
31
|
+
GET /posts.html?recent=10 # renders Post.authored_by(your_id).recent(10)
|
32
|
+
GET /posts/1.html # renders post 1 if it is authored by you, otherwise 404
|
33
|
+
PUT /posts/1.html # update same
|
34
|
+
DELETE /posts/1.html # or delete it
|
35
|
+
|
36
|
+
=== Point it at a different model class:
|
37
|
+
|
38
|
+
class ProfilesController < ApplicationController
|
39
|
+
include ResponderController
|
40
|
+
serves_model :user
|
41
|
+
end
|
42
|
+
|
43
|
+
=== Serve resources in a namespace:
|
44
|
+
|
45
|
+
class PostsController < ApplicationController
|
46
|
+
include ResponderController
|
47
|
+
responds_within 'my-blog'
|
48
|
+
end
|
49
|
+
|
50
|
+
# Client-side
|
51
|
+
GET /my-blog/posts.html
|
52
|
+
|
53
|
+
=== A nested resource, using blocks for dynamic behavior:
|
54
|
+
|
55
|
+
class CommentsController < ApplicationController
|
56
|
+
include ResponderController
|
57
|
+
|
58
|
+
# Only get comments for the identified post
|
59
|
+
scope do |comments|
|
60
|
+
comments.where :post_id => params[:post_id]
|
61
|
+
end
|
62
|
+
|
63
|
+
# Nest the comments under the post
|
64
|
+
responds_within do |comments|
|
65
|
+
Post.find(params[:post_id])
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
=== The same:
|
70
|
+
|
71
|
+
class CommentsController < ApplicationController
|
72
|
+
include ResponderController
|
73
|
+
children_of :post
|
74
|
+
end
|
75
|
+
|
76
|
+
== Note on Patches/Pull Requests
|
77
|
+
|
78
|
+
* Fork the project.
|
79
|
+
* Make your feature addition or bug fix.
|
80
|
+
* Add tests for it. This is important so I don't break it in a
|
81
|
+
future version unintentionally.
|
82
|
+
* Commit, do not mess with rakefile, version, or history.
|
83
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can
|
84
|
+
ignore when I pull)
|
85
|
+
* Send me a pull request. Bonus points for topic branches.
|
86
|
+
|
87
|
+
== Thanks
|
88
|
+
|
89
|
+
Thanks to SEOmoz (http://seomoz.org) for letting me build this at my desk in the afternoons instead of on the couch in the middle of the night ^_^.
|
90
|
+
|
91
|
+
== Copyright
|
92
|
+
|
93
|
+
Copyright (c) 2010 Phil Smith. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "responder_controller"
|
8
|
+
gem.summary = %Q{like resources_controller, but for rails 3 responders}
|
9
|
+
gem.description = %Q{Responders make crud controllers tiny, this wraps the rest.}
|
10
|
+
gem.email = "phil.h.smith@gmail.com"
|
11
|
+
gem.homepage = "http://github.com/phs/responder_controller"
|
12
|
+
gem.authors = ["Phil Smith"]
|
13
|
+
gem.add_dependency "activesupport", ">= 3.0.0.beta2"
|
14
|
+
gem.add_development_dependency "rspec", ">= 1.2.9"
|
15
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
16
|
+
end
|
17
|
+
Jeweler::GemcutterTasks.new
|
18
|
+
rescue LoadError
|
19
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
20
|
+
end
|
21
|
+
|
22
|
+
require 'spec/rake/spectask'
|
23
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
24
|
+
spec.libs << 'lib' << 'spec'
|
25
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
26
|
+
end
|
27
|
+
|
28
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
29
|
+
spec.libs << 'lib' << 'spec'
|
30
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
31
|
+
spec.rcov = true
|
32
|
+
end
|
33
|
+
|
34
|
+
task :spec => :check_dependencies
|
35
|
+
|
36
|
+
task :default => :spec
|
37
|
+
|
38
|
+
require 'rake/rdoctask'
|
39
|
+
Rake::RDocTask.new do |rdoc|
|
40
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
41
|
+
|
42
|
+
rdoc.rdoc_dir = 'rdoc'
|
43
|
+
rdoc.title = "responder_controller #{version}"
|
44
|
+
rdoc.rdoc_files.include('README*')
|
45
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
46
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,278 @@
|
|
1
|
+
require 'active_support/inflector'
|
2
|
+
require 'active_support/core_ext/string/inflections'
|
3
|
+
require 'active_support/core_ext/module/delegation'
|
4
|
+
|
5
|
+
module ResponderController
|
6
|
+
def self.included(mod)
|
7
|
+
mod.extend ClassMethods
|
8
|
+
mod.send :include, InstanceMethods
|
9
|
+
mod.send :include, Actions
|
10
|
+
end
|
11
|
+
|
12
|
+
# Configure how the controller finds and serves models of what flavor.
|
13
|
+
module ClassMethods
|
14
|
+
# The underscored, fully-qualified name of the served model class.
|
15
|
+
#
|
16
|
+
# By default, it is the underscored controller class name, without +_controller+.
|
17
|
+
def model_class_name
|
18
|
+
@model_class_name || name.underscore.gsub(/_controller$/, '').singularize
|
19
|
+
end
|
20
|
+
|
21
|
+
# Declare the underscored, fully-qualified name of the served model class.
|
22
|
+
#
|
23
|
+
# Modules are declared with separating slashes, such as in <tt>admin/setting</tt>. Strings
|
24
|
+
# or symbols are accepted, but other values (including actual classes) will raise
|
25
|
+
# <tt>ArgumentError</tt>s.
|
26
|
+
def serves_model(model_class_name)
|
27
|
+
unless model_class_name.is_a? String or model_class_name.is_a? Symbol
|
28
|
+
raise ArgumentError.new "Model must be a string or symbol"
|
29
|
+
end
|
30
|
+
|
31
|
+
@model_class_name = model_class_name.to_s
|
32
|
+
end
|
33
|
+
|
34
|
+
# Declare leading arguments ("responder context") for +respond_with+ calls.
|
35
|
+
#
|
36
|
+
# +respond_with+ creates urls from models. To avoid strongly coupling models to a url
|
37
|
+
# structure, it can take any number of leading parameters a la +polymorphic_url+.
|
38
|
+
# +responds_within+ declares these leading parameters, to be used on each +respond_with+ call.
|
39
|
+
#
|
40
|
+
# It takes either a varargs or a block, but not both. In
|
41
|
+
# InstanceMethods#respond_with_contextual, the blocks are called with +instance_exec+, taking
|
42
|
+
# the model (or models) as a parameter. They should return an array.
|
43
|
+
def responds_within(*args, &block)
|
44
|
+
if block and args.any?
|
45
|
+
raise ArgumentError.new("responds_within can take arguments or a block, but not both")
|
46
|
+
elsif block or args.any?
|
47
|
+
@responds_within ||= []
|
48
|
+
if not args.empty?
|
49
|
+
@responds_within.concat args
|
50
|
+
else
|
51
|
+
@responds_within << block
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
@responds_within || model_class_name.split('/')[0...-1].collect { |m| m.to_sym }
|
56
|
+
end
|
57
|
+
|
58
|
+
# The served model class, identified by #model_class_name.
|
59
|
+
def model_class
|
60
|
+
model_class_name.camelize.constantize
|
61
|
+
end
|
62
|
+
|
63
|
+
# Declare a class-level scope for model collections.
|
64
|
+
#
|
65
|
+
# The model class is expected to respond to +all+, returning an Enumerable of models.
|
66
|
+
# Declared scopes are applied to (and replace) this collection, suitable for active record
|
67
|
+
# scopes.
|
68
|
+
#
|
69
|
+
# It takes one of a string, symbol or block. Symbols and strings are called as methods on the
|
70
|
+
# collection without arguments. Blocks are called with +instance_exec+ taking the current,
|
71
|
+
# accumulated query and returning the new, scoped one.
|
72
|
+
def scope(*args, &block)
|
73
|
+
scope = args.first || block
|
74
|
+
|
75
|
+
scope = scope.to_sym if String === scope
|
76
|
+
unless scope.is_a? Symbol or scope.is_a? Proc
|
77
|
+
raise ArgumentError.new "Scope must be a string, symbol or block"
|
78
|
+
end
|
79
|
+
|
80
|
+
(@scopes ||= []) << scope
|
81
|
+
end
|
82
|
+
|
83
|
+
# The array of declared class-level scopes, as symbols or procs.
|
84
|
+
attr_reader :scopes
|
85
|
+
|
86
|
+
# Declare a (non-singleton) parent resource class.
|
87
|
+
#
|
88
|
+
# <tt>children_of 'accounts/user'</tt> implies a scope and some responder context. The scope
|
89
|
+
# performs an ActiveRecord <tt>where :user_id => params[:user_id]</tt>. The responder context
|
90
|
+
# is a call to <tt>#responds_within</tt> declaring the parent model's modules along with the
|
91
|
+
# parent itself, found with <tt>Accounts::User.find(params[:user_id])</tt>.
|
92
|
+
def children_of(parent_model_class_name)
|
93
|
+
parent_model_class_name = parent_model_class_name.to_s.underscore
|
94
|
+
|
95
|
+
parent_name_parts = parent_model_class_name.split('/')
|
96
|
+
parent_modules = parent_name_parts[0...-1].collect(&:to_sym)
|
97
|
+
parent_id = "#{parent_name_parts.last}_id".to_sym
|
98
|
+
|
99
|
+
scope do |query|
|
100
|
+
query.where parent_id => params[parent_id]
|
101
|
+
end
|
102
|
+
|
103
|
+
responds_within do
|
104
|
+
parent = parent_model_class_name.camelize.constantize.find params[parent_id]
|
105
|
+
parent_modules + [parent]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Instance methods that support the Actions module.
|
111
|
+
module InstanceMethods
|
112
|
+
delegate :model_class_name, :model_class, :scopes, :responds_within, :to => "self.class"
|
113
|
+
|
114
|
+
# Apply scopes to the given query.
|
115
|
+
#
|
116
|
+
# Applicable scopes come from two places. They are either declared at the class level with
|
117
|
+
# <tt>ClassMethods#scope</tt>, or named in the request itself. The former is good for
|
118
|
+
# defining topics or enforcing security, while the latter is free slicing and dicing for
|
119
|
+
# clients.
|
120
|
+
#
|
121
|
+
# Class-level scopes are applied first. Request scopes come after, and are discovered by
|
122
|
+
# examining +params+. If any +params+ key matches a name found in
|
123
|
+
# <tt>ClassMethods#model_class.scopes.keys</tt>, then it is taken to be a scope and is
|
124
|
+
# applied. The values under that +params+ key are passed along as arguments.
|
125
|
+
#
|
126
|
+
# TODO: and if the scope taketh arguments not?
|
127
|
+
def scope(query)
|
128
|
+
query = (scopes || []).inject(query) do |query, scope|
|
129
|
+
if Symbol === scope and model_class.scopes.key? scope
|
130
|
+
query.send scope
|
131
|
+
elsif Proc === scope
|
132
|
+
instance_exec query, &scope
|
133
|
+
else
|
134
|
+
raise ArgumentError.new "Unknown scope #{model_class}.#{scope}"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
scopes_from_request = (model_class.scopes.keys & params.keys.collect { |k| k.to_sym })
|
139
|
+
query = scopes_from_request.inject(query) do |query, scope|
|
140
|
+
query.send scope, *params[scope.to_s]
|
141
|
+
end
|
142
|
+
|
143
|
+
query
|
144
|
+
end
|
145
|
+
|
146
|
+
# Find all models in #scope.
|
147
|
+
#
|
148
|
+
# The initial, unscoped is <tt>ClassMethods#model_class.all</tt>.
|
149
|
+
def find_models
|
150
|
+
scope model_class.all
|
151
|
+
end
|
152
|
+
|
153
|
+
# Find a particular model.
|
154
|
+
#
|
155
|
+
# #find_models is asked to find a model with <tt>params[:id]</tt>. This ensures that
|
156
|
+
# class-level scopes are enforced (potentially for security.)
|
157
|
+
def find_model
|
158
|
+
find_models.find(params[:id])
|
159
|
+
end
|
160
|
+
|
161
|
+
# The underscored model class name, as a symbol.
|
162
|
+
#
|
163
|
+
# Model modules are omitted.
|
164
|
+
def model_slug
|
165
|
+
model_class_name.split('/').last.to_sym
|
166
|
+
end
|
167
|
+
|
168
|
+
# Like #model_slug, but plural.
|
169
|
+
def models_slug
|
170
|
+
model_slug.to_s.pluralize.to_sym
|
171
|
+
end
|
172
|
+
|
173
|
+
# The name of the instance variable holding a single model instance.
|
174
|
+
def model_ivar
|
175
|
+
"@#{model_slug}"
|
176
|
+
end
|
177
|
+
|
178
|
+
# The name of the instance variable holding a collection of models.
|
179
|
+
def models_ivar
|
180
|
+
model_ivar.pluralize
|
181
|
+
end
|
182
|
+
|
183
|
+
# Retrive #models_ivar
|
184
|
+
def models
|
185
|
+
instance_variable_get models_ivar
|
186
|
+
end
|
187
|
+
|
188
|
+
# Assign #models_ivar
|
189
|
+
def models=(models)
|
190
|
+
instance_variable_set models_ivar, models
|
191
|
+
end
|
192
|
+
|
193
|
+
# Retrive #model_ivar
|
194
|
+
def model
|
195
|
+
instance_variable_get model_ivar
|
196
|
+
end
|
197
|
+
|
198
|
+
# Assign #model_ivar
|
199
|
+
def model=(model)
|
200
|
+
instance_variable_set model_ivar, model
|
201
|
+
end
|
202
|
+
|
203
|
+
# Apply ClassMethods#responds_within to the given model (or symbol.)
|
204
|
+
#
|
205
|
+
# "Apply" just means turning +responds_within+ into an array and appending +model+ to the
|
206
|
+
# end. If +responds_within+ is an array, it used directly.
|
207
|
+
#
|
208
|
+
# If it is a proc, it is called with +instance_exec+, passing +model+ in. It should return an
|
209
|
+
# array, which +model+ will be appended to. (So, don't include it in the return value.)
|
210
|
+
def responder_context(model)
|
211
|
+
context = responds_within.collect do |o|
|
212
|
+
o = instance_exec model, &o if o.is_a? Proc
|
213
|
+
o
|
214
|
+
end.flatten + [model]
|
215
|
+
end
|
216
|
+
|
217
|
+
# Pass +model+ through InstanceMethods#responder_context, and pass that to #respond_with.
|
218
|
+
def respond_with_contextual(model)
|
219
|
+
respond_with *responder_context(model)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
# The seven standard restful actions.
|
224
|
+
module Actions
|
225
|
+
# Find, assign and respond with models.
|
226
|
+
def index
|
227
|
+
self.models = find_models
|
228
|
+
respond_with_contextual models
|
229
|
+
end
|
230
|
+
|
231
|
+
# Find, assign and respond with a single model.
|
232
|
+
def show
|
233
|
+
self.model = find_model
|
234
|
+
respond_with_contextual model
|
235
|
+
end
|
236
|
+
|
237
|
+
# Build (but do not save), assign and respond with a new model.
|
238
|
+
#
|
239
|
+
# The new model is built from the <tt>InstanceMethods#find_models</tt> collection, meaning it
|
240
|
+
# could inherit any properties implied by those scopes.
|
241
|
+
def new
|
242
|
+
self.model = find_models.build
|
243
|
+
respond_with_contextual model
|
244
|
+
end
|
245
|
+
|
246
|
+
# Find, assign and respond with a single model.
|
247
|
+
def edit
|
248
|
+
self.model = find_model
|
249
|
+
respond_with_contextual model
|
250
|
+
end
|
251
|
+
|
252
|
+
# Build, save, assign and respond with a new model.
|
253
|
+
#
|
254
|
+
# The model is created with attributes from the request params, under the
|
255
|
+
# <tt>InstanceMethods#model_slug</tt> key.
|
256
|
+
def create
|
257
|
+
self.model = find_models.build(params[model_slug])
|
258
|
+
model.save
|
259
|
+
respond_with_contextual model
|
260
|
+
end
|
261
|
+
|
262
|
+
# Find, update, assign and respond with a single model.
|
263
|
+
#
|
264
|
+
# The new attributes are taken from the request params, under the
|
265
|
+
# <tt>InstanceMethods#model_slug</tt> key.
|
266
|
+
def update
|
267
|
+
self.model = find_model
|
268
|
+
model.update_attributes(params[model_slug])
|
269
|
+
respond_with_contextual model
|
270
|
+
end
|
271
|
+
|
272
|
+
# Find and destroy a model. Respond with <tt>InstanceMethods#models_slug</tt>.
|
273
|
+
def destroy
|
274
|
+
find_model.destroy
|
275
|
+
respond_with_contextual models_slug
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{responder_controller}
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Phil Smith"]
|
12
|
+
s.date = %q{2010-05-12}
|
13
|
+
s.description = %q{Responders make crud controllers tiny, this wraps the rest.}
|
14
|
+
s.email = %q{phil.h.smith@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".gitignore",
|
22
|
+
"LICENSE",
|
23
|
+
"README.rdoc",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"lib/responder_controller.rb",
|
27
|
+
"responder_controller.gemspec",
|
28
|
+
"spec/responder_controller_spec.rb",
|
29
|
+
"spec/spec.opts",
|
30
|
+
"spec/spec_helper.rb"
|
31
|
+
]
|
32
|
+
s.homepage = %q{http://github.com/phs/responder_controller}
|
33
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
34
|
+
s.require_paths = ["lib"]
|
35
|
+
s.rubygems_version = %q{1.3.6}
|
36
|
+
s.summary = %q{like resources_controller, but for rails 3 responders}
|
37
|
+
s.test_files = [
|
38
|
+
"spec/responder_controller_spec.rb",
|
39
|
+
"spec/spec_helper.rb"
|
40
|
+
]
|
41
|
+
|
42
|
+
if s.respond_to? :specification_version then
|
43
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
44
|
+
s.specification_version = 3
|
45
|
+
|
46
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
47
|
+
s.add_runtime_dependency(%q<activesupport>, [">= 3.0.0.beta2"])
|
48
|
+
s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
|
49
|
+
else
|
50
|
+
s.add_dependency(%q<activesupport>, [">= 3.0.0.beta2"])
|
51
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
52
|
+
end
|
53
|
+
else
|
54
|
+
s.add_dependency(%q<activesupport>, [">= 3.0.0.beta2"])
|
55
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
@@ -0,0 +1,511 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
class Post
|
4
|
+
class <<self
|
5
|
+
def scopes
|
6
|
+
{
|
7
|
+
:recent => "a scope",
|
8
|
+
:unpublished => "another",
|
9
|
+
:authored_by => "and another",
|
10
|
+
:commented_on_by => "even one more",
|
11
|
+
:published_after => "man they just keep coming"
|
12
|
+
}
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module Accounts
|
18
|
+
class User
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module Admin
|
23
|
+
class Setting
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "ResponderController" do
|
28
|
+
|
29
|
+
class ApplicationController
|
30
|
+
include ResponderController
|
31
|
+
|
32
|
+
def params
|
33
|
+
@params ||= { :user_id => 'me', :id => 7 }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class PostsController < ApplicationController
|
38
|
+
end
|
39
|
+
|
40
|
+
class Admin::SettingsController < ApplicationController
|
41
|
+
end
|
42
|
+
|
43
|
+
before :each do
|
44
|
+
@query = mock("the scoped query")
|
45
|
+
@query.stub!(:unpublished).and_return(@query)
|
46
|
+
@query.stub!(:recent).and_return(@query)
|
47
|
+
@query.stub!(:owned_by).with('me').and_return(@query)
|
48
|
+
end
|
49
|
+
|
50
|
+
describe '.model_class_name', 'by default' do
|
51
|
+
it 'is taken from the controller class name' do
|
52
|
+
PostsController.model_class_name.should == 'post'
|
53
|
+
end
|
54
|
+
|
55
|
+
it "includes the controller's modules divided by whacks" do
|
56
|
+
Admin::SettingsController.model_class_name.should == 'admin/setting'
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe '.serves_model' do
|
61
|
+
it 'sets the model class name to the passed value' do
|
62
|
+
PostsController.serves_model 'some_other_model'
|
63
|
+
PostsController.model_class_name.should == 'some_other_model'
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'accepts symbols as well as strings' do
|
67
|
+
PostsController.serves_model :some_other_model
|
68
|
+
PostsController.model_class_name.should == 'some_other_model'
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'raises ArgumentError for other values' do
|
72
|
+
lambda do
|
73
|
+
PostsController.serves_model [:not_a, :string_or_symbol]
|
74
|
+
end.should raise_error ArgumentError
|
75
|
+
end
|
76
|
+
|
77
|
+
after :each do
|
78
|
+
PostsController.serves_model :post
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe '#model_class_name' do
|
83
|
+
it "is .model_class_name" do
|
84
|
+
PostsController.new.model_class_name.should == PostsController.model_class_name
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe '.model_class' do
|
89
|
+
it 'is the constant named by model_class_name' do
|
90
|
+
PostsController.model_class.should == Post
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'handles modules in the name' do
|
94
|
+
Admin::SettingsController.model_class.should == Admin::Setting
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe '#scope', 'by default' do
|
99
|
+
it "passes its argument out" do
|
100
|
+
PostsController.new.scope(@query).should == @query
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe '#model_class' do
|
105
|
+
it 'is .model_class' do
|
106
|
+
PostsController.new.model_class.should == PostsController.model_class
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
describe '.scope' do
|
111
|
+
it 'takes a string naming a scope on model_class' do
|
112
|
+
PostsController.scope 'unpublished'
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'can take a symbol' do
|
116
|
+
PostsController.scope :recent
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
describe '.scopes' do
|
121
|
+
it 'is an array of scopes in order, as symbols' do
|
122
|
+
PostsController.scopes.should == [:unpublished, :recent]
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe '#scopes' do
|
127
|
+
it 'is .scopes' do
|
128
|
+
PostsController.new.scopes.should == PostsController.scopes
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
describe '#scope', 'with explicit scopes' do
|
133
|
+
it "asks model_class for the declared scopes in order" do
|
134
|
+
@query.should_receive(:unpublished).and_return(@query)
|
135
|
+
@query.should_receive(:recent).and_return(@query)
|
136
|
+
PostsController.new.scope(@query).should == @query
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
describe '.scope', 'with a block' do
|
141
|
+
it 'omits the name and can reference params' do
|
142
|
+
PostsController.scope do |posts|
|
143
|
+
posts.owned_by(params[:user_id])
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'puts a lambda on .scopes' do
|
148
|
+
PostsController.scopes.last.should be_a Proc
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
describe '.scope', 'with something that is not a string, symbol or block' do
|
153
|
+
it 'explodes' do
|
154
|
+
lambda do
|
155
|
+
PostsController.scope [:not_a, :string_symbol_or_block]
|
156
|
+
end.should raise_error ArgumentError
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
describe '#scope', 'with a block scope' do
|
161
|
+
it 'instance_execs the block while passing in the current query' do
|
162
|
+
@query.should_receive(:owned_by).with('me').and_return(@query)
|
163
|
+
|
164
|
+
controller = PostsController.new
|
165
|
+
controller.scope(@query).should == @query
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
describe '#scope', 'with an unknown scope' do
|
170
|
+
it 'explodes' do
|
171
|
+
PostsController.scope :furst_p0sts
|
172
|
+
|
173
|
+
lambda do
|
174
|
+
PostsController.new.scope
|
175
|
+
end.should raise_error ArgumentError
|
176
|
+
|
177
|
+
PostsController.scopes.pop
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
describe '#scope', 'with request parameters naming scopes' do
|
182
|
+
before :each do
|
183
|
+
@controller = PostsController.new
|
184
|
+
@controller.params['commented_on_by'] = 'you'
|
185
|
+
end
|
186
|
+
|
187
|
+
it 'applies the requested scopes in order' do
|
188
|
+
@query.should_receive(:commented_on_by).with('you').and_return(@query)
|
189
|
+
@controller.scope @query
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'is applied after class-level scopes' do
|
193
|
+
class_level_query = mock("class-level scoped query")
|
194
|
+
@query.should_receive(:owned_by).and_return(class_level_query) # last class-level scope
|
195
|
+
|
196
|
+
class_level_query.should_receive(:commented_on_by).with('you').and_return(class_level_query)
|
197
|
+
@controller.scope(@query).should == class_level_query
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
describe '#find_models' do
|
202
|
+
it 'is #scope #model_class.find(:all)' do
|
203
|
+
controller = PostsController.new
|
204
|
+
|
205
|
+
Post.should_receive(:all).and_return(@query)
|
206
|
+
controller.should_receive(:scope).with(@query).and_return(@query)
|
207
|
+
|
208
|
+
controller.find_models.should == @query
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
describe '#find_model' do
|
213
|
+
it 'is #find_models.find(params[:id])' do
|
214
|
+
controller = PostsController.new
|
215
|
+
controller.should_receive(:find_models).and_return(@query)
|
216
|
+
@query.should_receive(:find).with(controller.params[:id]).and_return(post = mock("the post"))
|
217
|
+
|
218
|
+
controller.find_model.should == post
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
describe '#model_slug' do
|
223
|
+
it 'is the model class name' do
|
224
|
+
PostsController.new.model_slug.should == :post
|
225
|
+
end
|
226
|
+
|
227
|
+
it 'drops the leading module names, if any' do
|
228
|
+
Admin::SettingsController.new.model_slug.should == :setting
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
describe '#models_slug' do
|
233
|
+
it 'is ths symbolized plural of #model_slug' do
|
234
|
+
PostsController.new.models_slug.should == :posts
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
describe '#model_ivar' do
|
239
|
+
it 'is the #model_slug with a leading @' do
|
240
|
+
PostsController.new.model_ivar.should == '@post'
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
describe '#models_ivar' do
|
245
|
+
it 'is the plural #model_ivar' do
|
246
|
+
(controller = PostsController.new).models_ivar.should == controller.model_ivar.pluralize
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
describe "#models" do
|
251
|
+
it "gets #models_ivar" do
|
252
|
+
(controller = PostsController.new).instance_variable_set("@posts", :some_posts)
|
253
|
+
controller.models.should == :some_posts
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
describe "#model" do
|
258
|
+
it "gets #model_ivar" do
|
259
|
+
(controller = PostsController.new).instance_variable_set("@post", :a_post)
|
260
|
+
controller.model.should == :a_post
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
describe "#models=" do
|
265
|
+
it "assigns to #models_ivar" do
|
266
|
+
assigned = mock("some models")
|
267
|
+
(controller = PostsController.new).models = assigned
|
268
|
+
controller.instance_variable_get("@posts").should == assigned
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
describe "#model=" do
|
273
|
+
it "assigns to #model_ivar" do
|
274
|
+
assigned = mock("a model")
|
275
|
+
(controller = PostsController.new).model = assigned
|
276
|
+
controller.instance_variable_get("@post").should == assigned
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
describe '.responds_within' do
|
281
|
+
it "contains the model's enclosing module names as symbols by default" do
|
282
|
+
PostsController.responds_within.should == []
|
283
|
+
Admin::SettingsController.responds_within.should == [:admin]
|
284
|
+
end
|
285
|
+
|
286
|
+
it "takes, saves and returns a varargs" do
|
287
|
+
PostsController.responds_within(:foo, :bar, :baz).should == [:foo, :bar, :baz]
|
288
|
+
PostsController.responds_within.should == [:foo, :bar, :baz]
|
289
|
+
end
|
290
|
+
|
291
|
+
it "accumulates between calls" do
|
292
|
+
PostsController.responds_within(:foo).should == [:foo]
|
293
|
+
PostsController.responds_within(:bar, :baz).should == [:foo, :bar, :baz]
|
294
|
+
PostsController.responds_within.should == [:foo, :bar, :baz]
|
295
|
+
end
|
296
|
+
|
297
|
+
it "can take a block instead" do
|
298
|
+
block = lambda {}
|
299
|
+
PostsController.responds_within(&block).should == [block]
|
300
|
+
PostsController.responds_within.should == [block]
|
301
|
+
end
|
302
|
+
|
303
|
+
it "whines if both positional arguments and a block are passed" do
|
304
|
+
lambda do
|
305
|
+
PostsController.responds_within(:foo, :bar, :baz) {}
|
306
|
+
end.should raise_error ArgumentError
|
307
|
+
end
|
308
|
+
|
309
|
+
after :each do
|
310
|
+
PostsController.instance_variable_set "@responds_within", nil # clear out the garbage
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
describe '.children_of' do
|
315
|
+
it 'takes a underscored model class name' do
|
316
|
+
PostsController.children_of 'accounts/user'
|
317
|
+
end
|
318
|
+
|
319
|
+
it "can take symbols" do
|
320
|
+
PostsController.children_of 'accounts/user'.to_sym
|
321
|
+
end
|
322
|
+
|
323
|
+
it "creates a scope filtering by the parent model's foreign key as passed in params" do
|
324
|
+
PostsController.children_of 'accounts/user'
|
325
|
+
controller = PostsController.new
|
326
|
+
controller.params[:user_id] = :the_user_id
|
327
|
+
|
328
|
+
user_query = mock("user-restricted query")
|
329
|
+
@query.should_receive(:where).with(:user_id => :the_user_id).and_return(user_query)
|
330
|
+
controller.scope(@query).should == user_query
|
331
|
+
end
|
332
|
+
|
333
|
+
it "adds a responds_within context, of the parent modules followed by the parent itself" do
|
334
|
+
PostsController.children_of 'accounts/user'
|
335
|
+
controller = PostsController.new
|
336
|
+
controller.params[:user_id] = :the_user_id
|
337
|
+
|
338
|
+
Accounts::User.should_receive(:find).with(:the_user_id).and_return(user = mock("the user"))
|
339
|
+
|
340
|
+
controller.responder_context(:argument).should == [:accounts, user, :argument]
|
341
|
+
end
|
342
|
+
|
343
|
+
after :each do
|
344
|
+
PostsController.instance_variable_set "@responds_within", nil # clear out the garbage
|
345
|
+
PostsController.scopes.clear
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
describe '#responds_within' do
|
350
|
+
it 'is .responds_within' do
|
351
|
+
PostsController.new.responds_within.should == PostsController.responds_within
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
describe '#responder_context' do
|
356
|
+
it "is the argument prepended with responds_within" do
|
357
|
+
Admin::SettingsController.new.responder_context(:argument).should == [:admin, :argument]
|
358
|
+
end
|
359
|
+
|
360
|
+
it "passes the argument to responds_within and prepends the result if it is a lambda" do
|
361
|
+
Admin::SettingsController.responds_within do |model|
|
362
|
+
model.should == :argument
|
363
|
+
[:nested, :namespace]
|
364
|
+
end
|
365
|
+
|
366
|
+
Admin::SettingsController.new.responder_context(:argument).should == [:nested, :namespace, :argument]
|
367
|
+
end
|
368
|
+
|
369
|
+
it "wraps the lambda result in an array if needed" do
|
370
|
+
Admin::SettingsController.responds_within { |model| :namespace }
|
371
|
+
Admin::SettingsController.new.responder_context(:argument).should == [:namespace, :argument]
|
372
|
+
end
|
373
|
+
|
374
|
+
after :each do
|
375
|
+
Admin::SettingsController.instance_variable_set "@responds_within", nil
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
describe '#respond_with_contextual' do
|
380
|
+
it 'passed #responder_context to #respond_with' do
|
381
|
+
controller = PostsController.new
|
382
|
+
controller.should_receive(:responder_context).with(:argument).and_return([:contextualized_argument])
|
383
|
+
controller.should_receive(:respond_with).with(:contextualized_argument)
|
384
|
+
|
385
|
+
controller.respond_with_contextual :argument
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
describe 'actions' do
|
390
|
+
before :each do
|
391
|
+
@controller = PostsController.new
|
392
|
+
@controller.stub!(:find_models).and_return(@posts = mock("some posts"))
|
393
|
+
@controller.stub!(:find_model).and_return(@post = mock("a post"))
|
394
|
+
@controller.stub!(:respond_with)
|
395
|
+
|
396
|
+
@posts.stub!(:build).and_return(@post)
|
397
|
+
end
|
398
|
+
|
399
|
+
describe '#index' do
|
400
|
+
it 'assigns #find_models to #models=' do
|
401
|
+
@controller.should_receive(:find_models).and_return(@posts)
|
402
|
+
@controller.index
|
403
|
+
@controller.instance_variable_get('@posts').should == @posts
|
404
|
+
end
|
405
|
+
|
406
|
+
it '#respond_with_contextual @models' do
|
407
|
+
@controller.should_receive(:respond_with_contextual).with(@posts)
|
408
|
+
@controller.index
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
[:show, :edit].each do |verb|
|
413
|
+
describe "##{verb}" do
|
414
|
+
it 'assigns #find_model to #model=' do
|
415
|
+
@controller.should_receive(:find_model).and_return(@post)
|
416
|
+
@controller.send verb
|
417
|
+
@controller.instance_variable_get('@post').should == @post
|
418
|
+
end
|
419
|
+
|
420
|
+
it '#respond_with_contextual @model' do
|
421
|
+
@controller.should_receive(:respond_with_contextual).with(@post)
|
422
|
+
@controller.send verb
|
423
|
+
end
|
424
|
+
end
|
425
|
+
end
|
426
|
+
|
427
|
+
describe '#new' do
|
428
|
+
it 'assigns #find_models.new to #model=' do
|
429
|
+
@posts.should_receive(:build).and_return(@post)
|
430
|
+
@controller.new
|
431
|
+
@controller.instance_variable_get('@post').should == @post
|
432
|
+
end
|
433
|
+
|
434
|
+
it '#respond_with_contextual @model' do
|
435
|
+
@controller.should_receive(:respond_with_contextual).with(@post)
|
436
|
+
@controller.new
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
describe '#create' do
|
441
|
+
before :each do
|
442
|
+
@post.stub!(:save)
|
443
|
+
end
|
444
|
+
|
445
|
+
it 'passes params[model_slug] to #find_models.new' do
|
446
|
+
@controller.params[:post] = :params_to_new
|
447
|
+
@posts.should_receive(:build).with(:params_to_new)
|
448
|
+
@controller.create
|
449
|
+
end
|
450
|
+
|
451
|
+
it 'assigns #find_models.new to #model=' do
|
452
|
+
@controller.create
|
453
|
+
@controller.instance_variable_get('@post').should == @post
|
454
|
+
end
|
455
|
+
|
456
|
+
it 'saves the model' do
|
457
|
+
@post.should_receive(:save)
|
458
|
+
@controller.create
|
459
|
+
end
|
460
|
+
|
461
|
+
it '#respond_with_contextual @model' do
|
462
|
+
@controller.should_receive(:respond_with_contextual).with(@post)
|
463
|
+
@controller.create
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
describe '#update' do
|
468
|
+
before :each do
|
469
|
+
@post.stub!(:update_attributes)
|
470
|
+
end
|
471
|
+
|
472
|
+
it 'assigns #find_model to #model=' do
|
473
|
+
@controller.should_receive(:find_model).and_return(@post)
|
474
|
+
@controller.update
|
475
|
+
@controller.instance_variable_get('@post').should == @post
|
476
|
+
end
|
477
|
+
|
478
|
+
it "updates the model's attributes" do
|
479
|
+
@controller.params[:post] = :params_to_update
|
480
|
+
@post.should_receive(:update_attributes).with(:params_to_update)
|
481
|
+
@controller.update
|
482
|
+
end
|
483
|
+
|
484
|
+
it '#respond_with_contextual @model' do
|
485
|
+
@controller.should_receive(:respond_with_contextual).with(@post)
|
486
|
+
@controller.update
|
487
|
+
end
|
488
|
+
end
|
489
|
+
|
490
|
+
describe '#destroy' do
|
491
|
+
before :each do
|
492
|
+
@post.stub!(:destroy)
|
493
|
+
end
|
494
|
+
|
495
|
+
it 'finds the model' do
|
496
|
+
@controller.should_receive(:find_model).and_return(@post)
|
497
|
+
@controller.destroy
|
498
|
+
end
|
499
|
+
|
500
|
+
it 'destroys the model' do
|
501
|
+
@post.should_receive(:destroy)
|
502
|
+
@controller.destroy
|
503
|
+
end
|
504
|
+
|
505
|
+
it '#respond_with_contextual #models_slug' do
|
506
|
+
@controller.should_receive(:respond_with_contextual).with(:posts)
|
507
|
+
@controller.destroy
|
508
|
+
end
|
509
|
+
end
|
510
|
+
end
|
511
|
+
end
|
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
gem 'activesupport'
|
6
|
+
|
7
|
+
require 'responder_controller'
|
8
|
+
require 'spec'
|
9
|
+
require 'spec/autorun'
|
10
|
+
|
11
|
+
Spec::Runner.configure do |config|
|
12
|
+
|
13
|
+
end
|
metadata
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: responder_controller
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Phil Smith
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-05-12 00:00:00 -07:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: activesupport
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 3
|
29
|
+
- 0
|
30
|
+
- 0
|
31
|
+
- beta2
|
32
|
+
version: 3.0.0.beta2
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: rspec
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
segments:
|
43
|
+
- 1
|
44
|
+
- 2
|
45
|
+
- 9
|
46
|
+
version: 1.2.9
|
47
|
+
type: :development
|
48
|
+
version_requirements: *id002
|
49
|
+
description: Responders make crud controllers tiny, this wraps the rest.
|
50
|
+
email: phil.h.smith@gmail.com
|
51
|
+
executables: []
|
52
|
+
|
53
|
+
extensions: []
|
54
|
+
|
55
|
+
extra_rdoc_files:
|
56
|
+
- LICENSE
|
57
|
+
- README.rdoc
|
58
|
+
files:
|
59
|
+
- .document
|
60
|
+
- .gitignore
|
61
|
+
- LICENSE
|
62
|
+
- README.rdoc
|
63
|
+
- Rakefile
|
64
|
+
- VERSION
|
65
|
+
- lib/responder_controller.rb
|
66
|
+
- responder_controller.gemspec
|
67
|
+
- spec/responder_controller_spec.rb
|
68
|
+
- spec/spec.opts
|
69
|
+
- spec/spec_helper.rb
|
70
|
+
has_rdoc: true
|
71
|
+
homepage: http://github.com/phs/responder_controller
|
72
|
+
licenses: []
|
73
|
+
|
74
|
+
post_install_message:
|
75
|
+
rdoc_options:
|
76
|
+
- --charset=UTF-8
|
77
|
+
require_paths:
|
78
|
+
- lib
|
79
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
segments:
|
84
|
+
- 0
|
85
|
+
version: "0"
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
segments:
|
91
|
+
- 0
|
92
|
+
version: "0"
|
93
|
+
requirements: []
|
94
|
+
|
95
|
+
rubyforge_project:
|
96
|
+
rubygems_version: 1.3.6
|
97
|
+
signing_key:
|
98
|
+
specification_version: 3
|
99
|
+
summary: like resources_controller, but for rails 3 responders
|
100
|
+
test_files:
|
101
|
+
- spec/responder_controller_spec.rb
|
102
|
+
- spec/spec_helper.rb
|