marley 0.1.0 → 0.2.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 CHANGED
@@ -1,48 +1,75 @@
1
1
  =Marley
2
2
 
3
- Marley is a project consisting of several parts:
3
+ Marley is a framework for quickly building RESTful web services and applications. It consists of several parts:
4
4
 
5
- * A server side micro-framework for returning data from ORM models (currently only Sequel) and other objects (Ruby, based on rack with thin as the default web server)
6
- * A framework for adding reusable content to the framework ("Joints")
7
- * A JSON data format ("Reggae")
8
- * A simple JS client ("Jamaica") which renders Reggae as HTML.
5
+ * A simple Rack application that acts as request parser/router.
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.
10
+ * Marley Joints - A framework for creating reusable Marley resource sets.
11
+ * Reggae - A JSON data format
12
+ * Jamaica - A minimal JS/jQuery client which renders Reggae resources as browser DOM objects.
9
13
 
10
- The point of the whole thing is to create as many useful default behaviors as possible, while making it easy to override any or all of them.
14
+ 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.
11
15
 
12
- Please see the examples/forum.rb to get a basic idea of what a Marley application looks like.
16
+ Please see the examples/forum.rb and the included Joints' code to get a basic idea of what a Marley application looks like.
13
17
 
14
18
  -----
15
19
 
16
- More about each part:
20
+ ==Marley Resources
17
21
 
18
- ==Marley
22
+ Marley resources are constants in the Marley::Resources namespace.
19
23
 
20
- * Marley resources are constants in the Marley::Resources namespace.
21
- * A resource must respond either to a #controller method or to one or more of #rest_get, #rest_post, #rest_put, or #rest_delete.
22
- * Resources implementing a #controller method should return an object that responds to one or more REST verbs from that method
23
- * REST verb methods should return an object which responds to #to_json by returning a string to be sent to the client
24
+ * A resource must respond either to a #controller method or to one or more REST verbs (#rest_get, #rest_post, #rest_put, or #rest_delete).
25
+ * A #controller method must return an object that responds to one or more REST verbs.
26
+ * REST verb methods should return an object with a #to_json method.
24
27
 
25
- * Marley provides 2 plugins for the Sequel ORM.
26
- * RestConvenience - Adds a default controller for standard rest routes to a model
27
- * RestAuthorization - Adds default authorization to a model
28
28
 
29
- I use Sequel exclusively, so I've only written these plugins for it. I imagine it would be pretty trivial to port them to other ORMs.
29
+ ==The Parser/Router
30
+
31
+ The parser splits the request path on '/' and treats the first part as the requested resource. If no resource is specified in the request, it uses the default resource.
32
+
33
+ if the resource has a REST method corresponding the the request verb, the parser calls that method and sends the return value to the client.
34
+
35
+ The parser also traps various errors and returns the appropriate error message to the client.
36
+
37
+ == The Sequel plugins
38
+
39
+ Marley activates both of them for Sequel::Model. This will soon change to an option.
40
+
41
+ ==The Default Model Controller
42
+
43
+ One of the things that the RestConvenience Sequel plugin does is add a #controller method to affected models. This method instantiates and returns a ModelController object for the model in question. At initialization, the Controller parses the request path to determine the model instances to which the request refers.
44
+
30
45
 
31
46
  ==Joints
32
47
 
33
- "Joints" are pre-packaged resource sets that can be included in a Marley application. The joints API is very much a work in progress.
48
+ "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:
49
+
50
+ * Find a file named '_joint_name_' in the 'joints/' directory.
51
+ * Require that file.
52
+ * run its #smoke method.
53
+
54
+ The Joint#smoke method looks for 3 modules inside the joint's namespace: Resources, ClassMethods, and InstanceMethods.
55
+
56
+ * if the Resources module exists, Joint#smoke will copy all of its constants to Marley::Resources.
57
+ * If the ClassMethods module exists, Joint#smoke will cycle through the modules within it, and extend objects in Marley::Resources with the same name.
58
+ * If the InstanceMethods module exists, Joint#smoke will cycle through the modules within it, and call their #append_features with the corresponding objects in Marley::Resources with the same name.
34
59
 
35
- For now, there are 3 joints included in the Marley distribution:
60
+ For now, there are 5 joints included in the Marley distribution:
36
61
 
37
62
  * Basic User
38
63
  * Basic Messaging
64
+ * Basic Menu System
39
65
  * Tagging
66
+ * Tagged Messaging
40
67
 
41
- With a bit of configuration and a few menus, these comprise the example forum application, which is in turn the targt for the test suite.
68
+ With a bit of configuration, these comprise the example forum application, which is in turn the targt for the test suite.
42
69
 
43
70
  ==Reggae
44
71
 
45
- The server and client use a JSON based data representation I developed for this project and tentatively named "Reggae." It is documented roughly in Reggae.ebnf. I am considering some structural changes, which are reflected in Reggae2.ebnf. I'd love some comments on this.
72
+ The server and client use a JSON based data representation format "Reggae." Reggae.ebnf describes the format in canonical form, and reggae.rb contains a Reggae parser and generator.
46
73
 
47
74
  ==Jamaica
48
75
 
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+
5
+ task :default => [:test]
6
+
7
+ Rake::TestTask.new(:test) do |t|
8
+ t.ruby_opts=['-r test/test_include']
9
+ t.test_files=FileList['test/*_tests.rb']
10
+ end
11
+
data/examples/blog.rb ADDED
@@ -0,0 +1,26 @@
1
+ require 'rubygems'
2
+ require 'sequel'
3
+ require 'digest/sha1'
4
+
5
+ $: << "#{File.dirname(__FILE__)}/../lib/"
6
+
7
+ APP_DIR=File.dirname(__FILE__)
8
+ require "marley"
9
+ require "client/jamaica"
10
+ #need to automate the following somehow but can't think of anything that isn't ugly ATM
11
+ DB=Sequel.sqlite("#{APP_DIR}/forum#{ENV["MARLEY_TESTING"] ? '_test' : ''}.sqlite3")#,:loggers => [Logger.new($stdout)])
12
+
13
+ Marley.config({:app_name => 'The Forum',:client => Marley::Client.new({:app_name => 'The Forum'})})
14
+
15
+ Marley.joint 'tagged_messaging', {:resources => ['user','admin']}
16
+ Marley.joint 'basic_menu_system'
17
+
18
+ module Marley
19
+ module Resources
20
+ class Author < User
21
+ def self.requires_user?;true;end
22
+ end
23
+ class Post < MJ::TaggedMessaging::Resources::Post
24
+ end
25
+ end
26
+ end
Binary file
Binary file
@@ -6,7 +6,7 @@ EXAMPLES_DIR=File.dirname(__FILE__) + '/../examples'
6
6
  ENV['MARLEY_TESTING']='true'
7
7
  `cp #{EXAMPLES_DIR}/empty.sqlite3 #{EXAMPLES_DIR}/forum_test.sqlite3`
8
8
  require "#{EXAMPLES_DIR}/forum.rb"
