orange 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. data/lib/orange/application.rb +1 -1
  2. data/lib/orange/carton.rb +55 -27
  3. data/lib/orange/cartons/site_carton.rb +4 -1
  4. data/lib/orange/core.rb +10 -2
  5. data/lib/orange/magick.rb +11 -0
  6. data/lib/orange/middleware/access_control.rb +16 -7
  7. data/lib/orange/middleware/base.rb +40 -1
  8. data/lib/orange/middleware/flex_router.rb +59 -0
  9. data/lib/orange/middleware/route_context.rb +2 -2
  10. data/lib/orange/middleware/route_site.rb +1 -1
  11. data/lib/orange/middleware/show_exceptions.rb +3 -1
  12. data/lib/orange/middleware/site_load.rb +14 -1
  13. data/lib/orange/middleware/static.rb +0 -2
  14. data/lib/orange/middleware/template.rb +3 -1
  15. data/lib/orange/resources/admin_resource.rb +25 -0
  16. data/lib/orange/resources/mapper.rb +3 -0
  17. data/lib/orange/resources/model_resource.rb +15 -9
  18. data/lib/orange/resources/page_parts.rb +0 -7
  19. data/lib/orange/resources/parser.rb +5 -4
  20. data/lib/orange/resources/singleton_model_resource.rb +7 -0
  21. data/lib/orange/resources/sitemap_resource.rb +96 -0
  22. data/lib/orange/stack.rb +5 -5
  23. data/spec/{application_spec.rb → orange/application_spec.rb} +0 -0
  24. data/spec/orange/carton_spec.rb +136 -0
  25. data/spec/{core_spec.rb → orange/core_spec.rb} +10 -0
  26. data/spec/{magick_spec.rb → orange/magick_spec.rb} +11 -0
  27. data/spec/orange/middleware/access_control_spec.rb +3 -0
  28. data/spec/orange/middleware/base_spec.rb +37 -0
  29. data/spec/orange/middleware/database_spec.rb +3 -0
  30. data/spec/orange/middleware/globals_spec.rb +3 -0
  31. data/spec/orange/middleware/recapture_spec.rb +3 -0
  32. data/spec/orange/middleware/rerouter_spec.rb +3 -0
  33. data/spec/orange/middleware/restful_router_spec.rb +3 -0
  34. data/spec/orange/middleware/route_context_spec.rb +3 -0
  35. data/spec/orange/middleware/route_site_spec.rb +3 -0
  36. data/spec/orange/middleware/show_exceptions_spec.rb +3 -0
  37. data/spec/orange/middleware/site_load_spec.rb +26 -0
  38. data/spec/orange/middleware/static_file_spec.rb +3 -0
  39. data/spec/orange/middleware/static_spec.rb +3 -0
  40. data/spec/orange/middleware/template_spec.rb +3 -0
  41. data/spec/{mock → orange/mock}/mock_app.rb +0 -0
  42. data/spec/orange/mock/mock_carton.rb +43 -0
  43. data/spec/{mock → orange/mock}/mock_core.rb +0 -0
  44. data/spec/{mock → orange/mock}/mock_middleware.rb +8 -0
  45. data/spec/{mock → orange/mock}/mock_mixins.rb +0 -0
  46. data/spec/{mock → orange/mock}/mock_model_resource.rb +4 -0
  47. data/spec/{mock → orange/mock}/mock_pulp.rb +0 -0
  48. data/spec/{mock → orange/mock}/mock_resource.rb +0 -0
  49. data/spec/{mock → orange/mock}/mock_router.rb +0 -0
  50. data/spec/{orange_spec.rb → orange/orange_spec.rb} +0 -0
  51. data/spec/{packet_spec.rb → orange/packet_spec.rb} +0 -0
  52. data/spec/{resource_spec.rb → orange/resource_spec.rb} +0 -0
  53. data/spec/orange/resources/admin_resource_spec.rb +16 -0
  54. data/spec/{resources → orange/resources}/mapper_spec.rb +0 -0
  55. data/spec/{resources → orange/resources}/model_resource_spec.rb +104 -0
  56. data/spec/{resources → orange/resources}/parser_spec.rb +0 -0
  57. data/spec/{resources → orange/resources}/routable_resource_spec.rb +0 -0
  58. data/spec/orange/resources/singleton_model_resource_spec.rb +4 -0
  59. data/spec/{resources/flex_router_spec.rb → orange/resources/sitemap_resource_spec.rb} +1 -1
  60. data/spec/orange/spec_helper.rb +51 -0
  61. data/spec/{stack_spec.rb → orange/stack_spec.rb} +0 -0
  62. metadata +45 -40
  63. data/lib/orange/resources/flex_router.rb +0 -13
  64. data/spec/carton_spec.rb +0 -5
  65. data/spec/middleware/access_control_spec.rb +0 -0
  66. data/spec/middleware/base_spec.rb +0 -0
  67. data/spec/middleware/database_spec.rb +0 -0
  68. data/spec/middleware/globals_spec.rb +0 -0
  69. data/spec/middleware/recapture_spec.rb +0 -0
  70. data/spec/middleware/rerouter_spec.rb +0 -0
  71. data/spec/middleware/restful_router_spec.rb +0 -0
  72. data/spec/middleware/route_context_spec.rb +0 -0
  73. data/spec/middleware/route_site_spec.rb +0 -0
  74. data/spec/middleware/show_exceptions_spec.rb +0 -0
  75. data/spec/middleware/site_load_spec.rb +0 -0
  76. data/spec/middleware/static_file_spec.rb +0 -0
  77. data/spec/middleware/static_spec.rb +0 -0
  78. data/spec/middleware/template_spec.rb +0 -0
  79. data/spec/mock/mock_carton.rb +0 -15
  80. data/spec/spec_helper.rb +0 -20
