snusnu-merb_resource_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/LICENSE +20 -0
- data/README.textile +306 -0
- data/Rakefile +81 -0
- data/TODO +7 -0
- data/lib/merb_resource_controller/action_timeout_support.rb +53 -0
- data/lib/merb_resource_controller/actions.rb +169 -0
- data/lib/merb_resource_controller/identity_map_support.rb +20 -0
- data/lib/merb_resource_controller/resource_controller.rb +160 -0
- data/lib/merb_resource_controller/resource_proxy.rb +317 -0
- data/lib/merb_resource_controller.rb +29 -0
- data/spec/mrc_test_app/Rakefile +52 -0
- data/spec/mrc_test_app/app/controllers/application.rb +6 -0
- data/spec/mrc_test_app/app/controllers/articles.rb +3 -0
- data/spec/mrc_test_app/app/controllers/community/comments.rb +9 -0
- data/spec/mrc_test_app/app/controllers/community/ratings.rb +9 -0
- data/spec/mrc_test_app/app/controllers/editors.rb +7 -0
- data/spec/mrc_test_app/app/models/article.rb +19 -0
- data/spec/mrc_test_app/app/models/editor.rb +11 -0
- data/spec/mrc_test_app/app/views/articles/edit.html.erb +13 -0
- data/spec/mrc_test_app/app/views/articles/index.html.erb +25 -0
- data/spec/mrc_test_app/app/views/articles/new.html.erb +12 -0
- data/spec/mrc_test_app/app/views/articles/show.html.erb +8 -0
- data/spec/mrc_test_app/app/views/community/comments/edit.html.erb +12 -0
- data/spec/mrc_test_app/app/views/community/comments/index.html.erb +25 -0
- data/spec/mrc_test_app/app/views/community/comments/new.html.erb +3 -0
- data/spec/mrc_test_app/app/views/community/comments/show.html.erb +3 -0
- data/spec/mrc_test_app/app/views/community/ratings/edit.html.erb +11 -0
- data/spec/mrc_test_app/app/views/community/ratings/index.html.erb +25 -0
- data/spec/mrc_test_app/app/views/community/ratings/show.html.erb +3 -0
- data/spec/mrc_test_app/app/views/editors/edit.html.erb +12 -0
- data/spec/mrc_test_app/app/views/editors/new.html.erb +12 -0
- data/spec/mrc_test_app/app/views/editors/show.html.erb +7 -0
- data/spec/mrc_test_app/config/database.yml +33 -0
- data/spec/mrc_test_app/config/environments/development.rb +15 -0
- data/spec/mrc_test_app/config/environments/rake.rb +11 -0
- data/spec/mrc_test_app/config/environments/test.rb +12 -0
- data/spec/mrc_test_app/config/init.rb +36 -0
- data/spec/mrc_test_app/config/rack.rb +11 -0
- data/spec/mrc_test_app/config/router.rb +50 -0
- data/spec/mrc_test_app/spec/lib/resource_proxy_spec.rb +292 -0
- data/spec/mrc_test_app/spec/request/article_comments_spec.rb +208 -0
- data/spec/mrc_test_app/spec/request/article_editor_spec.rb +202 -0
- data/spec/mrc_test_app/spec/request/articles_spec.rb +208 -0
- data/spec/mrc_test_app/spec/request/comments_spec.rb +221 -0
- data/spec/mrc_test_app/spec/spec.opts +2 -0
- data/spec/mrc_test_app/spec/spec_helper.rb +206 -0
- metadata +166 -0
@@ -0,0 +1,160 @@
|
|
1
|
+
module Merb
|
2
|
+
module ResourceController
|
3
|
+
|
4
|
+
class ResourceControllerException < Exception; end
|
5
|
+
class WebMethodsNotAvailable < ResourceControllerException; end
|
6
|
+
class InvalidRoute < ResourceControllerException; end
|
7
|
+
|
8
|
+
module Mixin
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
|
12
|
+
def controlling(name, options = {})
|
13
|
+
options = { :flash => true }.merge!(options)
|
14
|
+
@resource_proxy = Merb::ResourceController::ResourceProxy.new(name, options)
|
15
|
+
yield @resource_proxy if block_given?
|
16
|
+
class_inheritable_reader :resource_proxy
|
17
|
+
include InstanceMethods
|
18
|
+
include FlashSupport if options[:flash]
|
19
|
+
@resource_proxy.registered_actions.each do |a|
|
20
|
+
include Merb::ResourceController::Actions.const_get("#{a[:name].to_s.camel_case}")
|
21
|
+
show_action(a[:name])
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
module InstanceMethods
|
28
|
+
|
29
|
+
protected
|
30
|
+
|
31
|
+
def resource_proxy
|
32
|
+
self.class.resource_proxy
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
def singleton_controller?
|
37
|
+
resource_proxy.singleton_resource?
|
38
|
+
end
|
39
|
+
|
40
|
+
def has_parent?
|
41
|
+
resource_proxy.has_parent? && has_parent_param?
|
42
|
+
end
|
43
|
+
|
44
|
+
def has_parent_param?
|
45
|
+
!!parent_param
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
def parent
|
50
|
+
resource_proxy.path_to_resource(params)[-2].last
|
51
|
+
end
|
52
|
+
|
53
|
+
# TODO refactor so that no additional queries are necessary
|
54
|
+
def parents
|
55
|
+
resource_proxy.parents.map do |parent|
|
56
|
+
parent[:class].get(params[parent[:key]])
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def parent_param
|
61
|
+
params[resource_proxy.parent_key]
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
def load_resource
|
66
|
+
path = resource_proxy.path_to_resource(params)
|
67
|
+
# puts "path_to_resource: #{path.inspect}"
|
68
|
+
path.each do |pc|
|
69
|
+
# puts "setting @#{pc[0]} to #{pc[1].inspect}" if pc[1]
|
70
|
+
instance_variable_set("@#{pc[0]}", pc[1]) if pc[1]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def requested_resource
|
75
|
+
resource_proxy.path_to_resource(params).last.last
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
def set_collection(obj)
|
80
|
+
instance_variable_set("@#{collection_name}", obj)
|
81
|
+
end
|
82
|
+
|
83
|
+
def set_member(obj)
|
84
|
+
instance_variable_set("@#{member_name}", obj)
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
def collection_name(resource = nil)
|
89
|
+
resource_proxy.collection_name(resource)
|
90
|
+
end
|
91
|
+
|
92
|
+
def member_name(resource = nil)
|
93
|
+
resource_proxy.member_name(resource)
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
def collection
|
98
|
+
instance_variable_get("@#{collection_name}")
|
99
|
+
end
|
100
|
+
|
101
|
+
def member
|
102
|
+
instance_variable_get("@#{member_name}")
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
def new_member(attributes = {})
|
107
|
+
resource_proxy.new_member(params, attributes)
|
108
|
+
end
|
109
|
+
|
110
|
+
def member_params(attributes = {})
|
111
|
+
resource_proxy.member_params(params, attributes)
|
112
|
+
end
|
113
|
+
|
114
|
+
def parent_params
|
115
|
+
resource_proxy.parent_params(params)
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
def flash_supported?
|
120
|
+
self.kind_of?(FlashSupport)
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
|
125
|
+
module FlashSupport
|
126
|
+
|
127
|
+
protected
|
128
|
+
|
129
|
+
def successful_create_messages
|
130
|
+
{ :notice => "#{member.class.name} was successfully created" }
|
131
|
+
end
|
132
|
+
|
133
|
+
def failed_create_messages
|
134
|
+
{ :error => "Failed to create new #{member.class.name}" }
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
def successful_update_messages
|
139
|
+
{ :notice => "#{member.class.name} was successfully updated" }
|
140
|
+
end
|
141
|
+
|
142
|
+
def failed_update_messages
|
143
|
+
{ :error => "Failed to update #{member.class.name}" }
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
def successful_destroy_messages
|
148
|
+
{ :notice => "#{member.class.name} was successfully destroyed" }
|
149
|
+
end
|
150
|
+
|
151
|
+
def failed_destroy_messages
|
152
|
+
{ :error => "Failed to destroy #{member.class.name}" }
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
160
|
+
end
|
@@ -0,0 +1,317 @@
|
|
1
|
+
module Merb
|
2
|
+
module ResourceController
|
3
|
+
|
4
|
+
class ResourceProxy
|
5
|
+
|
6
|
+
DEFAULT_NESTING_OPTIONS = {
|
7
|
+
:singleton => false,
|
8
|
+
:fully_qualified => false
|
9
|
+
}
|
10
|
+
|
11
|
+
DEFAULT_OPTIONS = DEFAULT_NESTING_OPTIONS.merge({
|
12
|
+
:defaults => true,
|
13
|
+
:use => :all
|
14
|
+
})
|
15
|
+
|
16
|
+
attr_reader :resource, :parents, :registered_methods
|
17
|
+
|
18
|
+
def initialize(resource, options = {})
|
19
|
+
options = DEFAULT_OPTIONS.merge(options)
|
20
|
+
@resource, @singleton = load_resource(resource), !!options[:singleton]
|
21
|
+
@fully_qualified = !!options[:fully_qualified]
|
22
|
+
@actions, @registered_methods, @parents = [], [], []
|
23
|
+
@specific_methods_registered = options[:use] != :all
|
24
|
+
register_default_actions! if options[:defaults]
|
25
|
+
register_methods!(options[:use])
|
26
|
+
end
|
27
|
+
|
28
|
+
def action(name, options = {})
|
29
|
+
@actions << { :name => name.to_sym }.merge(options)
|
30
|
+
end
|
31
|
+
|
32
|
+
def actions(*names)
|
33
|
+
names.each { |n| @actions << n.is_a?(Hash) ? n : { :name => n.to_sym } }
|
34
|
+
end
|
35
|
+
|
36
|
+
def registered_actions
|
37
|
+
@actions
|
38
|
+
end
|
39
|
+
|
40
|
+
# ----------------------------------------------------------------------------------------
|
41
|
+
# # one level nestings
|
42
|
+
# ----------------------------------------------------------------------------------------
|
43
|
+
# r.belongs_to :article # assumes :key => :article_id
|
44
|
+
# r.belongs_to :article, :key => :foo # override :key => :foo
|
45
|
+
# ----------------------------------------------------------------------------------------
|
46
|
+
# # multi level nestings (array item ordering reflects nesting strategy)
|
47
|
+
# ----------------------------------------------------------------------------------------
|
48
|
+
# r.belongs_to [ :article, :post ] # assumes :key => :article_id and :key => :post_id
|
49
|
+
# r.belongs_to [ [ :article, :key => :foo ], :post ]
|
50
|
+
# r.belongs_to [ [ :article, :key => :foo ], [ :post, :key => :bar ] ] ]
|
51
|
+
# ----------------------------------------------------------------------------------------
|
52
|
+
|
53
|
+
def belongs_to(parent, options = {})
|
54
|
+
case parent
|
55
|
+
when Symbol, String then
|
56
|
+
options = DEFAULT_NESTING_OPTIONS.merge(:key => key_name(parent)).merge(options)
|
57
|
+
@parents << { :name => parent, :class => load_resource(parent) }.merge(options)
|
58
|
+
when Array then
|
59
|
+
parent.each do |p|
|
60
|
+
case p
|
61
|
+
when Symbol, String then
|
62
|
+
options = DEFAULT_NESTING_OPTIONS.merge(:key => key_name(p))
|
63
|
+
@parents << { :name => p, :class => load_resource(p) }.merge(options)
|
64
|
+
when Array then
|
65
|
+
if (p[0].is_a?(Symbol) || p[0].is_a?(String)) && p[1].is_a?(Hash)
|
66
|
+
options = DEFAULT_NESTING_OPTIONS.merge(:key => key_name(p[0])).merge(p[1])
|
67
|
+
@parents << { :name => p[0], :class => load_resource(p[0]) }.merge(options)
|
68
|
+
else
|
69
|
+
raise ArgumentError, "use [ Symbol|String, Hash ] to denote one of multiple parents"
|
70
|
+
end
|
71
|
+
else
|
72
|
+
raise ArgumentError, "parent must be Symbol, String or Array but was #{p.class}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
else
|
76
|
+
raise ArgumentError, "parent must be Symbol, String or Array but was #{parent.class}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def belongs_to?(parent)
|
81
|
+
@parents.any? { |h| h[:name] == parent }
|
82
|
+
end
|
83
|
+
|
84
|
+
def has_parent?
|
85
|
+
!@parents.empty?
|
86
|
+
end
|
87
|
+
|
88
|
+
def has_parents?
|
89
|
+
@parents.size > 1
|
90
|
+
end
|
91
|
+
|
92
|
+
def fully_qualified?
|
93
|
+
@fully_qualified
|
94
|
+
end
|
95
|
+
|
96
|
+
def singleton_resource?
|
97
|
+
@singleton
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
def path_to_resource(params)
|
102
|
+
nesting_strategy_instance(nesting_strategy_template(params)).map do |i|
|
103
|
+
[ i[3] ? member_name(i[0], i[2]) : i[1] ? member_name(i[0], i[2]) : collection_name(i[0], i[2]), i[4] ]
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def nesting_strategy_instance(nst, idx = 0)
|
108
|
+
if nst[idx]
|
109
|
+
if idx == 0
|
110
|
+
if nst[idx][3]
|
111
|
+
if nst[idx][1]
|
112
|
+
raise "Toplevel singleton resources are not supported"
|
113
|
+
else
|
114
|
+
nst[idx] = nst[idx] + [ nst[idx][0].get(nst[idx][3]), nra(nst[idx][0], nst) ]
|
115
|
+
nesting_strategy_instance(nst, idx + 1)
|
116
|
+
end
|
117
|
+
else
|
118
|
+
nst[idx] = nst[idx] + [ nst[idx][0].all, nra(nst[idx][0], nst) ]
|
119
|
+
nesting_strategy_instance(nst, idx + 1)
|
120
|
+
end
|
121
|
+
else
|
122
|
+
if nst[idx][3]
|
123
|
+
if nst[idx][1]
|
124
|
+
nst[idx] = nst[idx] + [ nst[idx - 1][4].send(nst[idx - 1][5]), nra(nst[idx][0], nst) ]
|
125
|
+
nesting_strategy_instance(nst, idx + 1)
|
126
|
+
else
|
127
|
+
nst[idx] = nst[idx] + [ nst[idx - 1][4].send(nst[idx - 1][5]).get(nst[idx][3]), nra(nst[idx][0], nst) ]
|
128
|
+
nesting_strategy_instance(nst, idx + 1)
|
129
|
+
end
|
130
|
+
else
|
131
|
+
nst[idx] = nst[idx] + [ nst[idx - 1][4].send(nst[idx - 1][5]), nra(nst[idx][0], nst) ]
|
132
|
+
nesting_strategy_instance(nst, idx + 1)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
else
|
136
|
+
nst
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# nested_resource_accessor
|
141
|
+
def nra(member, nst)
|
142
|
+
member = member.is_a?(Class) ? member : member.class
|
143
|
+
return nil unless idx = nst.map { |el| el[0] }.index(member)
|
144
|
+
if child = nst[idx + 1]
|
145
|
+
model, singleton, fully_qualified, id = child[0], child[1], child[2], child[3]
|
146
|
+
if id
|
147
|
+
collection_name(model, fully_qualified)
|
148
|
+
else
|
149
|
+
singleton ? member_name(model, fully_qualified) : collection_name(model, fully_qualified)
|
150
|
+
end
|
151
|
+
else
|
152
|
+
nil
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
|
157
|
+
def nesting_strategy_template(params)
|
158
|
+
idx = -1
|
159
|
+
nesting_strategy_params(params).map do |nsp|
|
160
|
+
nesting_strategy[idx += 1] << nsp
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def nesting_strategy_params(params)
|
165
|
+
parent_param_values(params) << params["id"]
|
166
|
+
end
|
167
|
+
|
168
|
+
def nesting_strategy
|
169
|
+
parent_resources << [ @resource, @singleton, fully_qualified? ]
|
170
|
+
end
|
171
|
+
|
172
|
+
def nesting_level
|
173
|
+
nesting_strategy.size
|
174
|
+
end
|
175
|
+
|
176
|
+
|
177
|
+
# all parent parameters
|
178
|
+
def parent_param_values(params)
|
179
|
+
parent_keys.map { |k| params[k] }
|
180
|
+
end
|
181
|
+
|
182
|
+
# the immediate parent parameter
|
183
|
+
def parent_param_value(params)
|
184
|
+
parent_param_values(params).last
|
185
|
+
end
|
186
|
+
|
187
|
+
|
188
|
+
def new_member(params, attributes = {})
|
189
|
+
resource.new(member_params(params, attributes))
|
190
|
+
end
|
191
|
+
|
192
|
+
def member_params(params, attributes = {})
|
193
|
+
if attrs = params[member_name]
|
194
|
+
has_parent? ? parent_params(params).merge!(attrs).merge!(attributes) : attrs.merge!(attributes)
|
195
|
+
else
|
196
|
+
has_parent? ? parent_params(params).merge!(attributes) : attributes
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def parent_params(params)
|
201
|
+
parent_keys.inject({}) do |hash, key|
|
202
|
+
key = key.to_sym
|
203
|
+
hash[key] = params[key] if resource.properties.map { |p| p.name }.include?(key.to_sym)
|
204
|
+
hash
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
|
209
|
+
# all parent resources
|
210
|
+
def parent_resources
|
211
|
+
@parents.map { |h| [ h[:class], h[:singleton], h[:fully_qualified] ] }
|
212
|
+
end
|
213
|
+
|
214
|
+
# the immediate parent resource
|
215
|
+
def parent_resource
|
216
|
+
parent_resources.last
|
217
|
+
end
|
218
|
+
|
219
|
+
|
220
|
+
# all parent resource keys
|
221
|
+
def parent_keys
|
222
|
+
@parents.map { |h| h[:key] }
|
223
|
+
end
|
224
|
+
|
225
|
+
# the immediate parent resource key
|
226
|
+
def parent_key
|
227
|
+
@parents.last ? @parents.last[:key] : nil
|
228
|
+
end
|
229
|
+
|
230
|
+
|
231
|
+
def collection_name(resource = nil, fully_qualified = false)
|
232
|
+
if fully_qualified
|
233
|
+
Extlib::Inflection.tableize((resource || @resource).name).to_sym
|
234
|
+
else
|
235
|
+
Extlib::Inflection.demodulize((resource || @resource).name).pluralize.snake_case.to_sym
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def member_name(resource = nil, fully_qualified = false)
|
240
|
+
collection_name(resource, fully_qualified).to_s.singularize.to_sym
|
241
|
+
end
|
242
|
+
|
243
|
+
def key_name(resource = nil)
|
244
|
+
Extlib::Inflection.foreign_key(resource || @resource)
|
245
|
+
end
|
246
|
+
|
247
|
+
|
248
|
+
def specific_methods_registered?
|
249
|
+
@specific_methods_registered && !@registered_methods.empty?
|
250
|
+
end
|
251
|
+
|
252
|
+
def method_registered?(name)
|
253
|
+
specific_methods_registered? ? registered_methods.map { |m| m[:name] }.include?(name.to_sym) : true
|
254
|
+
end
|
255
|
+
|
256
|
+
|
257
|
+
def method_missing(name, *args, &block)
|
258
|
+
return super unless method_registered?(name)
|
259
|
+
@resource.send(name, *args, &block)
|
260
|
+
end
|
261
|
+
|
262
|
+
|
263
|
+
private
|
264
|
+
|
265
|
+
def load_resource(r)
|
266
|
+
case r
|
267
|
+
when Symbol
|
268
|
+
Module.find_const(r.to_s.singular.camel_case)
|
269
|
+
when String
|
270
|
+
Module.find_const(r.include?('::') ? r : r.singular.camel_case)
|
271
|
+
when Class
|
272
|
+
r
|
273
|
+
else
|
274
|
+
raise "resource must be either a Symbol, a String or a Class"
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
def register_default_actions!
|
279
|
+
action :index unless @singleton
|
280
|
+
[ :show, :new, :edit, :create, :update, :destroy ].each do |a|
|
281
|
+
action(a)
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
|
286
|
+
# KEEP THESE for later
|
287
|
+
|
288
|
+
def register_methods!(methods)
|
289
|
+
@registered_methods = case methods
|
290
|
+
when :all then []
|
291
|
+
when :web_methods then @resource.web_methods
|
292
|
+
when Array then methods
|
293
|
+
else raise
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
# def raise_if_invalid_options!(options)
|
298
|
+
# if options[:use] == :web_methods && !@resource.respond_to?(:web_methods)
|
299
|
+
# raise WebMethodsNotAvailable, "require 'dm-is-online' if you want to use web_methods"
|
300
|
+
# end
|
301
|
+
# meth = options[:to]
|
302
|
+
# if options[:use] == :all
|
303
|
+
# msg = "merb_resource_controller: #{@resource}.public_methods.include?(:#{meth}) == false"
|
304
|
+
# raise InvalidRoute, msg unless @resource.public_methods.include?(meth)
|
305
|
+
# elsif options[:use] == :web_methods
|
306
|
+
# msg = "merb_resource_controller: #{@resource}.web_methods.include?(:#{meth}) == false"
|
307
|
+
# raise InvalidRoute, msg unless @resource.web_methods.include?(meth)
|
308
|
+
# else
|
309
|
+
# msg = "merb_resource_controller: Invalid option[:use] = #{options[:use]}, using :all instead"
|
310
|
+
# Merb::Logger.warn(msg)
|
311
|
+
# end
|
312
|
+
# end
|
313
|
+
|
314
|
+
end
|
315
|
+
|
316
|
+
end
|
317
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# make sure we're running inside Merb
|
2
|
+
if defined?(Merb::Plugins)
|
3
|
+
|
4
|
+
# Merb gave me a Merb::Plugins.config hash
|
5
|
+
# i felt free to put my stuff in my piece of it
|
6
|
+
Merb::Plugins.config[:merb_resource_controller] = {
|
7
|
+
:identity_map => true,
|
8
|
+
:action_timeout => true
|
9
|
+
}
|
10
|
+
|
11
|
+
Merb::BootLoader.before_app_loads do
|
12
|
+
# require code that must be loaded before the application
|
13
|
+
mrc = File.join(File.dirname(__FILE__), 'merb_resource_controller')
|
14
|
+
require mrc / 'resource_proxy'
|
15
|
+
require mrc / 'actions'
|
16
|
+
require mrc / 'resource_controller'
|
17
|
+
if Merb::Plugins.config[:merb_resource_controller][:identity_map]
|
18
|
+
require mrc / 'identity_map_support'
|
19
|
+
end
|
20
|
+
if Merb::Plugins.config[:merb_resource_controller][:action_timeout]
|
21
|
+
require mrc / 'action_timeout_support'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
Merb::BootLoader.after_app_loads do
|
26
|
+
# code that can be required after the application loads
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/rdoctask'
|
3
|
+
|
4
|
+
require 'merb-core'
|
5
|
+
require 'merb-core/tasks/merb'
|
6
|
+
|
7
|
+
include FileUtils
|
8
|
+
|
9
|
+
# Load the basic runtime dependencies; this will include
|
10
|
+
# any plugins and therefore plugin rake tasks.
|
11
|
+
init_env = ENV['MERB_ENV'] || 'rake'
|
12
|
+
Merb.load_dependencies(:environment => init_env)
|
13
|
+
|
14
|
+
# Get Merb plugins and dependencies
|
15
|
+
Merb::Plugins.rakefiles.each { |r| require r }
|
16
|
+
|
17
|
+
# Load any app level custom rakefile extensions from lib/tasks
|
18
|
+
tasks_path = File.join(File.dirname(__FILE__), "lib", "tasks")
|
19
|
+
rake_files = Dir["#{tasks_path}/*.rake"]
|
20
|
+
rake_files.each{|rake_file| load rake_file }
|
21
|
+
|
22
|
+
desc "Start runner environment"
|
23
|
+
task :merb_env do
|
24
|
+
Merb.start_environment(:environment => init_env, :adapter => 'runner')
|
25
|
+
end
|
26
|
+
|
27
|
+
require 'spec/rake/spectask'
|
28
|
+
|
29
|
+
desc 'Run specifications'
|
30
|
+
Spec::Rake::SpecTask.new(:spec) do |t|
|
31
|
+
|
32
|
+
t.spec_opts << '--options' << 'spec/spec.opts' if File.exists?('spec/spec.opts')
|
33
|
+
t.spec_files = Pathname.glob(Pathname.new(__FILE__).dirname + 'spec/**/*_spec.rb')
|
34
|
+
|
35
|
+
begin
|
36
|
+
t.rcov = ENV.has_key?('NO_RCOV') ? ENV['NO_RCOV'] != 'true' : true
|
37
|
+
t.rcov_opts << '--exclude' << 'spec'
|
38
|
+
t.rcov_opts << '--text-summary'
|
39
|
+
t.rcov_opts << '--sort' << 'coverage' << '--sort-reverse'
|
40
|
+
rescue Exception
|
41
|
+
# rcov not installed
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
desc 'Default: run spec examples'
|
47
|
+
task :default => 'spec'
|
48
|
+
|
49
|
+
##############################################################################
|
50
|
+
# ADD YOUR CUSTOM TASKS IN /lib/tasks
|
51
|
+
# NAME YOUR RAKE FILES file_name.rake
|
52
|
+
##############################################################################
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class Article
|
2
|
+
|
3
|
+
include DataMapper::Resource
|
4
|
+
|
5
|
+
property :id, Serial
|
6
|
+
property :title, String, :nullable => false, :length => (3..80)
|
7
|
+
property :body, String
|
8
|
+
|
9
|
+
property :editor_id, Integer
|
10
|
+
|
11
|
+
belongs_to :editor
|
12
|
+
|
13
|
+
has n, :comments, :class_name => "Community::Comment"
|
14
|
+
|
15
|
+
def editor_name
|
16
|
+
editor ? editor.name : "Anonymous"
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
<h2>Edit Article</h2>
|
2
|
+
|
3
|
+
<%= form_for(@article, :action => resource(@article), :method => :put) do %>
|
4
|
+
|
5
|
+
<p><%= text_field :title, :label => "Title" %></p>
|
6
|
+
<p><%= text_field :body, :label => "Body" %></p>
|
7
|
+
<p><%= submit "Update" %></p>
|
8
|
+
|
9
|
+
<% end =%>
|
10
|
+
|
11
|
+
<%= link_to 'Show Editor', resource(@article, :editor) %> |
|
12
|
+
<%= link_to 'Show Article', resource(@article) %> |
|
13
|
+
<%= link_to 'All Articles', resource(:articles) %>
|
@@ -0,0 +1,25 @@
|
|
1
|
+
<h1>Listing Articles</h1>
|
2
|
+
|
3
|
+
<table>
|
4
|
+
<tr>
|
5
|
+
<th>Title</th>
|
6
|
+
<th>Editor</th>
|
7
|
+
<th>Body</th>
|
8
|
+
|
9
|
+
<th colspan="3">Actions</th>
|
10
|
+
</tr>
|
11
|
+
|
12
|
+
<% @articles.each do |article| %>
|
13
|
+
<tr>
|
14
|
+
<td><%=h article.title %></td>
|
15
|
+
<td><%=h article.editor_name %></td>
|
16
|
+
<td><%=h article.body %></td>
|
17
|
+
|
18
|
+
<td><%= link_to 'Show', resource(article) %></td>
|
19
|
+
<td><%= link_to 'Edit', resource(article, :edit) %></td>
|
20
|
+
<td><%= delete_button(article, "Delete #{article.title}") %></td>
|
21
|
+
</tr>
|
22
|
+
<% end %>
|
23
|
+
</table>
|
24
|
+
|
25
|
+
<%= link_to 'New Article', resource(:articles, :new) %>
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<h2>New Article</h2>
|
2
|
+
|
3
|
+
<%= form_for(@article, :action => resource(:articles)) do %>
|
4
|
+
|
5
|
+
<p><%= text_field :title, :label => "Title" %></p>
|
6
|
+
<p><%= text_area :body, :label => "Body" %></p>
|
7
|
+
|
8
|
+
<p><%= submit "Create" %></p>
|
9
|
+
|
10
|
+
<% end =%>
|
11
|
+
|
12
|
+
<%= link_to 'All Articles', resource(:articles) %>
|