marley 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/Favicon.ico +0 -0
- data/README.rdoc +51 -0
- data/TODO +17 -0
- data/examples/empty.sqlite3 +0 -0
- data/examples/forum.css +3 -0
- data/examples/forum.js +23 -0
- data/examples/forum.rb +20 -0
- data/examples/forum.sql +42 -0
- data/examples/forum.sqlite3 +0 -0
- data/examples/forum_test.sqlite3 +0 -0
- data/examples/run.sh +14 -0
- data/lib/client/jamaica.css +270 -0
- data/lib/client/jamaica.js +353 -0
- data/lib/client/jamaica.rb +38 -0
- data/lib/client/jquery-1.6.2.js +8981 -0
- data/lib/client/jquery.form.js +814 -0
- data/lib/controllers.rb +69 -0
- data/lib/joint.rb +27 -0
- data/lib/joints/basic_menu_system.rb +54 -0
- data/lib/joints/basic_messaging.rb +88 -0
- data/lib/joints/basic_user.rb +51 -0
- data/lib/joints/tagged_messaging.rb +122 -0
- data/lib/joints/tagging.rb +60 -0
- data/lib/joints/user_based_navigation.rb +39 -0
- data/lib/marley.rb +148 -0
- data/lib/reggae.rb +110 -0
- data/lib/sequel_plugins.rb +141 -0
- data/lib/test_helpers.rb +52 -0
- data/marley-0.1.gem +0 -0
- data/marley.gemspec +16 -0
- data/reggae.ebnf +60 -0
- data/test/forum_tests.rb +356 -0
- metadata +116 -0
data/lib/controllers.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
module Marley
|
2
|
+
class ModelController
|
3
|
+
def initialize(model)
|
4
|
+
@model=model
|
5
|
+
if $request[:path][1].to_s.match(/^\d+$/) #references a specific instance by ID
|
6
|
+
@instance=@model[$request[:path][1].to_i]
|
7
|
+
@method_name=$request[:path][2]
|
8
|
+
if @method_name
|
9
|
+
raise RoutingError unless @instance.respond_to?(@method_name)
|
10
|
+
@method=@instance.method(@method_name)
|
11
|
+
end
|
12
|
+
else #class method -- should yield 0 or more instances of model in an array
|
13
|
+
@method_name=$request[:path][1]
|
14
|
+
@method_name='list' if @method_name.nil? && $request[:verb]=='rest_get'
|
15
|
+
if @method_name
|
16
|
+
raise RoutingError unless @model.respond_to?(@method_name)
|
17
|
+
@method=@model.method(@method_name)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
if (a=@instance || @model).requires_user?
|
21
|
+
raise AuthorizationError unless a.authorize(@method_name)
|
22
|
+
end
|
23
|
+
if @method && $request[:verb] != 'rest_post'
|
24
|
+
@instances=if p=$request[:get_params][@model.resource_name.to_sym]
|
25
|
+
@method.call(p)
|
26
|
+
elsif i=$request[:path][3]
|
27
|
+
@method.call[i.to_i]
|
28
|
+
else
|
29
|
+
@method.call
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
def rest_get; @instances || @instance; end
|
34
|
+
def rest_post
|
35
|
+
if @instance
|
36
|
+
raise RoutingError unless @method
|
37
|
+
params=$request[:post_params][@model.resource_name.to_sym][@method_name.to_sym] || $request[:post_params][@method_name.to_sym]
|
38
|
+
raise ValidationFailed unless params
|
39
|
+
params=[params] unless params.class==Array
|
40
|
+
params.map do |param|
|
41
|
+
@instance.send("add_#{@method_name}",param)
|
42
|
+
end
|
43
|
+
else
|
44
|
+
@instance=@model.new($request[:post_params][@model.resource_name.to_sym] || {})
|
45
|
+
@instance.save(@instance.write_cols)
|
46
|
+
@instance.respond_to?('create_msg') ? @instance.create_msg : @instance
|
47
|
+
end
|
48
|
+
end
|
49
|
+
def rest_put
|
50
|
+
raise RoutingError unless @instance
|
51
|
+
(@instances || [@instance]).map do |i|
|
52
|
+
i.modified!
|
53
|
+
i.update_only($request[:post_params][@model.resource_name.to_sym],i.write_cols)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
def rest_delete
|
57
|
+
raise RoutingError unless @instance
|
58
|
+
if @instances
|
59
|
+
@instances.each do |instance|
|
60
|
+
meth="remove_#{instance.class}"
|
61
|
+
raise RoutingError unless @instance.respond_to?(meth)
|
62
|
+
@instance.send(meth,instance)
|
63
|
+
end
|
64
|
+
else
|
65
|
+
@instance.destroy
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/joint.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
module Marley
|
2
|
+
module Joints
|
3
|
+
MR=Marley::Resources
|
4
|
+
MJ=Marley::Joints
|
5
|
+
class Joint
|
6
|
+
def initialize(opts={})
|
7
|
+
config(opts)
|
8
|
+
end
|
9
|
+
def smoke
|
10
|
+
klass=self.class
|
11
|
+
{ 'resources' => lambda {|c| MR.const_set(c,klass::Resources.const_get(c))},
|
12
|
+
'class_methods' => lambda {|c| MR.const_get(c).extend klass::ClassMethods.const_get(c)},
|
13
|
+
'instance_methods' => lambda {|c| klass::InstanceMethods.const_get(c).send :append_features, MR.const_get(c)}
|
14
|
+
}.each_pair do |mod_name, importer|
|
15
|
+
if klass.constants.include?(mod_name.camelize)
|
16
|
+
klass.const_get(mod_name.camelize).constants.each do |c|
|
17
|
+
importer.call(c) unless @opts[mod_name.to_sym] && ! @opts[mod_name.to_sym].include?(c.underscore)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
def config(opts)
|
23
|
+
@opts=(@opts || {}).merge(opts)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
|
2
|
+
module Sequel::Plugins::RestSection
|
3
|
+
SECTION_PROPS='name','title','description','navigation'
|
4
|
+
module ClassMethods
|
5
|
+
SECTION_PROPS.each {|p| attr_accessor :"section_#{p}"}
|
6
|
+
def section
|
7
|
+
if SECTION_PROPS.find {|p| send(:"section_#{p}").to_s > ''}
|
8
|
+
Marley::ReggaeSection.new [SECTION_PROPS.inject({}) {|props,p| props[p.to_sym]=send(:"section_#{p}");props }]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
Sequel::Model.plugin :rest_section
|
14
|
+
module Marley
|
15
|
+
module Joints
|
16
|
+
class BasicMenuSystem < Joint
|
17
|
+
module Resources
|
18
|
+
class Menu
|
19
|
+
class <<self
|
20
|
+
attr_accessor :sections
|
21
|
+
end
|
22
|
+
attr_accessor :title,:name,:description, :navigation
|
23
|
+
def self.rest_get
|
24
|
+
new.to_json
|
25
|
+
end
|
26
|
+
def self.requires_user?
|
27
|
+
! $request[:path].to_a.empty?
|
28
|
+
end
|
29
|
+
def initialize
|
30
|
+
@name='main'
|
31
|
+
if $request[:user].new?
|
32
|
+
u=$request[:user].to_a
|
33
|
+
u[1].merge!({:description => 'If you don\'t already have an account, please create one here:'})
|
34
|
+
@title="Welcome to #{$request[:opts][:app_name]}"
|
35
|
+
@description='Login or signup here.'
|
36
|
+
@navigation=[LOGIN_FORM,u]
|
37
|
+
else
|
38
|
+
@title = "#{$request[:opts][:app_name]} Main Menu"
|
39
|
+
@description="Welcome to #{$request[:opts][:app_name]}, #{$request[:user].name}"
|
40
|
+
@navigation=(self.class.sections || MR.constants).map do |rn|
|
41
|
+
if (resource=MR.const_get(rn)).respond_to?(:section) && (s=resource.section)
|
42
|
+
[:link,{:title => s.title, :description =>s.description, :url => "#{resource.resource_name}/section" }]
|
43
|
+
end
|
44
|
+
end.compact
|
45
|
+
end
|
46
|
+
end
|
47
|
+
def to_json
|
48
|
+
[:section,{:title => @title,:description => @description,:name => @name, :navigation => @navigation}]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'sanitize'
|
2
|
+
|
3
|
+
module Marley
|
4
|
+
module Joints
|
5
|
+
class BasicMessaging < Joint
|
6
|
+
module Resources
|
7
|
+
class Message < Sequel::Model
|
8
|
+
plugin :single_table_inheritance, :message_type, :model_map => lambda{|v| v ? MR.const_get(v.to_s) : ''}, :key_map => lambda{|klass|klass.name.sub(/.*::/,'')}
|
9
|
+
plugin :tree
|
10
|
+
many_to_one :author, :class => :'Marley::Resources::User'
|
11
|
+
@owner_col=:author_id
|
12
|
+
def rest_cols; [:id,:author_id,:message,:title,:parent_id]; end
|
13
|
+
def write_cols; new? ? rest_cols - [:id] : []; end
|
14
|
+
def required_cols; write_cols - [:parent_id]; end
|
15
|
+
def rest_schema
|
16
|
+
super << [:text,:author,RESTRICT_RO,author.to_s]
|
17
|
+
end
|
18
|
+
def authorize_rest_get(meth)
|
19
|
+
current_user_role && (meth.nil? || get_actions.include?(meth))
|
20
|
+
end
|
21
|
+
def authorize_rest_put(meth); false; end
|
22
|
+
def after_initialize
|
23
|
+
super
|
24
|
+
if new?
|
25
|
+
self.author_id=$request[:user][:id]
|
26
|
+
self.thread_id=parent ? parent.thread_id : Message.select(:max.sql_function(:thread_id).as(:tid)).all[0][:tid].to_i + 1
|
27
|
+
end
|
28
|
+
end
|
29
|
+
def before_save
|
30
|
+
self.message=Sanitize.clean(self.message,:elements => %w[blockquote em strong ul ol li p code])
|
31
|
+
end
|
32
|
+
def validate
|
33
|
+
validates_presence [:author,:message,:title]
|
34
|
+
validates_type MR::User, :author
|
35
|
+
end
|
36
|
+
def thread
|
37
|
+
children.length > 0 ? to_a << children.map{|m| m.thread} : to_a
|
38
|
+
end
|
39
|
+
end
|
40
|
+
class PrivateMessage < Message
|
41
|
+
def get_actions;['reply','reply_all'];end
|
42
|
+
def rest_cols; super << :recipients; end
|
43
|
+
def current_user_role
|
44
|
+
super || (recipients.match(/\b#{$request[:user][:name]}\b/) && "recipient")
|
45
|
+
end
|
46
|
+
def authorize_rest_get(meth)
|
47
|
+
super && ($request[:user]==author || self.recipients.match(/\b#{$request[:user].name}\b/))
|
48
|
+
end
|
49
|
+
def authorize_rest_post(meth)
|
50
|
+
meth.to_s > '' && (author_id==$request[:user][:id] || recipients.match(/\b#{$request[:user][:name]}\b/))
|
51
|
+
end
|
52
|
+
def self.authorize_rest_post(asdf)
|
53
|
+
true #may need to change this, for now auth is handled in validation
|
54
|
+
end
|
55
|
+
def reply
|
56
|
+
self.class.new({:parent_id => self[:id],:author_id => $request[:user][:id],:recipients => author.name, :title => "re: #{title}"})
|
57
|
+
end
|
58
|
+
def reply_all
|
59
|
+
foo=reply
|
60
|
+
foo.recipients="#{author.name},#{recipients}".gsub(/\b(#{$request[:user][:name]})\b/,'').sub(',,',',')
|
61
|
+
foo
|
62
|
+
end
|
63
|
+
def validate
|
64
|
+
super
|
65
|
+
validates_presence [:recipients]
|
66
|
+
self.recipients.split(',').each do |recipient|
|
67
|
+
if u=MR::User[:name => recipient]
|
68
|
+
errors.add(:recipients, "You may only send PM's to Admins or Mods. #{recipient} is neither of those") unless (['Admin','Moderator'].include?(MR::User[:name => recipient].user_type) || [MR::Admin,MR::Moderator].include?($request[:user].class))
|
69
|
+
else
|
70
|
+
errors.add(:recipients, "Invalid user: #{recipient}")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
class Post < Message
|
76
|
+
def get_actions;['reply'];end
|
77
|
+
def current_user_role
|
78
|
+
super || 'reader'
|
79
|
+
end
|
80
|
+
def authorize_rest_post(meth);true;end
|
81
|
+
def reply
|
82
|
+
self.class.new({:parent_id => self[:id],:author_id => $request[:user][:id], :title => "re: #{title}"})
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
LOGIN_FORM= [:instance,{:url => 'login',:description => 'Existing users please log in here:',:new_rec => true,:schema => [[:text,'name',RESTRICT_REQ],[:password,'password',RESTRICT_REQ]]}]
|
3
|
+
module Marley
|
4
|
+
module Joints
|
5
|
+
class BasicUser < Joint
|
6
|
+
module Resources
|
7
|
+
class User < Sequel::Model
|
8
|
+
set_dataset :users
|
9
|
+
plugin :single_table_inheritance, :user_type, :model_map => lambda{|v| MR.const_get(v.to_sym)}, :key_map => lambda{|klass|klass.name.sub(/.*::/,'')}
|
10
|
+
attr_reader :menus
|
11
|
+
attr_accessor :old_password,:password, :confirm_password
|
12
|
+
def self.requires_user?
|
13
|
+
! ($request[:verb]=='rest_post')
|
14
|
+
end
|
15
|
+
def self.section
|
16
|
+
ReggaeSection.new([ {:title => 'User Info', :name => self.to_s.underscore, :navigation => []},$request[:user]]) if $request[:user].class == self
|
17
|
+
end
|
18
|
+
def write_cols;[:name,:email,:password,:confirm_password,:old_password];end
|
19
|
+
def rest_schema
|
20
|
+
schema=super.delete_if {|c| [:pw_hash,:description,:active].include?(c[NAME_INDEX])}
|
21
|
+
schema.push([:password,:old_password,0]) unless new?
|
22
|
+
schema.push([:password,:password ,new? ? RESTRICT_REQ : 0],[:password,:confirm_password,new? ? RESTRICT_REQ : 0])
|
23
|
+
schema
|
24
|
+
end
|
25
|
+
def self.authenticate(credentials)
|
26
|
+
u=find(:name => credentials[0], :pw_hash => Digest::SHA1.hexdigest(credentials[1]))
|
27
|
+
u.respond_to?(:user_type) ? Marley::Resources.const_get(u[:user_type].to_sym)[u[:id]] : u
|
28
|
+
end
|
29
|
+
def validate
|
30
|
+
super
|
31
|
+
validates_presence [:name]
|
32
|
+
validates_unique [:name]
|
33
|
+
if self.new? || self.old_password.to_s + self.password.to_s + self.confirm_password.to_s > ''
|
34
|
+
errors[:password]=['Password must contain at least 8 characters'] if self.password.to_s.length < 8
|
35
|
+
errors[:confirm_password]=['Passwords do not match'] unless self.password==self.confirm_password
|
36
|
+
errors[:old_password]=['Old Password Incorrect'] if !self.new? && Digest::SHA1.hexdigest(self.old_password.to_s) != self.pw_hash
|
37
|
+
end
|
38
|
+
end
|
39
|
+
def before_save
|
40
|
+
if self.new? || self.old_password.to_s + self.password.to_s + self.confirm_password.to_s > ''
|
41
|
+
self.pw_hash=Digest::SHA1.hexdigest(self.password)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
def create_msg
|
45
|
+
[[:msg,{:title => 'Success!'},"Your login, '#{self.name}', has been sucessfully created. You can now log in."]]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
Sequel::Plugins::ValidationHelpers::DEFAULT_OPTIONS.merge!(:presence => {:message => 'is required'})
|
2
|
+
Sequel::Model.plugin :timestamps, :create => :date_created, :update => :date_updated
|
3
|
+
|
4
|
+
Marley.joint 'basic_user',{:resources => []}
|
5
|
+
Marley.joint 'tagging'
|
6
|
+
Marley.joint 'basic_messaging',{:resources => ['message']}
|
7
|
+
module Marley
|
8
|
+
module Joints
|
9
|
+
class TaggedMessaging < Joint
|
10
|
+
def smoke
|
11
|
+
super
|
12
|
+
MR::Tag.tagging_for('PrivateMessage', 'User')
|
13
|
+
MR::Tag.tagging_for('Post', 'User')
|
14
|
+
MR::Tag.tagging_for('Post')
|
15
|
+
end
|
16
|
+
module ClassMethods
|
17
|
+
module Message
|
18
|
+
def list(params={})
|
19
|
+
if associations.include?(:public_tags)
|
20
|
+
specified_tags=params.delete(:tags)
|
21
|
+
specified_user_tags=params.delete(:user_tags)
|
22
|
+
else
|
23
|
+
specified_user_tags=params.delete(:tags)
|
24
|
+
end
|
25
|
+
tag_ids=MR::PublicTag.filter(:tag => specified_tags.split(/\s*,\s*/)).select(:id) if specified_tags
|
26
|
+
user_tag_ids=$request[:user].user_tags_dataset.filter(:tag => specified_user_tags.split(/\s*,\s*/)).select(:id) if specified_user_tags
|
27
|
+
items=filter(params)
|
28
|
+
#would love to make the following line more generic...
|
29
|
+
items=filter("author_id=#{$request[:user][:id]} or recipients like('%#{$request[:user][:name]}%')".lit) if new.rest_cols.include?(:recipients)
|
30
|
+
items=items.join(:messages_tags,:message_id => :id).filter(:tag_id => tag_ids) if specified_tags
|
31
|
+
items=items.join(:messages_tags,:message_id => :id).filter(:tag_id => user_tag_ids) if specified_user_tags
|
32
|
+
items.group(:thread_id).order(:max.sql_function(:date_created).desc,:max.sql_function(:date_updated).desc).map{|t|self[:parent_id => nil, :thread_id => t[:thread_id]].thread} rescue []
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
module InstanceMethods
|
37
|
+
module Message
|
38
|
+
def rest_associations
|
39
|
+
if ! new?
|
40
|
+
[ respond_to?(:public_tags) ? :public_tags : nil, respond_to?(:user_tags) ? user_tags_dataset.current_user_tags : nil].compact
|
41
|
+
end
|
42
|
+
end
|
43
|
+
def new_tags
|
44
|
+
[:instance,{:name => 'tags',:url => "#{url}tags", :new_rec => true, :schema => [['number','message_id',RESTRICT_HIDE,id],['text','tags',RESTRICT_REQ]]}]
|
45
|
+
end
|
46
|
+
def new_user_tags
|
47
|
+
[:instance,{:name => 'user_tags',:url => "#{url}user_tags", :new_rec => true, :schema => [['number','user_tags[message_id]',RESTRICT_HIDE,id],['text','user_tags[tags]',RESTRICT_REQ]]}]
|
48
|
+
end
|
49
|
+
def add_tags(tags,user=nil)
|
50
|
+
if respond_to?(:public_tags)
|
51
|
+
tags.to_s.split(',').each {|tag| add_public_tag(MR::PublicTag.find_or_create(:tag => tag))}
|
52
|
+
else
|
53
|
+
add_user_tags(tags,user)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
def add_user_tags(tags,user=nil) #does not conflict with add_user_tag
|
57
|
+
user||=$request[:user][:id]
|
58
|
+
if user.class==String
|
59
|
+
user.split(',').each {|u| add_user_tags(tags,MR::User[:name => u][:id])}
|
60
|
+
elsif user.class==Array
|
61
|
+
user.each {|u| add_user_tags(tags,u)}
|
62
|
+
elsif user.class==Fixnum
|
63
|
+
tags.to_s.split(',').each {|tag| add_user_tag(MR::UserTag.find_or_create(:user_id => user, :tag => tag))}
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
module Resources
|
69
|
+
class User < MJ::BasicUser::Resources::User
|
70
|
+
end
|
71
|
+
class Admin < User
|
72
|
+
def self.requires_user?;true;end
|
73
|
+
end
|
74
|
+
class Moderator < User
|
75
|
+
def self.requires_user?;true;end
|
76
|
+
end
|
77
|
+
class PrivateMessage < MJ::BasicMessaging::Resources::PrivateMessage
|
78
|
+
attr_accessor :tags
|
79
|
+
@section_title='Private Messages'
|
80
|
+
@section_name='pms'
|
81
|
+
def self.section_navigation
|
82
|
+
$request[:user].user_tags.map{|t| [:link,{:url => "/private_message?private_message[tag]=#{t.tag}",:title => t.tag.humanize}]}.unshift(PrivateMessage.reggae_link('new'))
|
83
|
+
end
|
84
|
+
def get_actions; super << 'new_tags';end
|
85
|
+
def rest_schema
|
86
|
+
super << [:text, :tags, 0,tags]
|
87
|
+
end
|
88
|
+
def reply
|
89
|
+
r=super
|
90
|
+
r.tags=(user_tags_dataset.current_user_tags.map{|t|t.tag} - RESERVED_PM_TAGS).join(',')
|
91
|
+
r
|
92
|
+
end
|
93
|
+
def after_create
|
94
|
+
add_user_tags("inbox,#{tags}",recipients)
|
95
|
+
add_user_tags("sent,#{recipients.match(/\b#{author.name}\b/) ? '' : tags}",author_id)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
class Post < MJ::BasicMessaging::Resources::Post
|
99
|
+
attr_accessor :tags,:my_tags
|
100
|
+
@section_title='Public Posts'
|
101
|
+
@section_name='posts'
|
102
|
+
def self.section_navigation
|
103
|
+
MR::Tag.filter(:user_id => nil).map{|t| [:link,{:url => "/post?post[tag]=#{t.tag}",:title => t.tag.humanize}]}.unshift([:link,{:url => '/post?post[untagged]=true',:title => 'Untagged Messages'}]).unshift(Post.reggae_link('new'))
|
104
|
+
end
|
105
|
+
def get_actions;(super << 'new_user_tags') << 'new_tags';end
|
106
|
+
def rest_schema
|
107
|
+
(super << [:text, :tags, 0,tags] ) << [:text, :my_tags, 0,my_tags]
|
108
|
+
end
|
109
|
+
def reply
|
110
|
+
r=super
|
111
|
+
r.tags=self.tags
|
112
|
+
r
|
113
|
+
end
|
114
|
+
def after_create
|
115
|
+
add_tags(tags) if tags
|
116
|
+
add_user_tags(my_tags) if my_tags
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Marley
|
2
|
+
module Joints
|
3
|
+
class Tagging < Joint
|
4
|
+
module Resources
|
5
|
+
class Tag < Sequel::Model
|
6
|
+
def self.tagging_for(klass, user_class=nil,join_table=nil)
|
7
|
+
current_user_tags=Module.new do
|
8
|
+
def current_user_tags
|
9
|
+
filter(:tags__user_id => $request[:user][:id])
|
10
|
+
end
|
11
|
+
end
|
12
|
+
tagged_class=Marley::Resources.const_get(klass.to_sym)
|
13
|
+
join_table||=:"#{tagged_class.table_name}_tags"
|
14
|
+
klass_key=:"#{tagged_class.table_name.to_s.singularize}_id"
|
15
|
+
tag_key=:tag_id
|
16
|
+
attr_accessor klass_key
|
17
|
+
if user_class
|
18
|
+
UserTag.many_to_many klass.underscore.to_sym,:class => "Marley::Resources::#{klass}", :join_table => join_table,:left_key => tag_key,:right_key => klass_key,:extend => current_user_tags
|
19
|
+
tagged_class.many_to_many :user_tags, :class => 'Marley::Resources::UserTag',:join_table => join_table,:left_key => klass_key,:right_key => tag_key, :extend => current_user_tags
|
20
|
+
Marley::Resources.const_get(user_class).one_to_many :user_tags, :class => 'Marley::Resources::UserTag'
|
21
|
+
UserTag.many_to_one user_class.underscore.to_sym,:class => "Marley::Resources::#{user_class}"
|
22
|
+
else
|
23
|
+
PublicTag.many_to_many klass.underscore.to_sym,:class => "Marley::Resources::#{klass}", :join_table => join_table,:left_key => tag_key,:right_key => klass_key
|
24
|
+
tagged_class.many_to_many :public_tags,:class => "Marley::Resources::PublicTag",:join_table => join_table,:left_key => klass_key,:right_key => tag_key
|
25
|
+
end
|
26
|
+
end
|
27
|
+
def to_a
|
28
|
+
a=super
|
29
|
+
a[1][:delete_action]='remove_parent'
|
30
|
+
a
|
31
|
+
end
|
32
|
+
def validate
|
33
|
+
validates_presence :tag
|
34
|
+
validates_unique [:tag,:user_id]
|
35
|
+
end
|
36
|
+
def before_save
|
37
|
+
super
|
38
|
+
self.tag.downcase!
|
39
|
+
self.tag.strip!
|
40
|
+
end
|
41
|
+
def after_save
|
42
|
+
super
|
43
|
+
assoc=methods.grep(/_id=$/) - ['user_id=']
|
44
|
+
assoc.each do |a|
|
45
|
+
if c=self.send(a.sub(/=$/,''))
|
46
|
+
send "add_#{a.sub(/_id=/,'')}", Marley::Resources.const_get(a.sub(/_id=/,'').camelize.to_sym)[c]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
class PublicTag < Tag
|
52
|
+
set_dataset DB[:tags].filter(:user_id => nil)
|
53
|
+
end
|
54
|
+
class UserTag < Tag
|
55
|
+
set_dataset DB[:tags].filter(~{:user_id => nil})
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|