@@ -81,7 +81,7 @@ module Orange
81
81
  # The intent is for the application subclass to override this method
82
82
  # and use it to handle packets not routed by Stack middleware.
83
83
  def route(packet)
84
- raise 'default response from Orange::Application.route'
84
+ raise "default response from Orange::Application.route"
85
85
  end
86
86
 
87
87
  # Used to set optional values at class level. Will be merged into the options
@@ -7,7 +7,9 @@ module Orange
7
7
  #
8
8
  # All subclasses should start by declaring the "id" attribute. All models
9
9
  # are assumed to have an id attribute by most everything else, so it's
10
- # a good idea to have one.
10
+ # a good idea to have one. Also, we use this to tie in initialization before
11
+ # using the other dsl-ish methods (since these other methods are class level
12
+ # and would happen before initialize ever gets called)
11
13
  #
12
14
  # Orange::Carton adds many shortcut methods for adding various datatypes
13
15
  # to the model in a more declarative style (`id` vs `property :id, Serial`)
@@ -15,8 +17,14 @@ module Orange
15
17
  # For classes that don't need anything but scaffolding, there's the
16
18
  # as_resource method, which automatically creates a scaffolding resource
17
19
  # for the model.
20
+ #
21
+ # A model that doesn't need scaffolded at all could optionally forgo the carton
22
+ # class and just include DataMapper::Resource. All carton methods are to
23
+ # improve scaffolding capability.
18
24
  class Carton
19
- #
25
+ SCAFFOLD_OPTIONS = [:display_name] unless defined?(SCAFFOLD_OPTIONS)
26
+ # Declares a ModelResource subclass that scaffolds this carton
27
+ # The Subclass will have the name of the carton followed by "_Resource"
20
28
  def self.as_resource
21
29
  name = self.to_s
22
30
  eval <<-HEREDOC
@@ -26,22 +34,28 @@ module Orange
26
34
  HEREDOC
27
35
  end
28
36
 
29
- # Info for
37
+ # Include DataMapper types (required to be able to use Serial)
30
38
  include DataMapper::Types
31
39
 
40
+ # Do setup of object and declare an id
32
41
  def self.id
33
42
  include DataMapper::Resource
34
- self.property(:id, Serial)
43
+ property(:id, Serial)
35
44
  @scaffold_properties = []
36
45
  init
37
46
  end
38
47
 
48
+ def self.scaffold_properties
49
+ @scaffold_properties ||= []
50
+ end
51
+
52
+ # Stub init method
39
53
  def self.init
40
54
  end
41
55
 
42
56
  # Return properties that should be shown for a given context
