marley 0.5.0 → 0.6.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/README.rdoc +3 -8
- data/lib/marley.rb +27 -131
- data/lib/marley/controllers.rb +12 -7
- data/lib/marley/core_ext.rb +8 -0
- data/lib/marley/errors.rb +51 -0
- data/lib/marley/joint.rb +14 -19
- data/lib/marley/joints/forum.rb +88 -0
- data/lib/marley/joints/messages.rb +86 -0
- data/lib/marley/joints/section.rb +63 -0
- data/lib/marley/joints/tags.rb +99 -0
- data/lib/marley/joints/user.rb +120 -0
- data/lib/marley/plugin.rb +39 -0
- data/lib/marley/plugins/orm_rest_convenience.rb +92 -0
- data/lib/marley/plugins/rest_convenience.rb +28 -0
- data/lib/marley/reggae.rb +5 -2
- data/lib/marley/resources.rb +10 -0
- data/lib/marley/router.rb +61 -0
- data/lib/marley/test_helpers.rb +12 -1
- data/lib/marley/utils.rb +57 -25
- data/rdoc/example_joint.rb +33 -0
- data/rdoc/example_plugin.rb +20 -0
- data/rdoc/forum_joint.rb +50 -0
- data/rdoc/forum_joint.rdoc +19 -0
- data/rdoc/hello.rb +14 -0
- data/rdoc/hello.rdoc +14 -0
- data/rdoc/joints.rdoc +69 -0
- data/rdoc/messages_joint.rb +34 -0
- data/rdoc/messages_joint.rdoc +18 -0
- data/rdoc/messages_joint/private_messages.rdoc +59 -0
- data/rdoc/messages_joint/public_messages.rdoc +65 -0
- data/rdoc/orm_rest_convenience_plugin.rb +28 -0
- data/rdoc/orm_rest_convenience_plugin.rdoc +99 -0
- data/rdoc/plugins.rdoc +35 -0
- data/rdoc/reggae.rb +3 -0
- data/rdoc/reggae.rdoc +11 -0
- data/rdoc/reggae/generate.rdoc +13 -0
- data/rdoc/reggae/parse.rdoc +44 -0
- data/rdoc/section_joint.rb +13 -0
- data/rdoc/section_joint.rdoc +19 -0
- data/rdoc/tags_joint.rb +16 -0
- data/rdoc/tags_joint.rdoc +22 -0
- data/rdoc/tags_joint/announcements.rdoc +64 -0
- data/rdoc/tags_joint/secrets.rdoc +65 -0
- data/rdoc/user_joint.rb +62 -0
- data/rdoc/user_joint.rdoc +13 -0
- data/rdoc/user_joint/exiting_users.rdoc +120 -0
- data/rdoc/user_joint/no_auth_provided.rdoc +34 -0
- data/reggae.ebnf +1 -1
- metadata +45 -37
- data/Favicon.ico +0 -0
- data/Rakefile +0 -14
- data/TODO +0 -19
- data/examples/blog.rb +0 -26
- data/examples/empty.sqlite3 +0 -0
- data/examples/forum.css +0 -3
- data/examples/forum.js +0 -23
- data/examples/forum.rb +0 -20
- data/examples/forum.sql +0 -42
- data/examples/forum.sqlite3 +0 -0
- data/examples/forum_test.sqlite3 +0 -0
- data/examples/run.sh +0 -14
- data/lib/client/jamaica.css +0 -270
- data/lib/client/jamaica.js +0 -353
- data/lib/client/jamaica.rb +0 -38
- data/lib/client/jquery-1.6.2.js +0 -8981
- data/lib/client/jquery.form.js +0 -814
- data/lib/joints/basic_menu_system.rb +0 -41
- data/lib/joints/basic_messaging.rb +0 -88
- data/lib/joints/basic_user.rb +0 -51
- data/lib/joints/tagged_messaging.rb +0 -122
- data/lib/joints/tagging.rb +0 -56
- data/lib/sequel/plugins/rest_auth.rb +0 -53
- data/lib/sequel/plugins/rest_convenience.rb +0 -87
- data/marley-0.4.0.gem +0 -0
- data/marley.gemspec +0 -17
- data/test/empty.sqlite3 +0 -0
- data/test/menu_tests.rb +0 -9
- data/test/tagged_messaging_tests.rb +0 -289
- data/test/test.sqlite3 +0 -0
- data/test/test_include.rb +0 -16
- data/test/user_tests.rb +0 -72
@@ -0,0 +1,86 @@
|
|
1
|
+
module Marley
|
2
|
+
module Joints
|
3
|
+
class Messages < Joint
|
4
|
+
def smoke
|
5
|
+
super
|
6
|
+
if @opts[:tags]
|
7
|
+
Marley.joint('tags')
|
8
|
+
Marley.plugin(:tagging,{:tag_type => 'private'}).apply(Resources::PrivateMessage)
|
9
|
+
Marley.plugin(:tagging,{:tag_type => 'private'}).apply(Resources::PublicMessage)
|
10
|
+
Marley.plugin(:tagging,{:tag_type => 'public'}).apply(Resources::PublicMessage)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
class Message < Sequel::Model
|
14
|
+
MU.sti(self)
|
15
|
+
MR::User.join_to(self) if MR::User
|
16
|
+
instance_actions![:new?][false]={:get => 'reply'}
|
17
|
+
derived_before_cols![:new?][false]=[:author]
|
18
|
+
ro_cols![:current_user_role] = {'reader' => [/.*/],'owner' => [/^author$/]}
|
19
|
+
def validate
|
20
|
+
super
|
21
|
+
validates_presence [:title]
|
22
|
+
end
|
23
|
+
def reply
|
24
|
+
self.class.new(:title => "re: #{title}",:content => content)
|
25
|
+
end
|
26
|
+
def author
|
27
|
+
MR::User[user_id].to_s
|
28
|
+
end
|
29
|
+
end
|
30
|
+
module Resources
|
31
|
+
class PrivateMessage < Message
|
32
|
+
ro_cols![:new?]={false => [/.*/], true => ['id', 'user_id','author']}
|
33
|
+
attr_writer :recipients
|
34
|
+
def rest_cols
|
35
|
+
[:recipients] + super
|
36
|
+
end
|
37
|
+
Marley::Utils.many_to_many_join(self, MR::User)
|
38
|
+
def self.list_dataset
|
39
|
+
filter(:id => DB[:messages_users].filter(:user_id => current_user[:id]).select(:message_id))
|
40
|
+
end
|
41
|
+
def current_user_role
|
42
|
+
super || (self.users.include?(self.class.current_user) && 'recipient')
|
43
|
+
end
|
44
|
+
def actions(parent_instance=nil)
|
45
|
+
return super if new? || ! recipients.to_s.match(/,/)
|
46
|
+
[:reply, :reply_all]
|
47
|
+
end
|
48
|
+
def recipients
|
49
|
+
users.map{|u|u.name}.join(',')
|
50
|
+
end
|
51
|
+
def reply
|
52
|
+
super.reggae_instance.set_values(:recipients => author)
|
53
|
+
end
|
54
|
+
def reply_all
|
55
|
+
reply.set_values(:recipients => "#{author},#{recipients}".sub(/\b#{self.class.current_user.name}\b,?/,''))
|
56
|
+
end
|
57
|
+
def validate
|
58
|
+
super
|
59
|
+
errors[:recipients]='Recipients must be specified' unless @recipients
|
60
|
+
@recipients=@recipients.split(',').map do |recipient_name|
|
61
|
+
u=MR::User[:name => recipient_name]
|
62
|
+
errors[:recipients] << "#{recipient_name} is not a valid message recipient" unless u
|
63
|
+
u
|
64
|
+
end
|
65
|
+
end
|
66
|
+
def after_save
|
67
|
+
super
|
68
|
+
@recipients.each {|recipient| add_user recipient}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
class PublicMessage < Message
|
72
|
+
def current_user_role
|
73
|
+
super || 'reader'
|
74
|
+
end
|
75
|
+
def actions(parent_instance=nil)
|
76
|
+
if current_user_role=='owner' && ! self.new?
|
77
|
+
{:delete => self.url}.update(super || {})
|
78
|
+
else
|
79
|
+
super
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Marley
|
2
|
+
module Plugins
|
3
|
+
class Section < Plugin
|
4
|
+
module ClassMethods
|
5
|
+
def section
|
6
|
+
ReggaeSection.new({
|
7
|
+
:name => send_or_default(:section_name, resource_name.underscore),
|
8
|
+
:title => send_or_default(:section_title, resource_name.humanize),
|
9
|
+
:navigation => send_or_nil(:section_nav),
|
10
|
+
:description => send_or_nil(:section_desc)},
|
11
|
+
send_or_nil(:section_contents))
|
12
|
+
end
|
13
|
+
def section_link
|
14
|
+
reggae_link('section').update(:title => resource_name.humanize.pluralize)
|
15
|
+
end
|
16
|
+
def section_nav
|
17
|
+
send_or_default(:model_actions,{})[:get].map{|a| reggae_link(a)}.compact
|
18
|
+
end
|
19
|
+
def authorize_rest_get(meth)
|
20
|
+
super || (meth.to_s=='section' && (respond_to?(:current_user) ? ! current_user.new? : true) )
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
module Joints
|
26
|
+
class Section < Joint
|
27
|
+
class Section
|
28
|
+
Marley.plugin('rest_convenience').apply(self)
|
29
|
+
Marley.plugin('section').apply(self)
|
30
|
+
Marley.plugin('current_user_methods').apply(self) if MP.const_defined?(:CurrentUserMethods)
|
31
|
+
def self.rest_get
|
32
|
+
section
|
33
|
+
end
|
34
|
+
end
|
35
|
+
module Resources
|
36
|
+
class MainMenu < Section
|
37
|
+
def self.requires_user?
|
38
|
+
if respond_to?(:current_user)
|
39
|
+
! ($request[:path].nil? || $request[:path].empty?)
|
40
|
+
else
|
41
|
+
false
|
42
|
+
end
|
43
|
+
end
|
44
|
+
def self.section_nav
|
45
|
+
if respond_to?(:current_user) && (current_user.nil? || current_user.new?)
|
46
|
+
[[:msg,{},'New users, please sign up below'],MR::User.new]
|
47
|
+
else
|
48
|
+
MR.resources_responding_to(:section).sort {|l,r|l.resource_name <=> r.resource_name}.map{|r| r.section_link}.compact
|
49
|
+
end
|
50
|
+
end
|
51
|
+
def self.section_desc
|
52
|
+
if respond_to?(:current_user) && (current_user.nil? || current_user.new?)
|
53
|
+
ReggaeLink.new({:url => '/main_menu', :title => 'Existing users, please click here to log in.'})
|
54
|
+
end
|
55
|
+
end
|
56
|
+
def self.section_link
|
57
|
+
ReggaeLink.new({:url => '/',:title => 'Main Menu'})
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module Marley
|
2
|
+
module Plugins
|
3
|
+
class Tagging < Plugin
|
4
|
+
@default_opts={:join_type => 'many_to_many', :add_to_write_cols? => true}
|
5
|
+
def apply(*klasses)
|
6
|
+
klasses.each do |klass|
|
7
|
+
klass=MR.const_get(klass) if klass.is_a?(String)
|
8
|
+
# not crazy about this nested if shit. there may be a better way...
|
9
|
+
if val=klass.instance_variable_get('@derived_after_cols')
|
10
|
+
if val[:new?]
|
11
|
+
val[:new?][:all] << @tag_col_name.to_sym
|
12
|
+
else
|
13
|
+
val[:new?]={:all => [@tag_col_name.to_sym]}
|
14
|
+
end
|
15
|
+
else
|
16
|
+
klass.instance_variable_set('@derived_after_cols',{:new? => {:all => [@tag_col_name.to_sym]}})
|
17
|
+
end
|
18
|
+
@instance_methods_mod.send(:append_features,klass)
|
19
|
+
tag_class=@tag_class
|
20
|
+
join_type=@opts[:"#{klass}_join_type"] || @opts[:join_type]
|
21
|
+
if join_type=='many_to_many'
|
22
|
+
Marley::Utils.many_to_many_join(klass, tag_class)
|
23
|
+
else
|
24
|
+
reciprocal_join=join_type.split('_').reverse.join('_')
|
25
|
+
tag_class.send(reciprocal_join.to_sym, klass.resource_name.pluralize.to_sym, {:class => klass})
|
26
|
+
klass.send(join_type.to_sym, tag_class.resource_name.pluralize.to_sym, {:class => tag_class})
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
def initialize(opts={})
|
31
|
+
super
|
32
|
+
tag_type=@tag_type=@opts[:tag_type]
|
33
|
+
tag_col_name=@tag_col_name="_#{@tag_type}_tags"
|
34
|
+
tag_class=@tag_class=MR.const_get(@tag_col_name.sub(/^_/,'').singularize.camelcase)
|
35
|
+
tags_ds_name=@tags_ds_name="#{tag_col_name.sub(/^_/,'')}_dataset"
|
36
|
+
add_to_write_cols=@opts[:add_to_write_cols?]
|
37
|
+
@instance_methods_mod=Module.new do |m|
|
38
|
+
attr_accessor tag_col_name
|
39
|
+
if add_to_write_cols
|
40
|
+
define_method(:write_cols) {
|
41
|
+
super << tag_col_name.to_sym
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
define_method("#{tag_col_name}_ds".to_sym) { #e.g. _private_tags_ds
|
46
|
+
send(tags_ds_name).filter({:tags__user_id => (tag_class.associations.include?(:user) ? self.class.current_user[:id] : nil)})
|
47
|
+
}
|
48
|
+
define_method(tag_col_name.to_sym) { #e.g. _private_tags
|
49
|
+
send("#{tag_col_name}_ds").map {|t| t.tag}.join(', ') unless new?
|
50
|
+
}
|
51
|
+
define_method("add#{tag_col_name}".to_sym) {|tags| #e.g. add_private_tags
|
52
|
+
vals_hash={:user_id => (tag_class.associations.include?(:user) ? self.class.current_user[:id] : nil)}
|
53
|
+
tags.to_s.split(',').each {|tag| self.send("add#{tag_col_name.singularize}",tag_class.find_or_create(vals_hash.update(:tag => tag))) }
|
54
|
+
}
|
55
|
+
define_method("replace#{tag_col_name}".to_sym) { #e.g. replace_private_tags
|
56
|
+
send("#{tag_col_name}_ds").each {|tag| send("remove#{tag_col_name}".singularize,tag)}
|
57
|
+
send("add#{tag_col_name}",instance_variable_get("@#{tag_col_name}"))
|
58
|
+
}
|
59
|
+
define_method(:after_save) {
|
60
|
+
super
|
61
|
+
methods.select {|m| m.match(/^replace_.+_tags/)}.each do |replace_method|
|
62
|
+
send(replace_method)
|
63
|
+
end
|
64
|
+
}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
module Joints
|
70
|
+
class Tags < Joint
|
71
|
+
module Resources
|
72
|
+
class Tag < Sequel::Model
|
73
|
+
def validate
|
74
|
+
validates_presence :tag
|
75
|
+
validates_unique [:tag,:user_id]
|
76
|
+
end
|
77
|
+
def actions(parent_instance)
|
78
|
+
{:delete => "#{parent_instance ? parent_instance.url : ''}#{url}"}
|
79
|
+
end
|
80
|
+
def before_save
|
81
|
+
super
|
82
|
+
self.tag.downcase!
|
83
|
+
self.tag.strip!
|
84
|
+
end
|
85
|
+
end
|
86
|
+
class PublicTag < Tag
|
87
|
+
@owner_col=nil
|
88
|
+
set_dataset DB[:tags].filter(:tags__user_id => nil).order(:tag)
|
89
|
+
end
|
90
|
+
class PrivateTag < Tag
|
91
|
+
MR::User.join_to(self) if MR::User
|
92
|
+
def self.list_dataset
|
93
|
+
current_user_dataset.order(:tag)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
module Marley
|
3
|
+
module Plugins
|
4
|
+
class CurrentUserMethods < Plugin
|
5
|
+
@default_opts={ :plugins => [:orm_rest_convenience],:class_attrs => [ [:owner_col,:user_id] ], :http_auth => true}
|
6
|
+
def apply(*args)
|
7
|
+
super
|
8
|
+
if @opts[:http_auth]
|
9
|
+
Marley.config(:authenticate => lambda { |env|
|
10
|
+
require 'rack/auth/basic'
|
11
|
+
auth = Rack::Auth::Basic::Request.new(env)
|
12
|
+
if (auth.provided? && auth.basic? && auth.credentials)
|
13
|
+
$request[:user]=MR::User.authenticate(auth.credentials)
|
14
|
+
raise AuthenticationError unless $request[:user]
|
15
|
+
else
|
16
|
+
$request[:user]=MR::User.new
|
17
|
+
end
|
18
|
+
})
|
19
|
+
end
|
20
|
+
end
|
21
|
+
module ClassMethods
|
22
|
+
def current_user; $request && $request[:user]; end
|
23
|
+
def current_user_class; current_user.class; end
|
24
|
+
def current_user_ds; filter(@owner_col.to_sym => current_user[:id]); end
|
25
|
+
def requires_user?(verb=nil,meth=nil);true;end
|
26
|
+
def authorize_rest_get(meth)
|
27
|
+
model_actions[:get].to_a.include?(meth.to_sym) && !current_user.new?
|
28
|
+
end
|
29
|
+
def authorize_rest_post(meth)
|
30
|
+
new(($request[:post_params][resource_name.to_sym]||{}).reject {|k,v| v.nil?}).current_user_role=='owner' && meth.nil?
|
31
|
+
end
|
32
|
+
def authorize_rest_put(meth);false; end
|
33
|
+
def authorize_rest_delete(meth):false; end
|
34
|
+
end
|
35
|
+
module InstanceMethods
|
36
|
+
def after_initialize
|
37
|
+
super
|
38
|
+
send("#{self.class.owner_col}=",$request[:user][:id]) if $request && self.class.owner_col && new?
|
39
|
+
end
|
40
|
+
def requires_user?(verb=nil,meth=nil);true;end
|
41
|
+
def authorize(meth)
|
42
|
+
if respond_to?(auth_type="authorize_#{$request[:verb]}")
|
43
|
+
send(auth_type,meth)
|
44
|
+
else
|
45
|
+
current_user_role && current_user_role != 'new'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
def current_user_role
|
49
|
+
if u=self.class.current_user
|
50
|
+
return 'new' if u.new?
|
51
|
+
return "owner" if owners.include?(u)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
def owners
|
55
|
+
if self.class.to_s.match(/User$/)||self.class.superclass.to_s.match(/User$/)
|
56
|
+
[self]
|
57
|
+
elsif self.class.owner_col
|
58
|
+
[MR::User[send(self.class.owner_col)]]
|
59
|
+
else
|
60
|
+
self.class.association_reflections.select {|k,v| v[:type]==:many_to_one}.map {|a| self.send(a[0]) && self.send(a[0]).owners}.flatten.compact
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
module Joints
|
67
|
+
class User < Joint
|
68
|
+
module Resources
|
69
|
+
class User < Sequel::Model
|
70
|
+
LOGIN_FORM= [:instance,{:name => 'login',:url => 'main_menu',:description => 'Existing users please log in here:',:new_rec => true,:schema => [[:text,'name',RESTRICT_REQ],[:password,'password',RESTRICT_REQ]]}]
|
71
|
+
Marley.plugin('current_user_methods').apply(self)
|
72
|
+
MU.sti(self)
|
73
|
+
@owner_col=nil
|
74
|
+
required_cols![:new?][true]=['password','confirm_password']
|
75
|
+
derived_after_cols![:new?]={true => [:password,:confirm_password]}
|
76
|
+
derived_after_cols![:current_user_role]={'owner' => [:old_password,:password,:confirm_password]}
|
77
|
+
reject_cols![:current_user_role]={:all => ['pw_hash']}
|
78
|
+
ro_cols![:current_user_role]={'new' => ['id'],nil => [/.*/]}
|
79
|
+
def self.join_to(klass, user_id_col_name=nil)
|
80
|
+
user_id_col_name||='user_id'
|
81
|
+
klass=MR.const_get(klass) if klass.class==String
|
82
|
+
Marley.plugin(:current_user_methods).apply(klass)
|
83
|
+
klass.owner_col!=user_id_col_name
|
84
|
+
one_to_many klass.resource_name.to_sym, :class => klass, :key => user_id_col_name
|
85
|
+
klass.send(:many_to_one, :user, :class => MR::User, :key => user_id_col_name)
|
86
|
+
end
|
87
|
+
attr_accessor :old_password,:password, :confirm_password
|
88
|
+
def self.requires_user?
|
89
|
+
! ($request[:verb]=='rest_post' || ($request[:verb]=='rest_get' && $request[:path][1]=='new'))
|
90
|
+
end
|
91
|
+
def self.authorize_rest_post
|
92
|
+
true
|
93
|
+
end
|
94
|
+
def self.authenticate(credentials)
|
95
|
+
u=find(:name => credentials[0], :pw_hash => Digest::SHA1.hexdigest(credentials[1]))
|
96
|
+
u.respond_to?(:user_type) ? Marley::Resources.const_get(u[:user_type].to_sym)[u[:id]] : u
|
97
|
+
end
|
98
|
+
def validate
|
99
|
+
super
|
100
|
+
validates_presence [:name]
|
101
|
+
validates_unique [:name]
|
102
|
+
if self.new? || self.old_password.to_s + self.password.to_s + self.confirm_password.to_s > ''
|
103
|
+
errors[:password]=['Password must contain at least 8 characters'] if self.password.to_s.length < 8
|
104
|
+
errors[:confirm_password]=['Passwords do not match'] unless self.password==self.confirm_password
|
105
|
+
errors[:old_password]=['Old Password Incorrect'] if !self.new? && Digest::SHA1.hexdigest(self.old_password.to_s) != self.pw_hash
|
106
|
+
end
|
107
|
+
end
|
108
|
+
def before_save
|
109
|
+
if self.new? || self.old_password.to_s + self.password.to_s + self.confirm_password.to_s > ''
|
110
|
+
self.pw_hash=Digest::SHA1.hexdigest(self.password)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
def create_msg
|
114
|
+
[:msg,{:title => 'Success!'},"Your login, '#{self.name}', has been sucessfully created. You can now log in."]
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
module Marley
|
3
|
+
module Plugins
|
4
|
+
Gem.find_files("lib/marley/plugins/*.rb").each do |f|
|
5
|
+
self.autoload(File.basename(f,'.rb').camelize, f)
|
6
|
+
end
|
7
|
+
class Plugin
|
8
|
+
extend Marley::Utils::ClassAttrs
|
9
|
+
class_attr(:default_opts,{})
|
10
|
+
def initialize(opts={})
|
11
|
+
config(opts)
|
12
|
+
end
|
13
|
+
def apply(*klasses)
|
14
|
+
plugin=self.class
|
15
|
+
klasses.flatten.each do |klass|
|
16
|
+
resource=klass.class==String ? MR.const_get(klass) : klass
|
17
|
+
@opts[:required_plugins].to_a.each do |p|
|
18
|
+
Marley.plugin(p).apply(klass)
|
19
|
+
end
|
20
|
+
plugin.constants.include?('ClassMethods') && resource.extend(plugin.const_get('ClassMethods'))
|
21
|
+
plugin.constants.include?('InstanceMethods') && resource.send(:include, plugin.const_get('InstanceMethods'))
|
22
|
+
if @opts[:lazy_class_attrs] || @opts[:class_attrs]
|
23
|
+
resource.extend Marley::Utils::ClassAttrs
|
24
|
+
end
|
25
|
+
if lazy_attrs=@opts[:lazy_class_attrs]
|
26
|
+
resource.lazy_class_attrs(lazy_attrs[0],lazy_attrs[1..-1])
|
27
|
+
end
|
28
|
+
@opts[:class_attrs].to_a.each do |att|
|
29
|
+
resource.class_attr(*att)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
def config(opts)
|
35
|
+
@opts=(@opts || self.class.default_opts || {}).merge(opts)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module Marley
|
2
|
+
module Plugins
|
3
|
+
class OrmRestConvenience < Plugin
|
4
|
+
#next 3 must go
|
5
|
+
Sequel::Model.plugin :validation_helpers
|
6
|
+
Sequel::Plugins::ValidationHelpers::DEFAULT_OPTIONS.merge!(:presence => {:message => 'is required'})
|
7
|
+
Sequel::Model.plugin :timestamps, :create => :date_created, :update => :date_updated
|
8
|
+
|
9
|
+
@default_opts={
|
10
|
+
:required_plugins => [:rest_convenience],
|
11
|
+
:class_attrs =>[ [:model_actions,{:get => [:new, :list]}] ],
|
12
|
+
:lazy_class_attrs => [ :new?,[:instance_actions,{:all => nil}],
|
13
|
+
[:derived_before_cols,{:all => []}],
|
14
|
+
[:derived_after_cols,{:all => []}],
|
15
|
+
[:reject_cols,{true => [/^id$/,/_type$/,/date_(created|updated)/], false => [/_type$/]}],
|
16
|
+
[:ro_cols,{true => [/^id$/,/_id$/], false => [/^id$/,/_id$/,/date_(created|updated)/]}],
|
17
|
+
[:hidden_cols,{:all => [/_id$/]}],
|
18
|
+
[:required_cols,{:all => []}] ]
|
19
|
+
}
|
20
|
+
module ClassMethods
|
21
|
+
def controller; Marley::ModelController.new(self); end
|
22
|
+
# the next 2 will have to be overridden for most applications
|
23
|
+
def authorize(verb); send_or_default("authorize_#{verb}",true) ; end
|
24
|
+
def requires_user?; false; end
|
25
|
+
|
26
|
+
def foreign_key_name; :"#{(respond_to?(:table_name) ? table_name : resource_name).to_s.singularize}_id"; end
|
27
|
+
|
28
|
+
def list(params={})
|
29
|
+
if respond_to?(:list_dataset)
|
30
|
+
list_dataset.filter(params).all
|
31
|
+
else
|
32
|
+
filter(params).all
|
33
|
+
end
|
34
|
+
end
|
35
|
+
def sti
|
36
|
+
plugin :single_table_inheritance, :"#{self.to_s.sub(/.*::/,'').underscore}_type", :model_map => lambda{|v| MR.const_get(v.to_sym)}, :key_map => lambda{|klass|klass.name.sub(/.*::/,'')}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
module InstanceMethods
|
40
|
+
# the next 2 will have to be overridden for most applications
|
41
|
+
def authorize(verb); true ; end
|
42
|
+
def requires_user?; false; end
|
43
|
+
|
44
|
+
def col_mods_match(mod_type); lambda {|c| c.to_s.match(Regexp.union(send(:"_#{mod_type}")))}; end
|
45
|
+
|
46
|
+
def rest_cols; _derived_before_cols.to_a + (columns.reject &col_mods_match(:reject_cols)) + _derived_after_cols.to_a;end
|
47
|
+
def write_cols; rest_cols.reject &col_mods_match(:ro_cols);end
|
48
|
+
def hidden_cols; rest_cols.select &col_mods_match(:hidden_cols);end
|
49
|
+
def required_cols; rest_cols.select &col_mods_match(:required_cols);end
|
50
|
+
def actions(parent_instance=nil); _instance_actions; end
|
51
|
+
|
52
|
+
def rest_associations;[];end
|
53
|
+
|
54
|
+
def reggae_schema
|
55
|
+
Marley::ReggaeSchema.new(
|
56
|
+
rest_cols.map do |col_name|
|
57
|
+
db_spec=db_schema.to_hash[col_name]
|
58
|
+
col_type=db_spec ? db_spec[:db_type].downcase : "text"
|
59
|
+
col_type=:password if col_name.to_s.match(/password/)
|
60
|
+
restrictions=0
|
61
|
+
restrictions|=RESTRICT_HIDE if hidden_cols.include?(col_name)
|
62
|
+
restrictions|=RESTRICT_RO unless write_cols.include?(col_name)
|
63
|
+
restrictions|=RESTRICT_REQ if required_cols.include?(col_name) || (db_spec && !db_spec[:allow_null])
|
64
|
+
[col_type, col_name, restrictions,send(col_name)]
|
65
|
+
end
|
66
|
+
)
|
67
|
+
end
|
68
|
+
def to_s
|
69
|
+
respond_to?('name') ? name : "#{self.class.name} #{id.to_s}"
|
70
|
+
end
|
71
|
+
def reggae_instance(parent_instance=nil)
|
72
|
+
a=Marley::ReggaeInstance.new(
|
73
|
+
{:name => self.class.resource_name,:url => url ,:new_rec => self.new?,:schema => reggae_schema,:actions => self.actions(parent_instance)}
|
74
|
+
)
|
75
|
+
a.contents=rest_associations.to_a.map do |assoc|
|
76
|
+
assoc.map{|instance| instance.reggae_instance(self)}
|
77
|
+
end unless new?
|
78
|
+
a
|
79
|
+
end
|
80
|
+
def to_json(*args)
|
81
|
+
reggae_instance.to_json
|
82
|
+
end
|
83
|
+
def url(action=nil)
|
84
|
+
"/#{self.class.resource_name}/#{self[:id]}/#{action}".sub(/\/$/,'')
|
85
|
+
end
|
86
|
+
def reggae_link(action='')
|
87
|
+
ReggaeLink.new({:url => url,:title => "#{action.humanize}"})
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|