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.
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
@@ -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