43
- def self.form_props(context)
44
- @scaffold_properties.select{|p| p[:levels].include?(context) }
57
+ def self.form_props(context = :live)
58
+ scaffold_properties.select{|p| p[:levels].include?(context) }
45
59
  end
46
60
 
47
61
  # Helper to wrap properties into admin level
@@ -65,25 +79,40 @@ module Orange
65
79
  @levels = false
66
80
  end
67
81
 
82
+ def self.add_scaffold(name, type, dm_type, opts)
83
+ scaffold_properties << {:name => name, :type => type, :levels => @levels}.merge(opts) if @levels
84
+ opts = opts.delete_if{|k,v| SCAFFOLD_OPTIONS.include?(k)} # DataMapper doesn't like arbitrary opts
85
+ self.property(name, dm_type, opts)
86
+ end
87
+
68
88
  # Define a helper for title type database stuff
69
89
  # Show in a context if wrapped in one of the helpers
70
90
  def self.title(name, opts = {})
71
- @scaffold_properties << {:name => name, :type => :title, :levels => @levels}.merge(opts) if @levels
72
- self.property(name, String, opts)
91
+ add_scaffold(name, :title, String, opts)
73
92
  end
74
93
 
75
94
  # Define a helper for fulltext type database stuff
76
95
  # Show in a context if wrapped in one of the helpers
77
96
  def self.fulltext(name, opts = {})
78
- @scaffold_properties << {:name => name, :type => :fulltext, :levels => @levels, :opts => opts} if @levels
79
- self.property(name, Text, opts)
97
+ add_scaffold(name, :fulltext, Text, opts)
98
+ end
99
+
100
+ # Define a helper for boolean type database stuff
101
+ # Show in a context if wrapped in one of the helpers
102
+ def self.boolean(name, opts = {})
103
+ add_scaffold(name, :boolean, Boolean, opts)
80
104
  end
81
105
 
82
106
  # Define a helper for input type="text" type database stuff
83
107
  # Show in a context if wrapped in one of the helpers
84
108
  def self.text(name, opts = {})
85
- @scaffold_properties << {:name => name, :type => :text, :levels => @levels, :opts => opts} if @levels
86
- self.property(name, String, opts)
109
+ add_scaffold(name, :text, String, opts)
110
+ end
111
+
112
+ # Define a helper for type database stuff
113
+ # Show in a context if wrapped in one of the helpers
114
+ def self.expose(name, opts = {})
115
+ scaffold_properties << {:name => name, :type => :text, :levels => @levels, :opts => opts} if @levels
87
116
  end
88
117
 
89
118
  # Define a helper for input type="text" type database stuff
@@ -93,35 +122,34 @@ module Orange
93
122
  end
94
123
 
95
124
  # Override DataMapper to include context sensitivity (as set by helpers)
96
- def self.property(name, type, opts = {})
125
+ def self.scaffold_property(name, type, opts = {})
97
126
  my_type = type.to_s.downcase.to_sym
98
- @scaffold_properties << {:name => name, :type => my_type, :levels => @levels}.merge(opts) if @levels
99
- property(name, type, opts)
127
+ add_scaffold(name, my_type, type, opts)
100
128
  end
101
129
 
102
130
 
103
- # For more generic cases, use same syntax as DataMapper
104
- # This will make it an admin property though.
131
+ # For more generic cases, use same syntax as DataMapper does.
132
+ # The difference is that this will make it an admin property.
105
133
  def self.admin_property(name, type, opts = {})
106
134
  my_type = type.to_s.downcase.to_sym
107
- @scaffold_properties << {:name => name, :type => my_type, :levels => [:admin, :orange]}.merge(opts)
108
- property(name, type, opts)
135
+ opts[:levels] = [:admin, :orange]
136
+ add_scaffold(name, my_type, type, opts)
109
137
  end
110
138
 
111
- # For more generic cases, use same syntax as DataMapper
112
- # This will make it a front property though.
139
+ # For more generic cases, use same syntax as DataMapper does.
140
+ # The difference is that this will make it a front property.
113
141
  def self.front_property(name, type, opts = {})
114
142
  my_type = type.to_s.downcase.to_sym
