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,28 @@
1
+
2
+
3
+ module Marley
4
+ module Plugins
5
+ class RestConvenience < Plugin
6
+ def apply(klass)
7
+ super
8
+ end
9
+ module ClassMethods
10
+ def resource_name; self.name.sub(/.*::/,'').underscore; end
11
+ def url
12
+ end
13
+ def reggae_link(action=nil, title=nil, args=nil)
14
+ ReggaeLink.new({:url => "/#{self.resource_name}/#{action}?#{args}",:title => (title||"#{action.to_s.humanize} #{self.resource_name.humanize}".strip)})
15
+ end
16
+ end
17
+ module InstanceMethods
18
+ def resource_type
19
+ if is_a? Sequel::Model
20
+ :instance
21
+ elsif is_a? MP::Section::Section
22
+ :section
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
data/lib/marley/reggae.rb CHANGED
@@ -27,7 +27,6 @@ module Marley
27
27
  end
28
28
  attr_reader :resource_type
29
29
  attr_accessor :properties,:contents
30
- # @param [Array] *args an array in Reggae syntax
31
30
  def initialize(*args)
32
31
  super
33
32
  if is_resource?
@@ -51,7 +50,7 @@ module Marley
51
50
  is_resource? ? Marley.const_get("Reggae#{resource_type.to_s.camelize}".to_sym).new(self) : self
52
51
  end
53
52
  def find_instances(rn,instances=Reggae.new([]))
54
- if self.class==ReggaeInstance && self.name.to_s==rn
53
+ if self.class==ReggaeInstance && self.name.to_s==rn.to_s
55
54
  instances << self
56
55
  else
57
56
  (is_resource? ? contents : self).each {|a| a && Reggae.new(a).to_resource.find_instances(rn,instances)}
@@ -68,6 +67,10 @@ module Marley
68
67
  super
69
68
  end
70
69
  end
70
+ def update(vals)
71
+ self.properties.update(vals)
72
+ self
73
+ end
71
74
  end
72
75
  class ReggaeSection < ReggaeResource
73
76
  properties :title,:description
@@ -0,0 +1,10 @@
1
+ module Marley
2
+ module Resources
3
+ def self.resources_responding_to(method)
4
+ constants.map{|c| r=const_get(c); r if r.respond_to?(method)}.compact
5
+ end
6
+ def self.map_resource_methods(method)
7
+ constants.map{|c| r=const_get(c); r.send(method) if r.respond_to?(method)}.compact
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,61 @@
1
+
2
+ module Marley
3
+ class Router
4
+ attr_reader :opts
5
+ def initialize(opts={},app=nil)
6
+ @opts=DEFAULT_OPTS.merge(opts)
7
+ end
8
+ def call(env)
9
+ request= Rack::Request.new(env)
10
+ $request={:request => request,:opts => @opts}
11
+ $request[:get_params]=Marley::Utils.hash_keys_to_syms(request.GET)
12
+ $request[:post_params]=Marley::Utils.hash_keys_to_syms(request.POST)
13
+ $request[:content_type]=request.xhr? ? 'application/json' : env['HTTP_ACCEPT'].to_s.sub(/,.*/,'')
14
+ $request[:content_type]='text/html' unless $request[:content_type] > ''
15
+
16
+ if env['rack.test']==true #there has to be a better way to do this...
17
+ require 'json/add/core'
18
+ $request[:content_type]='application/json'
19
+ end
20
+
21
+ @opts[:authenticate].call(env) if @opts[:authenticate]
22
+
23
+ $request[:path]=request.path.sub(/\/\/+/,'/').split('/')[1..-1]
24
+ verb=request.request_method.downcase
25
+ verb=$request[:post_params].delete(:_method).match(/^(put|delete)$/i)[1] rescue verb
26
+ $request[:verb]="rest_#{verb}"
27
+
28
+ rn=$request[:path] ? $request[:path][0].camelize : @opts[:default_resource]
29
+ return nil if rn=='Favicon.ico'
30
+ unless Resources.const_defined?(rn)
31
+ raise AuthenticationError if (@opts[:authenticate] && $request[:user].new? )
32
+ raise RoutingError
33
+ end
34
+ resource=Resources.const_get(rn)
35
+ raise AuthenticationError if @opts[:authenticate] && resource.respond_to?('requires_user?') && resource.requires_user? && $request[:user].new?
36
+
37
+ controller=nil #clear from previous call
38
+ controller=resource.controller if resource.respond_to?(:controller)
39
+ controller=resource if resource.respond_to?($request[:verb])
40
+ raise RoutingError unless controller
41
+
42
+ json=controller.send($request[:verb]).to_json
43
+ html=@opts[:client] ? @opts[:client].to_s(json) : json
44
+ resp_code=RESP_CODES[verb]
45
+ headers||={'Content-Type' => "#{$request[:content_type]}; charset=utf-8"}
46
+
47
+ [resp_code,headers,$request[:content_type].match(/json/) ? json : html]
48
+ rescue Sequel::ValidationFailed
49
+ ValidationError.new($!.errors).to_a
50
+ rescue
51
+ if $!.class.superclass==MarleyError
52
+ $!.to_a
53
+ else
54
+ p $!,$!.class,$!.backtrace
55
+ end
56
+ ensure
57
+ $log.info $request.merge({:request => nil,:user => $request[:user] ? $request[:user].name : nil})
58
+ $request=nil #mostly for testing
59
+ end
60
+ end
61
+ end
@@ -16,6 +16,17 @@ module Marley
16
16
  opts||=@opts
