snusnu-merb_resource_controller 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+