115
- @scaffold_properties << {:name => name, :type => my_type, :levels => [:live, :admin, :orange]}.merge(opts)
116
- property(name, type, opts)
143
+ opts[:levels] = [:live, :admin, :orange]
144
+ add_scaffold(name, my_type, type, opts)
117
145
  end
118
146
 
119
- # For more generic cases, use same syntax as DataMapper
120
- # This will make it an orange property though.
147
+ # For more generic cases, use same syntax as DataMapper does.
148
+ # The difference is that this will make it an orange property.
121
149
  def self.orange_property(name, type, opts = {})
122
150
  my_type = type.to_s.downcase.to_sym
123
- @scaffold_properties << {:name => name, :type => my_type, :levels => [:orange]}.merge(opts)
124
- property(name, type, opts)
151
+ opts[:levels] = [:orange]
152
+ add_scaffold(name, my_type, type, opts)
125
153
  end
126
154
 
127
155
  end
@@ -1,6 +1,9 @@
1
- require 'dm-core'
1
+ require 'orange/carton'
2
2
 
3
3
  module Orange
4
+ # Defines a carton that belongs to a specific site.
5
+ # (Subclasses should be sure to call super if they override init, since
6
+ # it is what defines the relationship)
4
7
  class SiteCarton < Carton
5
8
  def self.init
6
9
  belongs_to :orange_site, 'Orange::Site'
@@ -57,6 +57,7 @@ module Orange
57
57
  load(Orange::Parser.new, :parser)
58
58
  load(Orange::Mapper.new, :mapper)
59
59
  load(Orange::PageParts.new, :page_parts)
60
+ load(Orange::AdminResource.new, :admin)
60
61
  afterLoad
61
62
  self
62
63
  end
@@ -165,9 +166,16 @@ module Orange
165
166
  # Accesses resources array, stored as a hash {:short_name => Resource instance,...}
166
167
  #
167
168
  # @param [Symbol] name the short name for the requested resource
169
+ # @param [optional, Boolean] ignore Whether to ignore any calls to resource if not found
170
+ # (false is default). This will allow method calls to non-existent resources. Should be
171
+ # used with caution.
168
172
  # @return [Orange::Resource] the resource for the given short name
169
- def [](name)
170
- @resources[name]
173
+ def [](name, ignore = false)
174
+ if ignore && !loaded?(name)
175
+ Ignore.new
176
+ else
177
+ @resources[name]
178
+ end
171
179
  end
172
180
 
173
181
  # Includes module in the Packet class
@@ -64,6 +64,17 @@ module Orange
64
64
  end
65
65
  end
66
66
 
67
+ # This class acts as a simple sink for ignoring messages, it will return itself
68
+ # for any message call. Orange::Core can optionally return this when trying
69
+ # to access resources so that you can make method calls to a resource that
70
+ # might not be really there. It will silently swallow any errors that might arrise,
71
+ # so this should be used with caution.
72
+ class Ignore
73
+ def method_missing(name, *args, &block)
74
+ return self
75
+ end
76
+ end
77
+
67
78
  # Simple class for evaluating options and allowing us to access them.
68
79
  class Options
69
80
 
@@ -3,13 +3,23 @@ require 'orange/middleware/base'
3
3
 
4
4
 
5
5
  module Orange::Middleware
6
-
6
+ # This middleware locks down entire contexts and puts them behind an openid
7
+ # login system. Currently only supports a single user id.
8
+ #
9
+ #
7
10
  class AccessControl < Base
8
- def init(*args)
11
+ # Sets up the options for the middleware
12
+ # @param [Hash] opts hash of options
13
+ # @option opts [Boolean] :openid Whether to use openid logins or not (currently only option)
14
+ # @option opts [Boolean] :handle_login Whether the access control system should handle
15
+ # presenting the login form, or let other parts of the app do that.
16
+ # @option opts [Boolean] :config_id Whether to use the id set in a config file
17
+
18
+ def init(opts = {})
9
19
  defs = {:locked => [:admin, :orange], :login => '/login',
10
20
  :handle_login => true, :openid => true, :config_id => true}
11
- opts = args.extract_with_defaults(defs)
12
- @openid = opts.has_key?(:openid) ? opts[:openid] : false
21
+ opts = opts.with_defaults!(defs)
22
+ @openid = opts[:openid]
13
23
  @locked = opts[:locked]
