marley 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. data/README.rdoc +3 -8
  2. data/lib/marley.rb +27 -131
  3. data/lib/marley/controllers.rb +12 -7
  4. data/lib/marley/core_ext.rb +8 -0
  5. data/lib/marley/errors.rb +51 -0
  6. data/lib/marley/joint.rb +14 -19
  7. data/lib/marley/joints/forum.rb +88 -0
  8. data/lib/marley/joints/messages.rb +86 -0
  9. data/lib/marley/joints/section.rb +63 -0
  10. data/lib/marley/joints/tags.rb +99 -0
  11. data/lib/marley/joints/user.rb +120 -0
  12. data/lib/marley/plugin.rb +39 -0
  13. data/lib/marley/plugins/orm_rest_convenience.rb +92 -0
  14. data/lib/marley/plugins/rest_convenience.rb +28 -0
  15. data/lib/marley/reggae.rb +5 -2
  16. data/lib/marley/resources.rb +10 -0
  17. data/lib/marley/router.rb +61 -0
  18. data/lib/marley/test_helpers.rb +12 -1
  19. data/lib/marley/utils.rb +57 -25
  20. data/rdoc/example_joint.rb +33 -0
  21. data/rdoc/example_plugin.rb +20 -0
  22. data/rdoc/forum_joint.rb +50 -0
  23. data/rdoc/forum_joint.rdoc +19 -0
  24. data/rdoc/hello.rb +14 -0
  25. data/rdoc/hello.rdoc +14 -0
  26. data/rdoc/joints.rdoc +69 -0
  27. data/rdoc/messages_joint.rb +34 -0
  28. data/rdoc/messages_joint.rdoc +18 -0
  29. data/rdoc/messages_joint/private_messages.rdoc +59 -0
  30. data/rdoc/messages_joint/public_messages.rdoc +65 -0
  31. data/rdoc/orm_rest_convenience_plugin.rb +28 -0
  32. data/rdoc/orm_rest_convenience_plugin.rdoc +99 -0
  33. data/rdoc/plugins.rdoc +35 -0
  34. data/rdoc/reggae.rb +3 -0
  35. data/rdoc/reggae.rdoc +11 -0
  36. data/rdoc/reggae/generate.rdoc +13 -0
  37. data/rdoc/reggae/parse.rdoc +44 -0
  38. data/rdoc/section_joint.rb +13 -0
  39. data/rdoc/section_joint.rdoc +19 -0
  40. data/rdoc/tags_joint.rb +16 -0
  41. data/rdoc/tags_joint.rdoc +22 -0
  42. data/rdoc/tags_joint/announcements.rdoc +64 -0
  43. data/rdoc/tags_joint/secrets.rdoc +65 -0
  44. data/rdoc/user_joint.rb +62 -0
  45. data/rdoc/user_joint.rdoc +13 -0
  46. data/rdoc/user_joint/exiting_users.rdoc +120 -0
  47. data/rdoc/user_joint/no_auth_provided.rdoc +34 -0
  48. data/reggae.ebnf +1 -1
  49. metadata +45 -37
  50. data/Favicon.ico +0 -0
  51. data/Rakefile +0 -14
  52. data/TODO +0 -19
  53. data/examples/blog.rb +0 -26
  54. data/examples/empty.sqlite3 +0 -0
  55. data/examples/forum.css +0 -3
  56. data/examples/forum.js +0 -23
  57. data/examples/forum.rb +0 -20
  58. data/examples/forum.sql +0 -42
  59. data/examples/forum.sqlite3 +0 -0
  60. data/examples/forum_test.sqlite3 +0 -0
  61. data/examples/run.sh +0 -14
  62. data/lib/client/jamaica.css +0 -270
  63. data/lib/client/jamaica.js +0 -353
  64. data/lib/client/jamaica.rb +0 -38
  65. data/lib/client/jquery-1.6.2.js +0 -8981
  66. data/lib/client/jquery.form.js +0 -814
  67. data/lib/joints/basic_menu_system.rb +0 -41
  68. data/lib/joints/basic_messaging.rb +0 -88
  69. data/lib/joints/basic_user.rb +0 -51
  70. data/lib/joints/tagged_messaging.rb +0 -122
  71. data/lib/joints/tagging.rb +0 -56
  72. data/lib/sequel/plugins/rest_auth.rb +0 -53
  73. data/lib/sequel/plugins/rest_convenience.rb +0 -87
  74. data/marley-0.4.0.gem +0 -0
  75. data/marley.gemspec +0 -17
  76. data/test/empty.sqlite3 +0 -0
  77. data/test/menu_tests.rb +0 -9
  78. data/test/tagged_messaging_tests.rb +0 -289
  79. data/test/test.sqlite3 +0 -0
  80. data/test/test_include.rb +0 -16
  81. data/test/user_tests.rb +0 -72