9
- require "#{EXAMPLES_DIR}/../lib/test_helpers"
9
+ require "#{EXAMPLES_DIR}/../lib/marley/test_helpers"
10
10
 
11
11
  class UserTests < Test::Unit::TestCase
12
12
  def setup
@@ -24,18 +24,18 @@ class UserTests < Test::Unit::TestCase
24
24
  end
25
25
  should "validate new user properly" do
26
26
  assert resp=@client.create
27
- assert_equal "error", resp.resource_type
27
+ assert_equal :error, resp.resource_type
28
28
  assert_equal "validation", resp.error_type
29
29
  assert_equal ["is required"], resp.error_details[:name]
30
30
  resp=@client.create({:'user[name]' => 'asdf'})
31
- assert_equal "error", resp.resource_type
31
+ assert_equal :error, resp.resource_type
32
32
  assert_equal "validation", resp.error_type
33
33
  resp=@client.create({:'user[name]' => 'asdf',:'user[password]' => 'asdfaf'})
34
- assert_equal "error", resp.resource_type
34
+ assert_equal :error, resp.resource_type
35
35
  assert_equal "validation", resp.error_type
36
36
  assert_equal ["Password must contain at least 8 characters"], resp.error_details[:password]
37
37
  resp=@client.create(:'user[name]' => 'asdf',:'user[password]' => 'asdfasdf')
38
- assert_equal "error", resp.resource_type
38
+ assert_equal :error, resp.resource_type
39
39
  assert_equal "validation", resp.error_type
40
40
  assert_equal ["Passwords do not match"], resp.error_details[:confirm_password]
41
41
  end
@@ -64,7 +64,7 @@ class UserTests < Test::Unit::TestCase
64
64
  params=user.to_params
65
65
  assert @client.update(params,{:code => 204})
66
66
  assert err=@client.update(params.update('user[password]' => 'zxcvzxcv'),{:code => 400})
67
- assert_equal "error", err.resource_type
67
+ assert_equal :error, err.resource_type
68
68
  assert_equal "validation", err.error_type
69
69
  assert @client.update(params.update('user[password]' => 'zxcvzxcv','user[confirm_password]' => 'zxcvzxcv', 'user[old_password]' => 'asdfasdf'),:code => 204)
70
70
  assert @client.read({},:code => 401)
@@ -104,18 +104,18 @@ class MessageTests < Test::Unit::TestCase
104
104
  should "validate new user generated PMs properly" do
105
105
  #reject a PM with only recipients
106
106
  resp=@client.create({:'private_message[recipients]' => 'user2'},{:code => 400})
107
- assert_equal "error", resp.resource_type
107
+ assert_equal :error, resp.resource_type
108
108
  assert_equal "validation", resp.error_type
109
109
  assert_equal ["is required"], resp.error_details[:title]
110
110
  assert_equal ["is required"], resp.error_details[:message]
111
111
  #reject a PM to a non-existent user
112
112
  resp=@client.create({:'private_message[recipients]' => 'asdfasdfasdfasdf',:'private_message[title]' => 'asdf',:'private_message[message]' => 'asdf'},{:code => 400})
113
- assert_equal "error", resp.resource_type
113
+ assert_equal :error, resp.resource_type
114
114
  assert_equal "validation", resp.error_type
115
115
  assert resp.error_details[:recipients][0]
116
116
  #reject a PM from user to user
117
117
  resp=@client.create({:'private_message[recipients]' => 'user2',:'private_message[title]' => 'asdf',:'private_message[message]' => 'asdf'},{:code => 400})
118
- assert_equal "error", resp.resource_type
118
+ assert_equal :error, resp.resource_type
119
119
  assert_equal "validation", resp.error_type
120
120
  assert resp.error_details[:recipients][0]
121
121
  end
@@ -129,7 +129,7 @@ class MessageTests < Test::Unit::TestCase
129
129
  end
130
130
  should "validate new admin generated PMs properly" do
131
131
  resp=@client.create({:'private_message[recipients]' => 'user2'},{:code => 400})
132
- assert_equal "error", resp.resource_type
132
+ assert_equal :error, resp.resource_type
133
133
  assert_equal "validation", resp.error_type
134
134
  assert_equal ["is required"], resp.error_details[:title]
135
135
  assert_equal ["is required"], resp.error_details[:message]
@@ -298,7 +298,7 @@ class MessageTests < Test::Unit::TestCase
298
298
  context 'validation' do
299
299
  should "get a validation error trying to post without a title or message as admin, user1, or user2" do
300
300
  resp=@client.create({},{:code => 400,:auth => @admin_auth})
301
- assert_equal "error", resp.resource_type
301
+ assert_equal :error, resp.resource_type
302
302
  assert_equal "validation", resp.error_type
303
303
  assert_equal ["is required"], resp.error_details[:title]
304
304
  assert_equal ["is required"], resp.error_details[:message]
@@ -5,23 +5,30 @@ module Sequel::Plugins::RestSection
5
5
  SECTION_PROPS.each {|p| attr_accessor :"section_#{p}"}
6
6
  def section
7
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 }]
8
+ Marley::ReggaeSection.new(SECTION_PROPS.inject({}) do |props,p|
9
+ prop=send(:"section_#{p}")
10
+ props[p.to_sym]=prop.class==Hash ? prop[$request[:user].class] : prop
11
+ props
12
+ end)
9
13
  end
10
14
  end
11
15
  end
12
16
  end
13
- Sequel::Model.plugin :rest_section
14
17
  module Marley
15
18
  module Joints
16
19
  class BasicMenuSystem < Joint
20
+ def smoke
21
+ super
22
+ Sequel::Model.plugin :rest_section
23
+ end
17
24
  module Resources
18
25
  class Menu
19
26
  class <<self
20
27
  attr_accessor :sections
21
28
  end
22
- attr_accessor :title,:name,:description, :navigation
29
+ include Sequel::Plugins::RestSection::ClassMethods
23
30
  def self.rest_get
24
- new.to_json
31
+ new.section
25
32
  end
26
33
  def self.requires_user?
27
34
  ! $request[:path].to_a.empty?
@@ -31,22 +38,19 @@ module Marley
31
38
  if $request[:user].new?
32
39
  u=$request[:user].to_a
33
40
  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]
41
+ @section_title="Welcome to #{$request[:opts][:app_name]}"
42
+ @section_description='Login or signup here.'
43
+ @section_navigation=[LOGIN_FORM,u]
37
44
  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)
45
+ @section_title = "#{$request[:opts][:app_name]} Main Menu"
46
+ @section_description="Welcome to #{$request[:opts][:app_name]}, #{$request[:user].name}"
47
+ @section_navigation=(self.class.sections || (MR.constants - [self.class.to_s.sub(/.*::/,'').to_sym])).map do |rn|
48
+ if (resource=MR.const_get(rn)).respond_to?(:section) && (s=resource.section) && s.title
42
49
  [:link,{:title => s.title, :description =>s.description, :url => "#{resource.resource_name}/section" }]