14
24
  @login = opts[:login]
15
25
  @handle = opts[:handle_login]
@@ -65,15 +75,14 @@ module Orange::Middleware
65
75
  packet['user.openid.response'] = resp
66
76
 
67
77
  after = packet.session.has_key?('user.after_login') ?
68
- packet.session['user.after_login'] : false
78
+ packet.session['user.after_login'] : '/'
69
79
  packet.session['user.after_login'] = false
70
80
 
71
81
  # Save id into session if we have one.
72
82
  packet.session['user.id'] = packet['user.id']
73
83
 
74
84
  # If the user was supposed to be going somewhere, redirect there
75
- packet.reroute(after) if after
76
- packet.reroute('/')
85
+ packet.reroute(after)
77
86
  false
78
87
  else
79
88
  packet.session['flash.error'] = resp.status
@@ -10,30 +10,69 @@ require 'orange/packet'
10
10
  # a basic call
11
11
  module Orange::Middleware
12
12
  class Base
13
+ # Initialize will set the core and downstream app, then call init
14
+ # subclasses should override init instead of initialize
15
+ # @param [Object] app a downstream app
16
+ # @param [Orange::Core] core the orange core
17
+ # @param [optional, Array] args any arguments
13
18
  def initialize(app, core, *args)
14
19
  @app = app
15
20
  @core = core
16
21
  init(*args)
17
22
  end
18
23
 
24
+ # A stub method that subclasses can override to handle initialization
25
+ # @return [void]
19
26
  def init(*args)
20
27
  end
21
28
 
29
+ # The standard Rack "call". By default, Orange Middleware wraps the env into
30
+ # an Orange::Packet and passes it on to #packet_call. Subclasses will typically
31
+ # override packet_call rather than overriding call directly.
32
+ #
33
+ # Orange Middleware
34
+ # should expect to have this method ignored by upstream Orange-aware apps in
35
+ # favor of calling packet_call directly.
36
+ # @param [Hash] env the hash of environment variables given by the rack interface.
37
+ # @return [Array] the standard Rack striplet of status, headers and content
22
38
  def call(env)
23
39
  packet = Orange::Packet.new(@core, env)
24
40
  packet_call(packet)
25
41
  end
26
42
 
43
+ # Like the standard call, but with the env hash already wrapped into a Packet
44
+ # This is called automatically as part of #call, so subclasses can have a packet
45
+ # without having to initialize it. It will be called directly by Orange-aware
46
+ # upstream middleware, skipping the step of initializing the packet during #call.
47
+ #
48
+ # Passing the packet downstream should be done with #pass rather than the Rack
49
+ # standard @app.call, since #pass will take the packet and do a #packet_call
50
+ # if possible.
51
+ # @param [Orange::Packet] packet the packet corresponding to this env
52
+ # @return [Array] the standard Rack striplet of status, headers and content
27
53
  def packet_call(packet)
28
54
  pass packet
29
55
  end
30
56
 
57
+ # Pass will sent the packet to the downstream app by calling call or packet call.
58
+ # Calling pass on a packet is the preferred way to call downstream apps, as it
59
+ # will call packet_call directly if possible (to avoid reinitializing the packet)
60
+ # @param [Orange::Packet] packet the packet to pass to downstream apps
61
+ # @return [Array] the standard Rack striplet of status, headers and content
31
62
  def pass(packet)
32
- @app.call(packet.env)
63
+ if @app.respond_to?(:packet_call)
64
+ @app.packet_call(packet)
65
+ else
66
+ @app.call(packet.env)
67
+ end
33
68
  end
34
69
 
70
+ # Accessor for @core, which is the stack's instance of Orange::Core
71
+ # @return [Orange::Core] the stack's instance of Orange::Core
35
72
  def orange; @core; end
36
73
 
74
+ # Help stack traces
75
+ # @return [String] string representing this middleware (#to_s)
37
76
  def inspect
38
77
  self.to_s
39
78
  end