data/README.rdoc CHANGED
@@ -4,12 +4,9 @@ Marley is a framework for quickly building RESTful web services and applications
4
4
 
5
5
  * A simple Rack application that acts as request parser/router.
6
6
  * A default controller for ORM model classes.
7
- * Two plugins for the Sequel ORM.
8
- * RestConvenience - Connects the class to a default controller.
9
- * RestAuthorization - Adds default authorization methods to a model class and its instances.
7
+ * A plugin system similar to Sequel's.
10
8
  * Marley Joints - A framework for creating reusable Marley resource sets.
11
9
  * Reggae - A JSON data format
12
- * Jamaica - A minimal JS/jQuery client which renders Reggae resources as browser DOM objects.
13
10
 
14
11
  The point of the whole thing is to minimize the amount of non-model (non-datacentric) code that needs to be written to implement a web service/application.
15
12
 
@@ -34,9 +31,7 @@ if the resource has a REST method corresponding the the request verb, the parser
34
31
 
35
32
  The parser also traps various errors and returns the appropriate error message to the client.
36
33
 
37
- == The Sequel plugins
38
-
39
- Marley activates both of them for Sequel::Model. This will soon change to an option.
34
+ :include: rdoc/plugins.rdoc
40
35
 
41
36
  ==The Default Model Controller
42
37
 
@@ -49,5 +44,5 @@ One of the things that the RestConvenience Sequel plugin does is add a #controll
49
44
 
50
45
  ==Jamaica
51
46
 
52
- The default Marley client is "Jamaica", which consists of JS/CSS for browsers. It sucks right now and I'm hoping somebody takes it over as a sub-project, but it does work - at least on FF.
47
+ The default Marley client is "Jamaica", which consists of JS/CSS for browsers. It has now been moved to a separate repository at https://github.com/herbdaily/jamaica
53
48
 
data/lib/marley.rb CHANGED
@@ -1,37 +1,32 @@
1
- #!/usr/bin/ruby
1
+ #!/usr/bin/env ruby
2
2
  require 'json/ext'
3
- require 'json/add/core'
4
- require 'marley/reggae'
5
- require 'marley/utils'
6
3
  require 'rack'
7
- require 'rack/auth/basic'
8
4
  require 'rack/builder'
5
+ require 'logger'
9
6
  require 'sequel'
10
- require 'sequel/plugins/rest_convenience'
11
- require 'sequel/plugins/rest_auth'
7
+ require 'marley/core_ext'
8
+ require 'marley/utils'
9
+ require 'marley/reggae'
10
+ require 'marley/errors'
11
+ require 'marley/router'
12
+ require 'marley/resources'
12
13
  require 'marley/controllers'
13
- require 'logger'
14
+ require 'marley/joint'
15
+ require 'marley/plugin'
16
+ require 'client/jamaica' #should prob be ditched
14
17
  Sequel.extension :inflector
15
18
 
16
- Sequel::Model.plugin :rest_convenience
17
- Sequel::Model.plugin :rest_authorization
18
- Sequel::Model.plugin :validation_helpers
19
- Sequel::Plugins::ValidationHelpers::DEFAULT_OPTIONS.merge!(:presence => {:message => 'is required'})
20
- Sequel::Model.plugin :timestamps, :create => :date_created, :update => :date_updated
21
-
22
19
  log_fn='log/marley.log'
23
20
  $log=Logger.new(File.exists?(log_fn) ? log_fn : $stdout)
24
21
 
25
22
  module Marley
26
- JOINT_DIRS=[File.expand_path("joints/",File.dirname(__FILE__)),"#{Dir.pwd}/joints"]
27
- DEFAULT_OPTS={:http_auth => true,:app_name => 'Application',:port => 1620,:default_user_class => :User, :auth_class => :User,:default_resource => 'Menu', :server => 'thin'}
28
- RESP_CODES={'get' => 200,'post' => 201,'put' => 204,'delete' => 204}
29
-
30
- module Resources
31
- end
32
- module Joints
33
- end
34
- require 'marley/joint' #this needs to happen after Marley::Resources is defined
23
+ DEFAULT_OPTS={
24
+ :app_name => 'Application',
25
+ :port => 1620,
26
+ :default_resource => 'MainMenu',
27
+ :server => 'thin',
28
+ :client => Marley::Client.new}
29
+ RESP_CODES={'get' => 200,'post' => 201,'put' => 203,'delete' => 204}
35
30
 
