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.
Files changed (47) hide show
  1. data/LICENSE +20 -0
  2. data/README.textile +306 -0
  3. data/Rakefile +81 -0
  4. data/TODO +7 -0
  5. data/lib/merb_resource_controller/action_timeout_support.rb +53 -0
  6. data/lib/merb_resource_controller/actions.rb +169 -0
  7. data/lib/merb_resource_controller/identity_map_support.rb +20 -0
  8. data/lib/merb_resource_controller/resource_controller.rb +160 -0
  9. data/lib/merb_resource_controller/resource_proxy.rb +317 -0
  10. data/lib/merb_resource_controller.rb +29 -0
  11. data/spec/mrc_test_app/Rakefile +52 -0
  12. data/spec/mrc_test_app/app/controllers/application.rb +6 -0
  13. data/spec/mrc_test_app/app/controllers/articles.rb +3 -0
  14. data/spec/mrc_test_app/app/controllers/community/comments.rb +9 -0
  15. data/spec/mrc_test_app/app/controllers/community/ratings.rb +9 -0
  16. data/spec/mrc_test_app/app/controllers/editors.rb +7 -0
  17. data/spec/mrc_test_app/app/models/article.rb +19 -0
  18. data/spec/mrc_test_app/app/models/editor.rb +11 -0
  19. data/spec/mrc_test_app/app/views/articles/edit.html.erb +13 -0
  20. data/spec/mrc_test_app/app/views/articles/index.html.erb +25 -0
  21. data/spec/mrc_test_app/app/views/articles/new.html.erb +12 -0
  22. data/spec/mrc_test_app/app/views/articles/show.html.erb +8 -0
  23. data/spec/mrc_test_app/app/views/community/comments/edit.html.erb +12 -0
  24. data/spec/mrc_test_app/app/views/community/comments/index.html.erb +25 -0
  25. data/spec/mrc_test_app/app/views/community/comments/new.html.erb +3 -0
  26. data/spec/mrc_test_app/app/views/community/comments/show.html.erb +3 -0
  27. data/spec/mrc_test_app/app/views/community/ratings/edit.html.erb +11 -0
  28. data/spec/mrc_test_app/app/views/community/ratings/index.html.erb +25 -0
  29. data/spec/mrc_test_app/app/views/community/ratings/show.html.erb +3 -0
  30. data/spec/mrc_test_app/app/views/editors/edit.html.erb +12 -0
  31. data/spec/mrc_test_app/app/views/editors/new.html.erb +12 -0
  32. data/spec/mrc_test_app/app/views/editors/show.html.erb +7 -0
  33. data/spec/mrc_test_app/config/database.yml +33 -0
  34. data/spec/mrc_test_app/config/environments/development.rb +15 -0
  35. data/spec/mrc_test_app/config/environments/rake.rb +11 -0
  36. data/spec/mrc_test_app/config/environments/test.rb +12 -0
  37. data/spec/mrc_test_app/config/init.rb +36 -0
  38. data/spec/mrc_test_app/config/rack.rb +11 -0
  39. data/spec/mrc_test_app/config/router.rb +50 -0
  40. data/spec/mrc_test_app/spec/lib/resource_proxy_spec.rb +292 -0
  41. data/spec/mrc_test_app/spec/request/article_comments_spec.rb +208 -0
  42. data/spec/mrc_test_app/spec/request/article_editor_spec.rb +202 -0
  43. data/spec/mrc_test_app/spec/request/articles_spec.rb +208 -0
  44. data/spec/mrc_test_app/spec/request/comments_spec.rb +221 -0
  45. data/spec/mrc_test_app/spec/spec.opts +2 -0
  46. data/spec/mrc_test_app/spec/spec_helper.rb +206 -0
  47. 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,6 @@
1
+ class Application < Merb::Controller
2
+ include Merb::ResourceController::DM::IdentityMapSupport
3
+ extend Merb::ResourceController::Mixin::ClassMethods
4
+ extend Merb::ResourceController::ActionTimeout
5
+ set_action_timeout 1
6
+ end
@@ -0,0 +1,3 @@
1
+ class Articles < Application
2
+ controlling :articles
3
+ end
@@ -0,0 +1,9 @@
1
+ module Community
2
+
3
+ class Comments < Application
4
+ controlling "Community::Comment" do |c|
5
+ c.belongs_to :article
6
+ end
7
+ end
8
+
9
+ end
@@ -0,0 +1,9 @@
1
+ module Community
2
+
3
+ class Ratings < Application
4
+ controlling "Community::Rating" do |r|
5
+ r.belongs_to [ :article, "Community::Comment" ]
6
+ end
7
+ end
8
+
9
+ end
@@ -0,0 +1,7 @@
1
+ class Editors < Application
2
+
3
+ controlling :editor, :singleton => true do |e|
4
+ e.belongs_to :article
5
+ end
6
+
7
+ end
@@ -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,11 @@
1
+ class Editor
2
+
3
+ include DataMapper::Resource
4
+
5
+ property :id, Serial
6
+
7
+ property :name, String, :nullable => false, :length => (3..40)
8
+
9
+ has n, :articles
10
+
11
+ 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) %>
@@ -0,0 +1,8 @@
1
+ <h2>Show Article</h2>
2
+
3
+ <h3><%=h @article.title %></h3>
4
+ <p><%=h @article.body %></p>
5
+
6
+ <%= link_to 'Edit Article', resource(@article, :edit) %>
7
+ <%= link_to 'All Articles', resource(:articles) %>
8
+