@@ -0,0 +1,59 @@
1
+ require 'orange/middleware/base'
2
+
3
+ module Orange::Middleware
4
+ class FlexRouter < Base
5
+ def init(*args)
6
+ opts = args.extract_options!.with_defaults(:contexts => [:admin, :orange], :root_resource => :not_found)
7
+ @contexts = opts[:contexts]
8
+ @root_resource = opts[:root_resource]
9
+ end
10
+
11
+ # sets resource, resource_id, resource_action and resource_path
12
+ # /resource/id/action/[resource/path/if/any]
13
+ # /resource/action/[resource/path/if/any]
14
+ #
15
+ # In future - support for nested resources
16
+ def packet_call(packet)
17
+ return (pass packet) if packet['route.router'] # Don't route if other middleware
18
+ # already has
19
+ if(@contexts.include?(packet['route.context']))
20
+ path = packet['route.path'] || packet.request.path_info
21
+ parts = path.split('/')
22
+ pad = parts.shift
23
+ if !parts.empty?
24
+ resource = parts.shift
25
+ if orange.loaded?(resource.to_sym)
26
+ packet['route.resource'] = resource.to_sym
27
+ if !parts.empty?
28
+ second = parts.shift
29
+ if second =~ /^\d+$/
30
+ packet['route.resource_id'] = second
31
+ if !parts.empty?
32
+ packet['route.resource_action'] = parts.shift.to_sym
33
+ end
34
+ else
35
+ packet['route.resource_action'] = second.to_sym
36
+ end
37
+ end # end check for second part
38
+ else
39
+ parts.unshift(resource)
40
+ end # end check for loaded resource
41
+ end # end check for nonempty route
42
+
43
+ packet['route.resource'] ||= @root_resource
44
+ packet['route.resource_path'] = parts.unshift(pad).join('/')
45
+ packet['route.router'] = self
46
+ end # End context match if
47
+
48
+ pass packet
49
+ end
50
+
51
+ def route(packet)
52
+ resource = packet['route.resource']
53
+ raise 'resource not found' unless orange.loaded? resource
54
+ mode = packet['route.resource_action'] ||
55
+ (packet['route.resource_id'] ? :show : :list)
56
+ packet[:content] = orange[resource].view packet
57
+ end
58
+ end
59
+ end
@@ -1,7 +1,7 @@
1
1
  require 'orange/middleware/base'
2
2
 
3
3
  module Orange::Middleware
4
- # This middleware handles setting orange.env[:context]
4
+ # This middleware handles setting orange.env['route.context']
5
5
  # to a value based on the route, if any. The route is then
6
6
  # trimmed before continuing on.
7
7
  class RouteContext < Base
@@ -33,7 +33,7 @@ module Orange::Middleware
33
33
  packet['route.context'] = @default
34
34
  end
35
35
  end
36
- @app.call(packet.env)
36
+ pass packet
37
37
  end
38
38
  end
39
39
  end
@@ -45,7 +45,7 @@ module Orange::Middleware
45
45
  else
46
46
  packet['route.site_url'] = request.host
47
47
  end
48
- @app.call(packet.env)
48
+ pass packet
49
49
  end
50
50
  end
51
51
  end
@@ -11,7 +11,9 @@ module Orange::Middleware
11
11
  #
12
12
  # Be careful when you use this on public-facing sites as it could
13
13
  # reveal information helpful to attackers.
14
-
14
+ #
15
+ # Orange::Middleware::ShowExceptions is a slightly modified
16
+ # version of Rack::ShowExceptions
15
17
  class ShowExceptions < Base
16
18
  CONTEXT = 7
17
19
 
@@ -11,7 +11,16 @@ module Orange::Middleware
11
11
  def packet_call(packet)
12
12
  url = packet['route.site_url']
13
13
  site = Orange::Site.first(:url.like => url)
14
- packet['site'] = site if site
14
+ if site
15
+ packet['site'] = site
16
+ elsif
17
+ nil
18
+ else
19
+ s = Orange::Site.new({:url => packet['route.site_url'],
20
+ :name => 'An Orange Site'})
21
+ s.save
22
+ packet['site'] = s
23
+ end
15
24
  pass packet
16
25
  end
17
26
  end
@@ -29,5 +38,9 @@ module Orange
29
38
 
30
39
  class SiteResource < ModelResource
31
40
  use Orange::Site
41
+ def afterLoad
42
+ orange[:admin, true].add_link('Settings', :resource => @my_orange_name,
43
+ :text => 'Site')
44
+ end
32
45
  end
33
46
  end