36
31
  def self.config(opts=nil)
37
32
  @marley_opts||=DEFAULT_OPTS
@@ -40,123 +35,24 @@ module Marley
40
35
  @marley_opts
41
36
  end
42
37
 
38
+ def self.plugin(plugin_name, *opts)
39
+ Plugins.const_get(plugin_name.to_s.camelize).new(*opts)
40
+ end
43
41
  def self.joint(joint_name, *opts)
44
- unless Marley::Joints.constants.include?(joint_name.camelize)
45
- joint_d=JOINT_DIRS.find {|d| File.exists?("#{d}/#{joint_name}.rb") }
46
- require "#{joint_d}/#{joint_name}"
47
- @marley_opts && @marley_opts[:client] && @marley_opts[:client].joint(joint_d,joint_name)
48
- end
49
- Marley::Joints.const_get(joint_name.camelize).new(*opts).smoke
42
+ Joints.const_get(joint_name.to_s.camelize).new(*opts).smoke
50
43
  end
51
44
 
52
45
  def self.run(opts={})
53
- @marley_opts||=DEFAULT_OPTS
54
- marley_opts=@marley_opts.merge!(opts)
46
+ marley_opts=self.config(opts)
55
47
  Rack::Handler.get(marley_opts[:server]).run(Rack::Builder.new {
56
48
  use Rack::Reloader,0
57
49
  use Rack::Static, :urls => [opts[:image_path]] if opts[:image_path]
58
50
  run(Marley::Router.new(marley_opts))
59
51
  }.to_app,{:Port => @marley_opts[:port]})
60
52
  end