43
50
  end
44
51
  end.compact
45
52
  end
46
53
  end
47
- def to_json
48
- [:section,{:title => @title,:description => @description,:name => @name, :navigation => @navigation}]
49
- end
50
54
  end
51
55
  end
52
56
  end
@@ -6,6 +6,7 @@ module Marley
6
6
  module Resources
7
7
  class Message < Sequel::Model
8
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
+ # CHANGE NEEDED:tree is unnecessary, instead, select by thread_id, order by parent_id, inject [] for nesting. Should be much faster.
9
10
  plugin :tree
10
11
  many_to_one :author, :class => :'Marley::Resources::User'
11
12
  @owner_col=:author_id
@@ -22,7 +23,6 @@ module Marley
22
23
  def after_initialize
23
24
  super
24
25
  if new?
25
- self.author_id=$request[:user][:id]
26
26
  self.thread_id=parent ? parent.thread_id : Message.select(:max.sql_function(:thread_id).as(:tid)).all[0][:tid].to_i + 1
27
27
  end
28
28
  end
@@ -13,7 +13,7 @@ module Marley
13
13
  ! ($request[:verb]=='rest_post')
14
14
  end
15
15
  def self.section
16
- ReggaeSection.new([ {:title => 'User Info', :name => self.to_s.underscore, :navigation => []},$request[:user]]) if $request[:user].class == self
16
+ ReggaeSection.new( {:title => 'User Info', :name => self.to_s.sub(/.*::/,'').underscore, :navigation => []}) if $request[:user].class == self
17
17
  end
18
18
  def write_cols;[:name,:email,:password,:confirm_password,:old_password];end
19
19
  def rest_schema
@@ -9,9 +9,11 @@ module Marley
9
9
  class TaggedMessaging < Joint
10
10
  def smoke
11
11
  super
12
- MR::Tag.tagging_for('PrivateMessage', 'User')
13
- MR::Tag.tagging_for('Post', 'User')
14
- MR::Tag.tagging_for('Post')
12
+ MR::Tag.tagging_for('PrivateMessage', 'User') if MR.constants.include?('PrivateMessage')
13
+ if MR.constants.include?('Post')
14
+ MR::Tag.tagging_for('Post', 'User')
15
+ MR::Tag.tagging_for('Post')
16
+ end
15
17
  end
16
18
  module ClassMethods
17
19
  module Message
File without changes
@@ -1,7 +1,5 @@
1
1
  module Marley
2
2
  module Joints
3
- MR=Marley::Resources
4
- MJ=Marley::Joints
5
3
  class Joint
6
4
  def initialize(opts={})
7
5
  config(opts)
@@ -15,17 +15,18 @@ module Marley
15
15
  end
16
16
  def initialize(*args)
17
17
  super
18
+ self[1]=Utils.hash_keys_to_syms(self[1]) if self[1].class==Hash
18
19
  self.class.mk_prop_methods
19
20
  end
21
+ def properties
22
+ self[1] || nil
23
+ end
20
24
  def resource_type
21
- [String, Symbol].include?(self[0].class) ? self[0].to_s : nil
25
+ [String, Symbol].include?(self[0].class) ? self[0].to_sym : nil
22
26
  end
23
27
  def is_resource?
24
28
  ! resource_type.nil?
25
29
  end
26
- def properties
27
- self[1].class==Hash ? Utils.hash_keys_to_syms(self[1]) : nil
28
- end
29
30
  def contents
30
31
  is_resource? ? Reggae.new(self[2 .. -1]) : nil
31
32
  end
@@ -37,7 +38,7 @@ module Marley
37
38
  super.class==Array ? Reggae.new(super).to_resource : super
38
39
  end
39
40
  def to_resource
40
- is_resource? ? Marley.const_get("Reggae#{resource_type.camelize}".to_sym).new(self) : self
41
+ is_resource? ? Marley.const_get("Reggae#{resource_type.to_s.camelize}".to_sym).new(self) : self
41
42
  end
42
43
  def find_instances(rn,instances=Reggae.new([]))
43
44
  if self.class==ReggaeInstance && self.name.to_s==rn
@@ -49,9 +50,15 @@ module Marley
49
50
  end
50
51
  end
51
52
  class ReggaeResource < Reggae
53
+ def resource_type
54
+ self.class.to_s.sub(/.*Reggae/,'').underscore.to_sym
55
+ end
52
56
  def initialize(*args)
53
- super
54
- unshift self.class.to_s.sub(/.*Reggae/,'').underscore.to_sym unless is_resource?
57
+ if args[0].class==Hash
58
+ initialize [resource_type,args[0]]
59
+ else
60
+ super
61
+ end
55
62
  end
56
63
  end
57
64
  class ReggaeSection < ReggaeResource
@@ -64,27 +71,33 @@ module Marley
64
71
  self.valid_properties=[:title,:description,:url]
65
72
  end
66
73
  class ReggaeInstance < ReggaeResource
67
- self.valid_properties=[:name,:new_rec,:search,:url,:get_actions,:delete_action]
68
- def schema
69
- ReggaeSchema.new(self.properties[:schema])
74
+ self.valid_properties=[:name,:new_rec,:schema,:search,:url,:get_actions,:delete_action]
75
+ def initialize(*args)
76
+ super
77
+ self.schema=ReggaeSchema.new(self.schema)
70
78
  end
71
79
  def to_params
72
- resource_name=name
73
80
  schema.inject({}) do |params,spec|
74
- s=ReggaeColSpec.new(spec)
75
- params["#{resource_name}[#{s.col_name}]"]=s.col_value unless (s.col_restrictions & RESTRICT_RO > 0)
81
+ params["#{name}[#{spec.col_name}]"]=spec.col_value unless (spec.col_restrictions & RESTRICT_RO > 0)
76
82
  params
77
83
  end
78
84
  end
79
85
  def instance_action_url(action_name)
80
86
  "#{url}#{action_name}" if get_actions.include?(action_name.to_s)
81
87
  end
88
+ def col_value(col_name,col_value=nil)
89
+ col=schema[col_name]
90
+ col.col_value=col_value if col_value
91
+ col.col_value
92
+ end
93
+ def set_values(col_hash)
94
+ col_hash.each_pair {|k,v| col_value(k,v)}
95
+ self
96
+ end
82
97
  end
83
98
  class ReggaeInstanceList < ReggaeResource
84
99
  self.valid_properties=[:name,:description,:get_actions,:delete_action,:items]
85
- def schema
86
- ReggaeSchema.new(self.properties[:schema])
87
- end
100
+ #not implemented yet
88
101
  end
89
102
  class ReggaeMsg < ReggaeResource
90
103
  self.valid_properties=[:title,:description]
@@ -93,11 +106,15 @@ module Marley
93
106
  self.valid_properties=[:error_type,:description,:error_details]
94
107
  end
95
108
  class ReggaeSchema < Array
109
+ def initialize(*args)
110
+ super
111
+ replace(map{|spec| ReggaeColSpec.new(spec)})
112
+ end
96
113
  def [](i)