17
17
  opts[:url] || '/' + [:root_url, :resource_name, :instance_id, :method].map {|k| opts[k]}.compact.join('/') + opts[:extention].to_s
18
18
  end
19
+ def date_hack(reggae_obj)
20
+ if reggae_obj.class==Marley::Reggae
21
+ reggae_obj.map {|o| date_hack(o)}
22
+ elsif reggae_obj.class==Marley::ReggaeInstance
23
+ reggae_obj.set_values(:date_created => 'date_created') if reggae_obj.schema[:date_created]
24
+ reggae_obj.set_values(:date_updated => 'date_updated') if reggae_obj.schema[:date_updated]
25
+ reggae_obj
26
+ else
27
+ reggae_obj
28
+ end
29
+ end
19
30
  def process(verb,params={},opts={})
20
31
  #p 'params:',params,'opts:',opts,"-------------" if opts
21
32
  opts||={}
@@ -36,7 +47,7 @@ module Marley
36
47
  p last_response.status if opts[:debug]
37
48
  p expected_code if opts[:debug]
38
49
  return false unless (expected_code || RESP_CODES[method])==last_response.status
39
- Reggae.get_resource(JSON.parse(last_response.body)) rescue last_response.body
50
+ date_hack(Reggae.get_resource(JSON.parse(last_response.body))) rescue last_response.body
40
51
  end
41
52
  ['create','read','update','del'].each do |op|