61
- class Router
62
- def initialize(opts={},app=nil)
63
- @opts=DEFAULT_OPTS.merge(opts)
64
- end
65
- def call(env)
66
- request= Rack::Request.new(env)
67
- @auth = Rack::Auth::Basic::Request.new(env)
68
- $request={:request => request,:opts => @opts}
69
- $request[:get_params]=Marley::Utils.hash_keys_to_syms(request.GET)
70
- $request[:post_params]=Marley::Utils.hash_keys_to_syms(request.POST)
71
- $request[:content_type]=request.xhr? ? 'application/json' : env['HTTP_ACCEPT'].to_s.sub(/,.*/,'')
72
- $request[:content_type]='text/html' unless $request[:content_type] > ''
73
- $request[:content_type]='application/json' if env['rack.test']==true #there has to be a better way to do this...
74
- if @opts[:http_auth]
75
- if (@auth.provided? && @auth.basic? && @auth.credentials)
76
- $request[:user]=Resources.const_get(@opts[:auth_class]).authenticate(@auth.credentials)
77
- raise AuthenticationError unless $request[:user]
78
- else
79
- $request[:user]=Resources.const_get(@opts[:default_user_class]).new
80
- end
81
- end
82
- $request[:path]=request.path.sub(/\/\/+/,'/').split('/')[1..-1]
83
- verb=request.request_method.downcase
84
- verb=$request[:post_params].delete(:_method).match(/^(put|delete)$/i)[1] rescue verb
85
- $request[:verb]="rest_#{verb}"
86
- rn=$request[:path] ? $request[:path][0].camelize : @opts[:default_resource]
87
- raise RoutingError unless Resources.constants.include?(rn)
88
- @resource=Resources.const_get(rn)
89
- raise AuthenticationError if @opts[:http_auth] && @resource.respond_to?('requires_user?') && @resource.requires_user? && $request[:user].new?
90
- @controller=nil
91
- @controller=@resource.controller if @resource.respond_to?(:controller)
92
- @controller=@resource if @resource.respond_to?($request[:verb])
93
- raise RoutingError unless @controller
94
- json=@controller.send($request[:verb]).to_json
95
- html=@opts[:client] ? @opts[:client].to_s(json) : json
96
- resp_code=RESP_CODES[verb]
97
- headers||={'Content-Type' => "#{$request[:content_type]}; charset=utf-8"}
98
- [resp_code,headers,$request[:content_type].match(/json/) ? json : html]
99
- rescue Sequel::ValidationFailed
100
- ValidationError.new($!.errors).to_a
101
- rescue
102
- if $!.class.superclass==MarleyError
103
- $!.to_a
104
- else
105
- p $!,$!.backtrace
106
- end
107
- ensure
108
- $log.info $request.merge({:request => nil,:user => $request[:user] ? $request[:user].name : nil})
109
- end
110
- end
111
- class MarleyError < StandardError
112
- class << self
113
- attr_accessor :resp_code,:headers,:description,:details
114
- end
115
- @resp_code=500
116
- def initialize
117
- self.class.details=self.backtrace
118
- end
119
- def log_error
120
- $log.fatal("#$!.message}\n#{$!.backtrace}")
121
- end
122
- def to_a
123
- log_error
124
- json=[:error,{:error_type => self.class.name.underscore.sub(/_error$/,'').sub(/^marley\//,''),:description => self.class.description, :error_details => self.class.details}].to_json
125
- self.class.headers||={'Content-Type' => "#{$request[:content_type]}; charset=utf-8"}
126
- [self.class.resp_code,self.class.headers,json]
127
- end
128
- end
129
- class ValidationError < MarleyError
130
- @resp_code=400
131
- def initialize(errors)
132
- self.class.details=errors
133
- end
134
- def log_error
135
- $log.error(self.class.details)
136
- end
137
- end
138
- class AuthenticationError < MarleyError
139
- @resp_code=401
140
- @headers={'WWW-Authenticate' => %(Basic realm="Application")}
141
- def log_error
142
- $log.error("Authentication failed for #{@auth.credentials[0]}") if (@auth && @auth.provided? && @auth.basic? && @auth.credentials)
143
- end
144
- end
145
- class AuthorizationError < MarleyError
146
- @resp_code=403
147
- @description='You are not authorized for this operation'
148
- def log_error
149
- $log.error("Authorizationt Error:#{self.message}")
150
- end
151
- end
152
- class RoutingError < MarleyError
153
- @resp_code=404
154
- @description='Not Found'
155
- def log_error
156
- $log.fatal("path:#{$request[:path]}\n msg:#{$!.message}\n backtrace:#{$!.backtrace}")
157
- end
158
- end
159
53
  end
160
- MR=Marley::Resources
161
- MJ=Marley::Joints
54
+ MR=Marley::Resources
55
+ MJ=Marley::Joints
56
+ MP=Marley::Plugins
57
+ MU=Marley::Utils
162
58
  at_exit {Marley.run if ARGV[0]=='run'}
@@ -5,6 +5,7 @@ module Marley
5
5
  @model=model
6
6
  if $request[:path][1].to_s.match(/^\d+$/) #references a specific instance by ID
7
7
  @instance=@model[$request[:path][1].to_i]
8
+ raise RoutingError unless @instance
8
9
  @method_name=$request[:path][2]
9
10
  if @method_name
10
11
  raise RoutingError unless @instance.respond_to?(@method_name)
@@ -35,24 +36,28 @@ module Marley
35
36
  def rest_post
36
37
  if @instance
37
38
  raise RoutingError unless @method
38
- params=$request[:post_params][@model.resource_name.to_sym][@method_name.to_sym] || $request[:post_params][@method_name.to_sym]
39
+ params=$request[:post_params][@method_name.to_sym] || $request[:post_params][@model.resource_name.to_sym][@method_name.to_sym]
39
40
  raise ValidationFailed unless params
40
41
  params=[params] unless params.class==Array
41
42
  params.map do |param|
42
43
  @instance.send("add_#{@method_name}",param)
43
44
  end
44
45
  else
45
- @instance=@model.new($request[:post_params][@model.resource_name.to_sym] || {})
46
- @instance.save(@instance.write_cols)
46
+ params=($request[:post_params][@model.resource_name.to_sym]||{})
47
+ @instance=@model.new(params.reject {|k,v| v.nil?}) #reject nils to work around sequel validation flaw
48
+ raise AuthorizationError if params.keys.find {|k| ! @instance.write_cols.include?(k) }
49
+ params.keys.each {|k| @instance.send("#{k.to_s}=",params[k]) if k.to_s.match(/^_/)}
50
+ @instance.save
47
51
  @instance.respond_to?('create_msg') ? @instance.create_msg : @instance
48
52
  end
49
53
  end
50
54
  def rest_put
51
55
  raise RoutingError unless @instance
52
- (@instances || [@instance]).map do |i|
53
- i.modified!
54
- i.update_only($request[:post_params][@model.resource_name.to_sym],i.write_cols)
55
- end
56
+ params=($request[:post_params][@model.resource_name.to_sym]||{})
57
+ raise AuthorizationError if params.keys.find {|k| ! @instance.write_cols.include?(k) }
58
+ params.keys.each {|k| @instance.send("#{k.to_s}=",params[k]) if k.to_s.match(/^_/)}
59
+ @instance.modified!
60
+ @instance.update_only(params,@instance.write_cols)
56
61
  end
57
62
  def rest_delete
58
63
  raise RoutingError unless @instance
@@ -0,0 +1,8 @@
1
+ class Object
2
+ def send_or_default(meth, default)
3
+ respond_to?(meth) ? send(meth) : default
4
+ end
5
+ def send_or_nil(meth)
6
+ send_or_default(meth,nil)
7
+ end
8
+ end
@@ -0,0 +1,51 @@
1
+ module Marley
2
+ class MarleyError < StandardError
3
+ class << self
4
+ attr_accessor :resp_code,:headers,:description,:details
5
+ end
6
+ @resp_code=500
7
+ def initialize
8
+ self.class.details=self.backtrace
9
+ end
10
+ def log_error
11
+ $log.fatal("#$!.message}\n#{$!.backtrace}")
12
+ end
13
+ def to_a
14
+ log_error
15
+ json=[:error,{:error_type => self.class.name.underscore.sub(/_error$/,'').sub(/^marley\//,''),:description => self.class.description, :error_details => self.class.details}].to_json
16
+ self.class.headers||={'Content-Type' => "#{$request[:content_type]}; charset=utf-8"}
17
+ [self.class.resp_code,self.class.headers,json]
18
+ end
19
+ end
20
+ class ValidationError < MarleyError
21
+ @resp_code=400
22
+ def initialize(errors)
23
+ self.class.details=errors
24
+ end
25
+ def log_error
26
+ $log.error(self.class.details)
27
+ end
28
+ end
29
+ class AuthenticationError < MarleyError
30
+ @resp_code=401
31
+ @headers={'WWW-Authenticate' => %(Basic realm="Application")}
32
+ def log_error
33
+ $log.error("Authentication failed for #{@auth.credentials[0]}") if (@auth && @auth.provided? && @auth.basic? && @auth.credentials)
34
+ end
35
+ end
36
+ class AuthorizationError < MarleyError
37
+ @resp_code=403
38
+ @description='You are not authorized for this operation'
39
+ def log_error
40
+ $log.error("Authorizationt Error:#{self.message}")
41
+ end
42
+ end
43
+ class RoutingError < MarleyError
44
+ @resp_code=404
45
+ @description='Not Found'
46
+ def log_error
47
+ $log.fatal("path:#{$request[:path]}\n msg:#{$!.message}\n backtrace:#{$!.backtrace}")
48
+ end
49
+ end
50
+ end
51
+
data/lib/marley/joint.rb CHANGED
@@ -1,35 +1,30 @@
1
1
  module Marley
2
2
  module Joints
3
- # @abstract Subclass and implement one or more of the following modules within the class namespace:
4
- # - Resources - All constansts in this module will be imported into Marley::Resources
5
- # - ClassMethods - Modules within this module will extend any constant in Marley::Resources with the same name.
6
- # - InstanceMethods - Modules within this module will append their features to any constant in Marley::Resources with the same name.
3
+ Gem.find_files("lib/marley/joints/*.rb").each do |f|
4
+ self.autoload(File.basename(f,'.rb').camelize, f)
5
+ end
7
6
  class Joint
8
- MODS=['Resources','Plugins']
9
- MODS.each {|mod| const_set(mod,Module.new)}
10
- def self.mods
11
- MODS.map {|mod_name| self.const_get(mod_name) }
12
- end
7
+ extend Marley::Utils::ClassAttrs
8
+ class_attr(:default_opts,{})
9
+ attr_accessor :opts
13
10
  def initialize(opts={})
14
11
  config(opts)
15
12
  end
16
13
  def smoke
17
- self.class::Resources.constants.each do |resource_name|
18
- MR.const_set(resource_name, self.class::Resources.const_get(resource_name)) unless (@opts[:resources] && ! @opts[:resources].include?(resource_name))
14
+ @opts[:required_joints].to_a.each do |j|
15
+ Marley.joint(j)
19
16
  end
20
- self.class.constants.grep(/.+Plugin$/).each do |plugin_name|
21
- plugin=self.class.const_get(plugin_name)
22
- resource_name=plugin_name.sub(/Plugin$/,'')
23
- if MR.constants.include?(resource_name)
24
- resource=MR.const_get(resource_name)
25
- plugin.constants.include?('ClassMethods') && resource.extend(plugin.const_get('ClassMethods'))
26
- plugin.constants.include?('InstanceMethods') && plugin.const_get('InstanceMethods').send(:append_features,resource)
17
+ resources=self.class::Resources
18
+ resources.constants.each do |resource_name|
19
+ @opts[:plugins].to_a.each do |plugin_name|
20
+ Marley.plugin(plugin_name).apply(resources.const_get(resource_name))
27
21
  end
22
+ MR.const_set(resource_name, resources.const_get(resource_name)) unless (@opts[:resources] && ! @opts[:resources].include?(resource_name) )
28
23
  end
29
24
  self
30
25
  end
31
26
  def config(opts)
32
- @opts=(@opts || {}).merge(opts)
27
+ @opts=(@opts || self.class.default_opts || {}).merge(opts)
33
28
  end
34
29
  end
35
30
  end
@@ -0,0 +1,88 @@
1
+
2
+ module Marley
3
+ module Plugins
4
+ class MessageThreading < Plugin
5
+ module ClassMethods
6
+ def topics(params=nil)
7
+ filters=[]
8
+ if params && params[:tags]
9
+ filters << {:id => MR::Tag.join(:messages_tags, :tag_id => :id).select(:message_id).filter(:tag => params[:tags])}
10
+ end
11
+ filters.inject(self.dataset.filter(:parent_id => nil)) {|ds,f| ds.filter(f)}
12
+ end
13
+ def list(params=nil)
14
+ (params.is_a?(Sequel::Dataset) ? params : topics(params)).map{|t| t.thread}
15
+ end
16
+ end
17
+ module InstanceMethods
18
+ def write_cols
19
+ super.push(:topic_id, :parent_id)
20
+ end
21
+ def children
22
+ self.class.filter(:parent_id => id).all
23
+ end
24
+ def thread
25
+ return reggae_instance if children.length==0
26
+ foo=reggae_instance
27
+ foo[2] = children.map{|m| m.thread}
28
+ foo
29
+ end
30
+ def before_save
31
+ super
32
+ self.topic_id||=self.class.max(:topic_id).to_i+1
33
+ end
34
+ def reply
35
+ foo=reggae_instance.set_values(:parent_id => id,:title => "re: #{title}")
36
+ foo.new_rec=true
37
+ foo.url=self.class.new.url
38
+ foo.schema.delete_if {|c| [:author,:id].include?(c[NAME_INDEX])}
39
+ foo
40
+ end
41
+ end
42
+ end
43
+ class MessageNav < Plugin
44
+ module ClassMethods
45
+ def section_nav
46
+ [
47
+ self.reggae_link(:new, 'New Post'),
48
+ self.reggae_link(:list, 'All Posts'),
49
+ self.reggae_link(:recent_topics, 'Recent Topics')
50
+ ].push(
51
+ Marley::ReggaeMsg.new({
52
+ :title => 'Topics Tagged With:',
53
+ :description => MR::Tag.filter(:id => topics.join(:messages_tags).where(:messages__id => :message_id).select(:tag_id)).map{|t| reggae_link('list',t.tag,"#{resource_name}[tags]=#{t.tag}")}})
54
+ )
55
+ end
56
+ def section_contents
57
+ end
58
+ def recent_topics
59
+ list(:date_created > Date.today - 2)
60
+ end
61
+ end
62
+ end
63
+ end
64
+ module Joints
65
+ class Forum < Joint
66
+ Marley.plugin('orm_rest_convenience').apply(Sequel::Model)
67
+ Marley.joint('user')
68
+ Marley.joint('messages',{:tags => true})
69
+ Marley.joint('section')
70
+ Marley.plugin(:section).apply('PrivateMessage')
71
+ Marley.plugin(:section).apply('PublicMessage')
72
+ Marley.plugin(:message_threading).apply('PublicMessage')
73
+ Marley.plugin(:message_nav).apply('PublicMessage')
74
+ class << MR::PublicMessage
75
+ def section_title;'Public Forums';end
76
+ end
77
+ module Resources
78
+ class Topic < MJ::Messages::Message
79
+ end
80
+ class TopicTag < MR::Tag
81
+ end
82
+ class Admin < MR::User
83
+ def self.requires_user?; true;end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end