97
114
  if i.class==Fixnum
98
- ReggaeColSpec.new(super)
115
+ super
99
116
  else
100
- self[find_index {|cs|ReggaeColSpec.new(cs).col_name==i.to_s}]
117
+ find {|cs|cs.col_name.to_s==i.to_s}
101
118
  end
102
119
  end
103
120
  end
@@ -1,5 +1,5 @@
1
1
  require "rack/test"
2
- require 'reggae'
2
+ require 'marley/reggae'
3
3
  module Marley
4
4
  #simple mocking framework; could be expanded to a general use client by adding display code.
5
5
  class TestClient
@@ -18,9 +18,11 @@ module Marley
18
18
  opts[:url] || '/' + [:root_url, :resource_name, :instance_id, :method].map {|k| opts[k]}.compact.join('/') + opts[:extention].to_s
19
19
  end
20
20
  def process(verb,params={},opts={})
21
+ #p 'params:',params,'opts:',opts,"-------------" if opts
21
22
  opts||={}
22
23
  opts=@opts.merge(opts)
23
24
  expected_code=opts[:code] || RESP_CODES[verb]
25
+ params=params.to_params if params.respond_to?(:to_params)
24
26
  if opts[:debug]
25
27
  p opts
26
28
  p "#{verb} to: '#{make_url(opts)}'"
@@ -30,14 +32,16 @@ module Marley
30
32
  authorize opts[:auth][0],opts[:auth][1] if opts[:auth]
31
33
  header 'Authorization',nil unless opts[:auth] #clear auth from previous requests
32
34
  send(verb,make_url(opts),params)
35
+ #p last_response.body if opts[:debug]
36
+ #p JSON.parse(last_response.body) if opts[:debug]
33
37
  p last_response.status if opts[:debug]
34
38
  p expected_code if opts[:debug]
35
39
  return false unless (expected_code || RESP_CODES[method])==last_response.status
36
40
  Reggae.get_resource(JSON.parse(last_response.body)) rescue last_response.body
37
41
  end
38
42
  ['create','read','update','del'].each do |op|