42
53
  define_method op.to_sym, Proc.new { |*args|
data/lib/marley/utils.rb CHANGED
@@ -1,36 +1,68 @@
1
1
 
2
2
  module Marley
3
3
  module Utils
4
- def self.hash_keys_to_syms(hsh)
5
- hsh.inject({}) {|h,(k,v)| h[k.to_sym]= v.class==Hash ? hash_keys_to_syms(v) : v;h }
6
- end
7
- # @todo: make options inheritable?
8
- def self.rest_opts_mod(name,opts,key_proc)
9
- Module.new do |m|
10
- @create_opts=[name,opts,key_proc]
11
- def self.create_opts
12
- @create_opts
13
- end
14
- def self.new(name=nil,opts=nil,key_proc=nil)
15
- Marley::Utils.rest_opts_mod(*@create_opts)
16
- end
17
- opts.each {|opt| attr_accessor "#{name}_#{opt}"}
18
- define_method "rest_#{name}" do
19
- if opts.find {|opt| send(:"#{name}_#{opt}").to_s > ""}
20
- foo=opts.inject({}) do |h,k|
21
- i=send("#{name}_#{k}".sub(/^_/,''))
22
- h[k.to_sym]=i.class==Hash ? i[key_proc.call] : i
23
- h
4
+ module ClassAttrs
5
+ def lazy_class_attrs(key_proc,atts,op=nil,&block)
6
+ atts.to_a.each do |att|
7
+ att=[att] unless att.is_a?(Array)
8
+ class_attr(att[0], {key_proc => att[1]}, op, &block)
9
+ include(Module.new do |m|
10
+ define_method :"_#{att[0]}" do
11
+ a=self.class.send(att[0])
12
+ a.keys.inject(nil) {|res,key|
13
+ if self.respond_to?(key)
14
+ all=a[key][:all]
15
+ v=(a[key].has_key?(dyn_key=self.send(key)) && a[key][dyn_key] ) || all || res
16
+ Marley::Utils.combine(res,Marley::Utils.combine(all, v))
17
+ else
18
+ Marley::Utils.combine(res,a[key])
19
+ end
20
+ }
24
21
  end
25
- if Marley.constants.include?("Reggae#{name.camelcase}")
26
- Marley.const_get(:"Reggae#{name.camelcase}").new(foo)
22
+ end)
23
+ end
24
+ end
25
+ def class_attr(attr_name, val=nil, op=nil, &block)
26
+ block||=op ? lambda{ |o, x| o.__send__(op, x) } : lambda {|old, new| Marley::Utils.combine(old,new)}
27
+ extend(Module.new do |m|
28
+ define_method :"#{attr_name}!" do |*args|
29
+ if instance_variable_defined?("@#{attr_name}")
30
+ instance_variable_get("@#{attr_name}")
27
31
  else
28
- foo
32
+ instance_variable_set("@#{attr_name}", Marshal.load(Marshal.dump(val)))
29
33
  end
30
34
  end
31
- end
35
+ define_method attr_name.to_sym do
36
+ ancestors.reverse.inject(Marshal.load(Marshal.dump(val))) do |v, a|
37
+ if a.respond_to?(:"#{attr_name}!")
38
+ block.call(v,a.__send__(:"#{attr_name}!"))
39
+ else
40
+ v
41
+ end
42
+ end
43
+ end
44
+ end)
45
+ end
46
+ end
47
+ def self.combine(old,new)
48
+ if old.is_a?(Hash) && new.is_a?(Hash)
49
+ old.merge(new) {|k,o,n|Marley::Utils.combine(o,n)}
50
+ elsif old.is_a?(Array) && new.is_a?(Array)
51
+ (old + new).uniq
52
+ else
53
+ new
32
54
  end
33
55
  end
56
+ def self.sti(klass)
57
+ klass.plugin :single_table_inheritance, :"#{klass.to_s.sub(/.*::/,'').underscore}_type", :model_map => lambda{|v| MR.const_get(v.to_sym)}, :key_map => lambda{|clss|clss.name.sub(/.*::/,'')}
58
+ end
59
+ def self.many_to_many_join(lclass, rclass)
60
+ join_table=[lclass.table_name.to_s,rclass.table_name.to_s ].sort.join('_')
61
+ lclass.many_to_many(rclass.resource_name.pluralize.to_sym,:join_table => join_table,:class =>rclass, :left_key => lclass.foreign_key_name, :right_key => rclass.foreign_key_name)
62
+ rclass.many_to_many(lclass.resource_name.pluralize.to_sym,:join_table => join_table, :class =>lclass,:left_key => rclass.foreign_key_name, :right_key => lclass.foreign_key_name)
63
+ end
64
+ def self.hash_keys_to_syms(hsh)
65
+ hsh.inject({}) {|h,(k,v)| h[k.to_sym]= v.class==Hash ? hash_keys_to_syms(v) : v;h }
66
+ end
34
67
  end
35
- RestActions=Utils.rest_opts_mod('actions',['get','post','put','delete'],lambda {$request[:user].class})
36
68
  end
@@ -0,0 +1,33 @@
1
+ require 'rubygems'
2
+ require 'marley'
3
+
4
+ module Marley
5
+ module Plugins
6
+ class HelloPlugin < Plugin
7
+ module ClassMethods
8
+ def rest_post
9
+ new($request[:path][1])
10
+ end
11
+ end
12
+ module InstanceMethods
13
+ def initialize(greeting)
14
+ @greeting=greeting
15
+ end
16
+ def to_json
17
+ "#{@greeting} world"
18
+ end
19
+ end
20
+ end
21
+ end
22
+ module Joints
23
+ class ExampleJoint < Joint
24
+ module Resources
25
+ class GoodBye
26
+ def self.rest_get
27
+ "goodbye"
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,20 @@
1
+
2
+ module Marley
3
+ module Plugins
4
+ class ExamplePlugin < Plugin
5
+ module ClassMethods
6
+ def rest_post
7
+ new($request[:path][1])
8
+ end
9
+ end
10
+ module InstanceMethods
11
+ def initialize(greeting)
12
+ @greeting=greeting
13
+ end
14
+ def to_json
15
+ "#{@greeting} world"
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,50 @@
1
+ require 'marley'
2
+ require 'marley/test_helpers'
3
+
4
+
5
+ DB=Sequel.sqlite('')
6
+ DB.create_table :users do
7
+ primary_key :id
8
+ text :name, :unique => true
9
+ text :user_type, :index => true
10
+ text :pw_hash
11
+ datetime :date_created
12
+ text :description
13
+ end
14
+ DB.create_table :messages do
15
+ primary_key :id
16
+ integer :topic_id, :index => true
17
+ integer :parent_id, :index => true
18
+ integer :user_id, :index => true
19
+ datetime :date_created, :index => true
20
+ text :message_type, :index => true
21
+ text :title, :index => true,:null => false
22
+ clob :content
23
+ end
24
+ DB.create_table :messages_users do
25
+ primary_key :id
26
+ integer :user_id
27
+ integer :message_id
28
+ boolean :read, :index => true
29
+ index [:user_id, :message_id], :unique => true
30
+ end
31
+ DB.create_table :tags do
32
+ primary_key :id
33
+ integer :user_id,:index => true
34
+ text :tag,:index => true
35
+ end
36
+ DB.create_table :messages_tags do
37
+ primary_key :id
38
+ integer :tag_id
39
+ integer :message_id
40
+ index [:tag_id, :message_id], :unique => true
41
+ end
42
+ DB.create_table :topics_tags do
43
+ primary_key :id
44
+ integer :tag_id
45
+ integer :topic_id
46
+ index [:tag_id, :topic_id], :unique => true
47
+ end
48
+
49
+
50
+ Marley.joint('forum')
@@ -0,0 +1,19 @@
1
+ ==Forum Joint
2
+
3
+ setup
4
+ DB[:users].delete
5
+ DB[:messages].delete
6
+ DB[:messages_users].delete
7
+ DB[:tags].delete
8
+ DB[:SQLITE_SEQUENCE].update(:seq => 0)
9
+ user_client=Marley::TestClient.new(:resource_name => 'user')
10
+ user=user_client.read({},:method => 'new').set_values(:password => 'asdfasdf', :confirm_password => 'asdfasdf')
11
+ (1 .. 5).each { |i|
12
+ user_client.create(user.set_values(:name => "user#{i}"))
13
+ instance_variable_set(:"@client#{i}", Marley::TestClient.new(:auth => ["user#{i}",'asdfasdf'] ))
14
+ }
15
+ end
16
+
17
+ example:
18
+
19
+ !!!
data/rdoc/hello.rb ADDED
@@ -0,0 +1,14 @@
1
+ require 'marley'
2
+ require 'marley/test_helpers'
3
+
4
+ Marley.config :http_auth => false
5
+ module Marley
6
+ module Resources
7
+ class Hello
8
+ def self.rest_get
9
+ @who=$request[:get_params][:who] || 'World'
10
+ "Hello #{@who}!"
11
+ end
12
+ end
13
+ end
14
+ end
data/rdoc/hello.rdoc ADDED
@@ -0,0 +1,14 @@
1
+
2
+ =Marley Basics
3
+
4
+ setup
5
+ @client=Marley::TestClient.new({:resource_name => 'hello'})
6
+ end
7
+
8
+ example: return hello world
9
+
10
+ >> @client.read
11
+ => "\"Hello World!\""
12
+ >> @client.read({:who => 'Dolly'})
13
+ => "\"Hello Dolly!\""
14
+
data/rdoc/joints.rdoc ADDED
@@ -0,0 +1,69 @@
1
+
2
+ ==Joints
3
+
4
+
5
+ "Joints" are pre-packaged resource sets that can be included in a Marley application. A joint can be added to the current Resources by running Marley.joint('_joint_name_'). The Marley.joint method will then do the following:
6
+
7
+ * Find a file named '_joint_name_' in the 'joints/' directory.
8
+ * Require that file.
9
+ * run its #smoke method.
10
+
11
+ The Joint#smoke method scans the classes constants for a module namee 'Resources' and any modules whose name ends in 'Plugin'.
12
+
13
+ * If the 'Resources' module exists, Joint#smoke will copy all of its constants to Marley::Resources.
14
+ * For every module whose name ends in 'Plugin', if a corresponding class exists in Marley::Resources, it will be extended by the internal 'ClassMethods' module and have the internal 'InstanceMethods' module's features appended to it.
15
+
16
+ :include: rdoc/hello.rb
17
+
18
+ :include: rdoc/example_joint.rb
19
+
20
+ For now, there are 4 joints included in the Marley distribution:
21
+
22
+ * User
23
+ * Messages
24
+ * Tags
25
+ * Section
26
+
27
+ With a bit of configuration, these comprise the example forum application.
28
+
29
+ setup
30
+ @client=Marley::TestClient.new(:resource_name => 'hello')
31
+ end
32
+
33
+ example: without the joint, then with the joint
34
+
35
+ >> MR.constants
36
+ => ["Hello"]
37
+ >> @client.read
38
+ => "\"Hello World!\""
39
+ >> @client.read({}, :resource_name => 'goodbye',:code => 404)
40
+ => [:error, {:description=>"Not Found", :error_type=>"routing", :error_details=>nil}]
41
+ >> @client.create({}, :code => 404)
42
+ => [:error, {:description=>"Not Found", :error_type=>"routing", :error_details=>nil}]
43
+
44
+
45
+ >> Marley.joint('example_joint').class
46
+ => Marley::Joints::ExampleJoint
47
+ >> MR.constants.sort
48
+ => ["GoodBye", "Hello"]
49
+ >> Marley.plugin('HelloPlugin').apply(MR::Hello)
50
+ => nil
51
+
52
+ >> @client.read({}, :resource_name => 'goodbye')
53
+ => false
54
+ >> @client.read({}, :resource_name => 'good_bye')
55
+ => "\"goodbye\""
56
+ >> @client.create({})
57
+ => " world"
58
+ >> @client.create({},:method => 'howdy')
59
+ => "howdy world"
60
+
61
+ :include: rdoc/user_joint.rdoc
62
+
63
+ :include: rdoc/tags_joint.rdoc
64
+
65
+ :include: rdoc/messages_joint.rdoc
66
+
67
+ :include: rdoc/section_joint.rdoc
68
+
69
+