39
- define_method op.to_sym, Proc.new { |params,opts|
40
- process(CRUD2REST[op],params,opts)
43
+ define_method op.to_sym, Proc.new { |*args|
44
+ process(CRUD2REST[op],args[0],args[1])
41
45
  }
42
46
  end
43
47
  DEFAULT_OPTS.keys.each do |opt|
data/lib/marley.rb CHANGED
@@ -3,9 +3,10 @@ require 'json/ext'
3
3
  require 'rack'
4
4
  require 'rack/auth/basic'
5
5
  require 'rack/builder'
6
- require 'sequel_plugins'
7
- require 'controllers'
8
- require 'reggae'
6
+ require 'sequel/plugins/rest_convenience'
7
+ require 'sequel/plugins/rest_auth'
8
+ require 'marley/controllers'
9
+ require 'marley/reggae'
9
10
  require 'logger'
10
11
  Sequel.extension :inflector
11
12
 
@@ -26,7 +27,9 @@ module Marley #The main Marley namespace.
26
27
 
27
28
  module Resources #All objects in the Resources namespace are exposed by the server.
28
29
  end
29
- require 'joint' #this needs to happen after Marley::Resources is defined
30
+ module Joints
31
+ end
32
+ require 'marley/joint' #this needs to happen after Marley::Resources is defined
30
33
  def self.config(opts=nil)
31
34
  @marley_opts||=DEFAULT_OPTS
32
35
  @marley_opts.merge!(opts) if opts
@@ -36,7 +39,7 @@ module Marley #The main Marley namespace.
36
39
  def self.joint(joint_name, *opts)
37
40
  joint_d=JOINT_DIRS.find {|d| File.exists?("#{d}/#{joint_name}.rb") }
38
41
  require "#{joint_d}/#{joint_name}"
39
- @marley_opts[:client] && @marley_opts[:client].joint(joint_d,joint_name)
42
+ @marley_opts && @marley_opts[:client] && @marley_opts[:client].joint(joint_d,joint_name)
40
43
  joint=Marley::Joints.const_get(joint_name.camelize).new(*opts).smoke
41
44
  end
42
45
  def self.run(opts={})
@@ -145,4 +148,6 @@ module Marley #The main Marley namespace.
145
148
  end
146
149
  end
147
150
  end
151
+ MR=Marley::Resources
152
+ MJ=Marley::Joints
148
153
  at_exit {Marley.run if ARGV[0]=='run'}
@@ -0,0 +1,53 @@
1
+
2
+ module Sequel::Plugins::RestAuthorization
3
+ module ClassMethods
4
+ attr_accessor :owner_col, :allowed_get_methods
5
+ def inherited(c)
6
+ super
7
+ c.owner_col=@owner_col
8
+ c.allowed_get_methods=@allowed_get_methods
9
+ end
10
+ def requires_user?(verb=nil,meth=nil);true;end
11
+ def authorize(meth)
12
+ if respond_to?(auth_type="authorize_#{$request[:verb]}")
13
+ send(auth_type,meth)
14
+ else
15
+ case $request[:verb]
16
+ when 'rest_put','rest_delete'
17
+ false
18
+ when 'rest_post'
19
+ new($request[:post_params][resource_name.to_sym]||{}).current_user_role=='owner' && meth.nil?
20
+ when 'rest_get'
21
+ methods=@allowed_get_methods || ['section','list','new']
22
+ (methods.class==Hash ? methods[$request[:user].class] : methods).include?(meth)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ module InstanceMethods
28
+ def after_initialize
29
+ super
30
+ send("#{self.class.owner_col}=",$request[:user][:id]) if $request && self.class.owner_col && new?
31
+ end
32
+ def requires_user?(verb=nil,meth=nil);true;end
33
+ def authorize(meth)
34
+ if respond_to?(auth_type="authorize_#{$request[:verb]}")
35
+ send(auth_type,meth)
36
+ else
37
+ current_user_role=='owner'
38
+ end
39
+ end
40
+ def current_user_role
41
+ "owner" if owners.include?($request[:user])
42
+ end
43
+ def owners
44
+ if self.class.to_s.match(/User$/)||self.class.superclass.to_s.match(/User$/)
45
+ [self]
46
+ elsif @owner_col
47
+ [User[send(@owner_col)]]
48
+ else
49
+ 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
50
+ end
51
+ end
52
+ end
53
+ end
@@ -1,3 +1,4 @@
1
+
1
2
  RESTRICT_HIDE=1
2
3
  RESTRICT_RO=2
3
4
  RESTRICT_REQ=4
@@ -20,7 +21,7 @@ module Sequel::Plugins::RestConvenience
20
21
  if user.respond_to?(otm=self.resource_name.pluralize)
21
22
  if user.method(otm).arity==0
22
23
  if (relationship=user.send(otm)).respond_to?(:filter)
23
- relationship.filter($request[:get_params][resource_name.to_sym])
24
+ relationship.filter($request[:get_params][resource_name.to_sym] || {}).all
24
25
  else
25
26
  user.send(otm)
26
27
  end
@@ -69,7 +70,7 @@ module Sequel::Plugins::RestConvenience
69
70
  respond_to?('name') ? name : id.to_s
70
71
  end
71
72
  def to_a
72
- a=Marley::ReggaeInstance.new([ {:name => self.class.resource_name,:url => url ,:new_rec => self.new?,:schema => rest_schema,:get_actions => get_actions}])
73
+ a=Marley::ReggaeInstance.new( {:name => self.class.resource_name,:url => url ,:new_rec => self.new?,:schema => rest_schema,:get_actions => get_actions})
73
74
  if respond_to?(:rest_associations) && ! new?
74
75
  a.contents=rest_associations.map do |assoc|
75
76
  (assoc.class==Symbol ? send(assoc) : assoc).map{|instance| instance.to_a}
@@ -88,54 +89,3 @@ module Sequel::Plugins::RestConvenience
88
89
  end
89
90
  end
90
91
  end
91
- module Sequel::Plugins::RestAuthorization
92
- module ClassMethods
93
- attr_accessor :owner_col, :allowed_get_methods
94
- def inherited(c)
95
- super
96
- c.owner_col=@owner_col
97
- c.allowed_get_methods=@allowed_get_methods
98
- end
99
- def requires_user?(verb=nil,meth=nil);true;end
100
- def authorize(meth)
101
- if respond_to?(auth_type="authorize_#{$request[:verb]}")
102
- send(auth_type,meth)
103
- else
104
- case $request[:verb]
105
- when 'rest_put','rest_delete'
106
- false
107
- when 'rest_post'
108
- new($request[:post_params][resource_name.to_sym]||{}).current_user_role=='owner' && meth.nil?
109
- when 'rest_get'
110
- (@allowed_get_methods || ['section','list','new']).include?(meth)
111
- end
112
- end
113
- end
114
- end
115
- module InstanceMethods
116
- def after_initialize
117
- send("#{self.class.owner_col}=",$request[:user][:id]) if self.class.owner_col && new?
118
- end
119
- def requires_user?(verb=nil,meth=nil);true;end
120
- def authorize(meth)
121
- if respond_to?(auth_type="authorize_#{$request[:verb]}")
122
- send(auth_type,meth)
123
- else
124
- current_user_role=='owner'
125
- end
126
- end
127
- def current_user_role
128
- "owner" if owners.include?($request[:user])
129
- end
130
- def owners
131
- if self.class.to_s.match(/User$/)||self.class.superclass.to_s.match(/User$/)
132
- [self]
133
- elsif @owner_col
134
- [User[send(@owner_col)]]
135
- else
136
- 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
137
- end
138
- end
139
- end
140
- end
141
-
data/marley-0.1.0.gem ADDED
Binary file
data/marley.gemspec CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{marley}
5
- s.version = "0.1.0"
5
+ s.version = "0.2.0"
6
6
  s.summary = %q{Irie default restful routes for your models and other objects}
7
7
  s.description = %q{Marley implements a web services microframework on top of Rack and Sequel on the server side and Jquery on the client side.}
8
8
  s.authors = ["Herb Daily"]
Binary file
@@ -0,0 +1,9 @@
1
+
2
+
3
+ class MenuTests < Test::Unit::TestCase
4
+ def setup
5
+ end
6
+ should 'work' do
7
+ assert true
8
+ end
9
+ end
@@ -0,0 +1,289 @@
1
+
2
+ class MessageTests < Test::Unit::TestCase
3
+ def initialize(*args)
4
+ super
5
+ MR::User.delete
6
+ @client=Marley::TestClient.new(:resource_name => 'user')
7
+ ['user1','user2','admin'].each do |un|
8
+ MR::User.new(:name => un,:password => 'asdfasdf', :confirm_password => 'asdfasdf').save
9
+ end
10
+ MR::User[:name => 'admin'].update(:user_type => 'Admin')
11
+ @admin_auth=['admin','asdfasdf']
12
+ @user1_auth=['user1','asdfasdf']
13
+ @user2_auth=['user2','asdfasdf']
14
+ end
15
+ def setup
16
+ MR::Message.delete
17
+ MR::Tag.delete
18
+ DB[:messages_tags].delete
19
+ end
20
+ context "Private Messages" do
21
+ setup do
22
+ @client.resource_name='private_message'
23
+ @pm=MR::PrivateMessage.new.to_a
24
+ end
25
+ context "regular user (user1) logged in" do
26
+ setup do
27
+ @client.auth=@user1_auth
28
+ end
29
+ should "show PM list and new PM form" do
30
+ assert @client.read({})
31
+ assert @client.read({}, :method => 'new')
32
+ end
33
+ should "validate new user generated PMs properly" do
34
+ #reject a PM with only recipients
35
+ @pm.col_value(:recipients, 'user2')
36
+ resp=@client.create(@pm,{:code => 400})
37
+ assert_equal :error, resp.resource_type
38
+ assert_equal "validation", resp.error_type
39
+ assert_equal ["is required"], resp.error_details[:title]
40
+ assert_equal ["is required"], resp.error_details[:message]
41
+ #reject a PM from user to user
42
+ @pm.set_values({:title => 'asdf', :message => 'asdf'})
43
+ resp=@client.create(@pm,{:code => 400})
44
+ assert_equal :error, resp.resource_type
45
+ assert_equal "validation", resp.error_type
46
+ assert resp.error_details[:recipients][0]
47
+ #reject a PM to a non-existent user
48
+ @pm.col_value(:recipients, 'asdfasdfasdf')
49
+ resp=@client.create(@pm,{:code => 400})
50
+ assert_equal :error, resp.resource_type
51
+ assert_equal "validation", resp.error_type
52
+ assert resp.error_details[:recipients][0]
53
+ #accept a PM to admin
54
+ @pm.col_value(:recipients, 'admin')
55
+ assert @client.create({:'private_message[recipients]' => 'admin',:'private_message[title]' => 'asdf',:'private_message[message]' => 'asdf'})
56
+ end
57
+ end
58
+ context "admin logged in" do
59
+ setup do
60
+ @client.auth=@admin_auth
61
+ end
62
+ should "validate new admin generated PMs properly" do
63
+ resp=@client.create(@pm.set_values(:recipients => 'user2'),{:code => 400})
64
+ assert_equal :error, resp.resource_type
65
+ assert_equal "validation", resp.error_type
66
+ assert_equal ["is required"], resp.error_details[:title]
67
+ assert_equal ["is required"], resp.error_details[:message]
68
+ end
69
+ should "accept a PM to user1" do
70
+ assert @client.create(@pm.set_values(:'recipients' => 'user1',:'title' => 'asdf',:'message' => 'asdf'))
71
+ end
72
+ end
73
+ context "message with no tags" do
74
+ setup do
75
+ @client.auth=@admin_auth
76
+ @client.create(@pm.set_values({:'recipients' => 'user1',:'title' => 'asdf',:'message' => 'asdf'}))
77
+ end
78
+ should "show up in PM list of sender and receiver" do
79
+ resp=@client.read({})
80
+ assert_equal 1, resp.length
81
+ resp=@client.read({},{:auth => @user1_auth})
82
+ assert_equal 1, resp.length
83
+ end
84
+ should "have sent tag for sender" do
85
+ resp=@client.read({})
86
+ assert_equal 3, resp[0].length
87
+ assert_equal "sent", resp.find_instances('user_tag')[0].schema[:tag].col_value
88
+ end
89
+ should "have inbox tag for receiver" do
90
+ resp=@client.read({},{:auth => @user1_auth})
91
+ assert_equal 3, resp[0].length
92
+ assert_equal "inbox", resp.find_instances('user_tag')[0].schema[:tag].col_value
93
+ end
94
+ should "have reply, reply_all and new_tags instance get actions" do
95
+ resp=@client.read({})
96
+ assert_same_elements ['reply','reply_all','new_tags'], resp[0].get_actions
97
+ end
98
+ context "user1 instance actions" do
99
+ setup do
100
+ @client.auth=@user1_auth
101
+ @msg=@client.read({})[0]
102
+ @client.instance_id=@msg.schema[:id].col_value
103
+ @reply=@client.read({},{:method => 'reply'})
104
+ @new_tags=@client.read({},:method => 'new_tags')
105
+ end
106
+ context "reply" do
107
+ should "have author in to field and default title beginning with 're:'" do
108
+ assert_equal 'admin', @reply.schema[:recipients].col_value
109
+ assert_equal 're: ', @reply.schema[:title].col_value[0 .. 3]
110
+ end
111
+ should "accept reply" do
112
+ assert @client.create(@reply.set_values('message' => 'asdf'),{:method => nil,:instance_id => nil})
113
+ end
114
+ end
115
+ context "new tags" do
116
+ should "return tag instance with name tag and same url as original message" do
117
+ assert_equal 'tags', @new_tags.name
118
+ assert_equal "#{@msg.url}tags", @new_tags.url
119
+ end
120
+ should "accept new tags, which should then show up with the original message" do
121
+ assert @client.create({'private_message[tags]' => 'added_tag1, added_tag2'},{:method => 'tags'})
122
+ msg=@client.read({})
123
+ user_tags=msg.find_instances('user_tag')
124
+ assert_same_elements ["inbox", "added_tag1", "added_tag2"], user_tags.map{|t| t.schema[:tag].col_value}
125
+ end
126
+ end
127
+ end
128
+ end
129
+ context "message with 2 tags" do
130
+ setup do
131
+ @client.auth=@admin_auth
132
+ @client.create(@pm.set_values(:recipients => 'user1', :title => 'asdf', :message => 'asdf', :tags => 'test,test2'))
133
+ end
134
+ context "sender (admin) logged in" do
135
+ setup do
136
+ @msg=@client.read[0]
137
+ @tags=@msg.find_instances('user_tag')
138
+ end
139
+ should "have sent tag and both specified tags for sender" do
140
+ assert_same_elements ["sent", "test", "test2"], @tags.map{|t| t.schema[:tag].col_value}
141
+ end
142
+ should "allow sender to remove his own tags'" do
143
+ assert_equal 'remove_parent', @tags[0].delete_action
144
+ assert @client.del({},{:url => @tags[0].url+@msg.url})
145
+ assert_equal 2, @client.read[0].find_instances('user_tag').length
146
+ end
147
+ end
148
+ context "receiver (user1)" do
149
+ setup do
150
+ @client.auth=@user1_auth
151
+ @msg=@client.read[0]
152
+ @tags=@msg.find_instances('user_tag')
153
+ end
154
+ should "have inbox tag and both specified tags" do
155
+ assert_same_elements ["inbox", "test", "test2"], @tags.map{|t| t.schema[:tag].col_value}
156
+ end
157
+ should "have specified tags in reply" do
158
+ reply=@client.read({},{:instance_id => @msg.schema[:id].col_value,:method => 'reply'})
159
+ assert_equal 'test,test2', reply.schema[:tags].col_value
160
+ end
161
+ should "allow receiver to remove his own tags'" do
162
+ assert_equal 'remove_parent', @tags[0].delete_action
163
+ assert @client.del({},{:url => @tags[0].url+@msg.url})
164
+ assert_equal 2, @client.read[0].find_instances('user_tag').length
165
+ end
166
+ end
167
+ context 'user2' do
168
+ should "have no messages" do
169
+ assert resp=@client.read({},{:auth => @user2_auth})
170
+ assert_equal 0, resp.length
171
+ end
172
+ end
173
+ end
174
+ context "message with 2 tags and 2 receivers" do
175
+ setup do
176
+ @client.create(@pm.set_values(:recipients => 'user1,user2',:title => 'asdf',:message => 'asdf', :tags => 'test,test2'),{:auth => @admin_auth})
177
+ end
178
+ should "have sent tag and both specified for sender" do
179
+ resp=@client.read({},{:auth => @admin_auth})
180
+ user_tags=resp[0].find_instances('user_tag')
181
+ assert_same_elements ["sent", "test", "test2"], user_tags.map{|t| t.schema[:tag].col_value}
182
+ end
183
+ should "have inbox tag and both specified for 1st receiver (user1)" do
184
+ resp=@client.read({},{:auth => @user1_auth})
185
+ user_tags=resp[0].find_instances('user_tag')
186
+ assert_same_elements ["inbox", "test", "test2"], user_tags.map{|t| t.schema[:tag].col_value}
187
+ end
188
+ should "have inbox tag and both specified for 2st receiver (user2)" do
189
+ resp=@client.read({},{:auth => @user2_auth})
190
+ user_tags=resp[0].find_instances('user_tag')
191
+ assert_same_elements ["inbox", "test", "test2"], user_tags.map{|t| t.schema[:tag].col_value}
192
+ end
193
+ end
194
+ context "message listing" do
195
+ setup do
196
+ #3 messages with tag "test" for user 1
197
+ @client.create(@pm.set_values(:'recipients' => 'user1',:'title' => 'title1',:'message' => 'body1', :'tags' => 'test'),{:auth => @admin_auth})
198
+ @client.create(@pm.set_values(:'recipients' => 'user1',:'title' => 'title2',:'message' => 'body2', :'tags' => 'test'),{:auth => @admin_auth})
199
+ @client.create(@pm.set_values(:'recipients' => 'user1',:'title' => 'title3',:'message' => 'body3', :'tags' => 'test'),{:auth => @admin_auth})
200
+ #2 messages with tag "test1" for user1 and user2
201
+ @client.create(@pm.set_values(:'recipients' => 'user2,user1',:'title' => 'title1',:'message' => 'body1', :'tags' => 'test1'),{:auth => @admin_auth})
202
+ @client.create(@pm.set_values(:'recipients' => 'user2,user1',:'title' => 'title2',:'message' => 'body2', :'tags' => 'test1'),{:auth => @admin_auth})
203
+ end
204
+ should "for sender (admin) show 3 messages with 'test' tag,2 messages with 'test1' tag, and 5 messages with 'sent' tag" do
205
+ @client.auth=@admin_auth
206
+ assert_equal 3, @client.read({:'private_message[tags]' => 'test'}).length
207
+ assert_equal 2, @client.read({:'private_message[tags]' => 'test1'}).length
208
+ assert_equal 5, @client.read({:'private_message[tags]' => 'sent'}).length
209
+ end
210
+ should "for user1 show 3 messages with 'test' tag, 2 messages with 'test1' tag, 5 messages with 'inbox' tag, and 5 messages with 'test' or 'test1' tags" do
211
+ @client.auth=@user1_auth
212
+ assert_equal 3, @client.read({:'private_message[tags]' => 'test'}).length
213
+ assert_equal 2, @client.read({:'private_message[tags]' => 'test1'}).length
214
+ assert_equal 5, @client.read({:'private_message[tags]' => 'inbox'}).length
215
+ assert_equal 5, @client.read({:'private_message[tags]' => 'test,test1'}).length
216
+ end
217
+ should "for user2 show 0 messages with 'test' tag, 2 messages with 'test1' tag, 2 messages with 'inbox' tag and 2 messages with 'test' or 'test1' tags" do
218
+ @client.auth=@user2_auth
219
+ assert_equal 0, @client.read({:'private_message[tags]' => 'test'}).length
220
+ assert_equal 2, @client.read({:'private_message[tags]' => 'test1'}).length
221
+ assert_equal 2, @client.read({:'private_message[tags]' => 'inbox'}).length
222
+ assert_equal 2, @client.read({:'private_message[tags]' => 'test,test1'}).length
223
+ end
224
+ end
225
+ end
226
+ context "Posts" do
227
+ setup do
228
+ @client.resource_name='post'
229
+ @post=MR::Post.new.to_a
230
+ end
231
+ context 'validation' do
232
+ should "get a validation error trying to post without a title or message as admin, user1, or user2" do
233
+ resp=@client.create({},{:code => 400,:auth => @admin_auth})
234
+ assert_equal :error, resp.resource_type
235
+ assert_equal "validation", resp.error_type
236
+ assert_equal ["is required"], resp.error_details[:title]
237
+ assert_equal ["is required"], resp.error_details[:message]
238
+ user1_resp=@client.create({},{:code => 400,:auth => @user1_auth})
239
+ assert_equal user1_resp, resp
240
+ user2_resp=@client.create({},{:code => 400,:auth => @user2_auth})
241
+ assert_equal user2_resp, resp
242
+ end
243
+ should "be able to post with title and message as admin, user1, or user2" do
244
+ assert @client.create(@post.set_values('title' => 'test', 'message' => 'asdf'),{:auth => @admin_auth})
245
+ assert_equal 1, @client.read({},{:auth => @user1_auth}).length
246
+ assert @client.create(@post.set_values('title' => 'test', 'message' => 'asdf'),{:auth => @user1_auth})
247
+ assert_equal 2, @client.read({},{:auth => @user2_auth}).length
248
+ assert @client.create(@post.set_values('title' => 'test', 'message' => 'asdf'),{:auth => @user2_auth})
249
+ assert_equal 3, @client.read({},{:auth => @admin_auth}).length
250
+ end
251
+ end
252
+ should 'list posts by public tags' do
253
+ @client.create(@post.set_values('title' => 'test', 'message' => 'asdf','tags' => 'admintag1,admintag2'),{:auth => @admin_auth})
254
+ @client.create(@post.set_values('title' => 'test', 'message' => 'asdf','tags' => 'admintag1,admintag2,admintag3'),{:auth => @admin_auth})
255
+ @client.create(@post.set_values('title' => 'test', 'message' => 'asdf','tags' => 'user1tag1'),{:auth => @user1_auth})
256
+ @client.create(@post.set_values('title' => 'test', 'message' => 'asdf','tags' => 'user1tag1,user1tag2'),{:auth => @user1_auth})
257
+ @client.create(@post.set_values('title' => 'test', 'message' => 'asdf','tags' => 'user1tag1,user1tag2,user1tag3'),{:auth => @user1_auth})
258
+ @client.create(@post.set_values('title' => 'test', 'message' => 'asdf','tags' => 'user2tag1,user2tag2,user2tag3'),{:auth => @user2_auth})
259
+ @client.create(@post.set_values('title' => 'test', 'message' => 'asdf','tags' => 'user2tag1,user2tag2,user2tag3,user2tag4'),{:auth => @user2_auth})
260
+ assert_equal 7, @client.read({},{:auth => @admin_auth}).length
261
+ assert_equal 7, @client.read({'post[title]' => 'test'},{:auth => @admin_auth}).length
262
+ assert_equal 7, @client.read({'post[title]' => 'test'},{:auth => @user1_auth}).length
263
+ assert_equal 7, @client.read({'post[title]' => 'test'},{:auth => @user2_auth}).length
264
+ assert_equal 2, @client.read({'post[tags]' => 'admintag1'},{:auth => @admin_auth}).length
265
+ assert_equal 2, @client.read({'post[tags]' => 'admintag1'},{:auth => @user1_auth}).length
266
+ assert_equal 1, @client.read({'post[tags]' => 'admintag3'},{:auth => @user2_auth}).length
267
+ assert_equal 1, @client.read({'post[tags]' => 'admintag3'},{:auth => @admin_auth}).length
268
+ assert_equal 2, @client.read({'post[tags]' => 'user1tag2'},{:auth => @admin_auth}).length
269
+ assert_equal 3, @client.read({'post[tags]' => 'user1tag1'},{:auth => @admin_auth}).length
270
+ assert_equal 3, @client.read({'post[tags]' => 'user1tag1'},{:auth => @user2_auth}).length
271
+ end
272
+ should 'have usable reply, new_tags, and new_user_tags instance actions' do
273
+ @client.create(@post.set_values('title' => 'test', 'message' => 'asdf','tags' => 'admintag1,admintag2'),{:auth => @admin_auth})
274
+ @client.auth=@user2_auth
275
+ posts=@client.read({})
276
+ assert_same_elements ['reply','new_tags','new_user_tags'], posts[0].get_actions
277
+ reply=@client.read({},{:instance_id => posts[0].schema[:id].col_value,:method => 'reply'})
278
+ tags=@client.read({},{:instance_id => posts[0].schema[:id].col_value,:method => 'new_tags'})
279
+ user_tags=@client.read({},{:instance_id => posts[0].schema[:id].col_value,:method => 'new_user_tags'})
280
+ assert_equal 're: test', reply.schema[:title].col_value
281
+ assert @client.create(reply.set_values('message' => 'asdf'),{:method => nil,:instance_id => nil})
282
+ assert @client.create(tags.to_params.merge('post[tags]' => '1,2,3'),{:url => tags.url})
283
+ assert_same_elements ['1','2','3','admintag1','admintag2'], @client.read[0].find_instances('public_tag').map{|t| t.schema[:tag].col_value}
284
+ assert @client.create(user_tags.to_params.merge('post[user_tags]' => '4,5,6'),{:url => user_tags.url})
285
+ assert_same_elements ['4','5','6'], @client.read[0].find_instances('user_tag').map{|t| t.schema[:tag].col_value}
286
+ assert_equal [], @client.read({},{:auth => @user1_auth})[0].find_instances('user_tag').map{|t| t.schema[:tag].col_value}
287
+ end
288
+ end
289
+ end
data/test/test.sqlite3 ADDED
Binary file
@@ -0,0 +1,16 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+ require 'sequel'
5
+ require 'marley'
6
+ require 'marley/test_helpers'
7
+
8
+ `cp test/empty.sqlite3 test/test.sqlite3`
9
+ DB=Sequel.sqlite("test/test.sqlite3")
10
+
11
+ RESERVED_PM_TAGS=['inbox','sent']
12
+ RESERVED_POST_TAGS=['announcement']
13
+
14
+ Marley.joint 'tagged_messaging'
15
+ Marley.joint 'basic_menu_system'
16
+
@@ -0,0 +1,72 @@
1
+
2
+ class UserTests < Test::Unit::TestCase
3
+ def setup
4
+ MR::User.delete
5
+ @client=Marley::TestClient.new(:resource_name => 'user',:code => 200)
6
+ @user=@client.read({},:resource_name => '',:code => 200).navigation[1]
7
+ end
8
+ should "return login form with no params" do
9
+ assert @client.read({},{:resource_name => ''})
10
+ end
11
+ should "not allow access to menus, private messages, or posts" do
12
+ @client.code=401
13
+ assert @client.read({:resource_name =>'pm_menu'})
14
+ assert @client.read({:resource_name =>'post_menu'})
15
+ assert @client.read({:resource_name =>'private_message'})
16
+ assert @client.read({:resource_name =>'post'})
17
+ end
18
+ should "validate new user properly" do
19
+ @client.code=400
20
+ resp=@client.create(@user)
21
+ assert_equal :error, resp.resource_type
22
+ assert_equal "validation", resp.error_type
23
+ assert_equal ["is required"], resp.error_details[:name]
24
+ @user.col_value(:name,'asdf')
25
+ resp=@client.create(@user)
26
+ assert_equal :error, resp.resource_type
27
+ assert_equal "validation", resp.error_type
28
+ @user.col_value(:password,'asdfaf')
29
+ resp=@client.create(@user)
30
+ assert_equal :error, resp.resource_type
31
+ assert_equal "validation", resp.error_type
32
+ assert_equal ["Password must contain at least 8 characters"], resp.error_details[:password]
33
+ @user.col_value(:password,'asdfaasdf')
34
+ resp=@client.create(@user)
35
+ assert_equal :error, resp.resource_type
36
+ assert_equal "validation", resp.error_type
37
+ assert_equal ["Passwords do not match"], resp.error_details[:confirm_password]
38
+ @user.col_value(:confirm_password,'asdfaasdf')
39
+ assert @client.create(@user,{:code => 201})
40
+ assert @client.create(@user)
41
+ end
42
+ context "existing user logged in" do
43
+ setup do
44
+ @client.code=201
45
+ assert @client.create(:'user[name]' => 'user1',:'user[password]' => 'asdfasdf',:'user[confirm_password]' => 'asdfasdf')
46
+ assert @client.create(:'user[name]' => 'user2',:'user[password]' => 'asdfasdf',:'user[confirm_password]' => 'asdfasdf')
47
+ @client.code=200
48
+ @client.auth=['user1','asdfasdf']
49
+ end
50
+ should "show correct menu items" do
51
+ menu= @client.read({},:resource_name => '')
52
+ assert_same_elements ["User Info","Private Messages","Public Posts"], menu.navigation.map{|n| n.title}
53
+ end
54
+ should "allow viewing and changing of user columns with proper validation" do
55
+ @client.instance_id=1
56
+ assert user=@client.read({})
57
+ assert @client.update(user,{:code => 204})
58
+ user.col_value(:password, 'zxcvzxcv')
59
+ assert err=@client.update(user,{:code => 400})
60
+ assert_equal :error, err.resource_type
61
+ assert_equal "validation", err.error_type
62
+ user.col_value(:confirm_password, 'zxcvzxcv')
63
+ user.col_value(:old_password, 'asdfasdf')
64
+ assert @client.update(user,:code => 204)
65
+ assert @client.read({},:code => 401)
66
+ @client.auth=['user1','zxcvzxcv']
67
+ assert @client.read({})
68
+ @client.instance_id=2
69
+ assert @client.update(user,:code => 403)
70
+ end
71
+ end
72
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: marley
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Herb Daily
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2011-11-09 00:00:00 -03:00
12
+ date: 2011-11-18 00:00:00 -03:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -51,16 +51,21 @@ extensions: []
51
51
  extra_rdoc_files: []
52
52
 
53
53
  files:
54
- - marley-0.1.gem
55
54
  - marley.gemspec
55
+ - Rakefile
56
56
  - Favicon.ico
57
57
  - README.rdoc
58
58
  - marley-0.1.0.gem
59
59
  - TODO
60
60
  - reggae.ebnf
61
+ - forum_tests.rb
61
62
  - lib/marley.rb
62
- - lib/test_helpers.rb
63
- - lib/sequel_plugins.rb
63
+ - lib/marley/test_helpers.rb
64
+ - lib/marley/joint.rb
65
+ - lib/marley/controllers.rb
66
+ - lib/marley/reggae.rb
67
+ - lib/sequel/plugins/rest_convenience.rb
68
+ - lib/sequel/plugins/rest_auth.rb
64
69
  - lib/client/jamaica.css
65
70
  - lib/client/jquery-1.6.2.js
66
71
  - lib/client/jamaica.rb
@@ -72,18 +77,21 @@ files:
72
77
  - lib/joints/user_based_navigation.rb
73
78
  - lib/joints/basic_menu_system.rb
74
79
  - lib/joints/tagged_messaging.rb
75
- - lib/joint.rb
76
- - lib/controllers.rb
77
- - lib/reggae.rb
78
80
  - examples/forum.js
79
81
  - examples/forum.sql
82
+ - examples/blog.rb
80
83
  - examples/forum.rb
81
84
  - examples/forum.sqlite3
82
85
  - examples/forum_test.sqlite3
83
86
  - examples/empty.sqlite3
84
87
  - examples/forum.css
85
88
  - examples/run.sh
86
- - test/forum_tests.rb
89
+ - test/menu_tests.rb
90
+ - test/test_include.rb
91
+ - test/empty.sqlite3
92
+ - test/user_tests.rb
93
+ - test/test.sqlite3
94
+ - test/tagged_messaging_tests.rb
87
95
  has_rdoc: true
88
96
  homepage: http://github.com/herbdaily/marley
89
97
  licenses: []
data/marley-0.1.gem DELETED
Binary file