resourcelogic 0.9.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -1,26 +1,50 @@
1
1
  = Resourcelogic
2
2
 
3
- *Beta warning: right now this plugin is in what I like to call a "semi-beta". The only thing left to do is write documentation and add in the tests. I have already thoroughly tested this in a separate application, and am in the process of moving over the tests without having to pull over the entire rails application.*
3
+ <b>Beta warning: right now this plugin is in what I like to call a "semi-beta". The only thing left to do is write documentation and add in the tests. I have already thoroughly tested this in a separate application, and am in the process of moving over the tests without having to pull over the entire rails application.</b>
4
4
 
5
- The purpose of Resourcelogic is to support a development style I created called "Contextual Development" (see contextual development below). This is an idea I've had for a while and finally decided to give it a try on one of my projects. This library spawned out of the {resource_controller plugin}(http://github.com/giraffesoft/resource_controller) by James Gollick, which is an excellent plugin. I eventually made so many changes to it that it made more sense to rewrite my own.
5
+ The purpose of Resourcelogic is to support a development style I created called <b>"Contextual Development"</b> (see contextual development below). This is an idea I've had for a while and finally decided to give it a try on one of my projects. It worked out great, and as a result, is probably the cleanest app I've built to date. So I decided to package this idea up and release it as Resourcelogic. This library spawned out of the {resource_controller plugin}[http://github.com/giraffesoft/resource_controller] by James Gollick, which is an excellent plugin. I eventually made so many changes to it that it made more sense to rewrite my own.
6
6
 
7
7
  == Helpful links
8
8
 
9
9
  * <b>Documentation:</b> http://resourcelogic.rubyforge.org
10
+ * <b>Bugs / feature suggestions:</b> http://binarylogic.lighthouseapp.com/projects/28581-resourcelogic
10
11
 
11
12
  == Contextual Development
12
13
 
13
- The idea behind contextual development is simple: *an API should be a be a byproduct of good design*. Meaning you should never have to explicitly build an API, it should be an "accident".
14
+ The idea behind contextual development is simple: <b>an API should be a be a byproduct of good design</b>. Meaning you should never have to explicitly build an API. It should, in essence, be an "accident".
14
15
 
15
- Your interface should not dictate the structure of your controllers. Instead, your controllers should represent a collection of resources that your interface can use. The problem is that rails does so much magic, and makes so many assumptions, that sometimes it distorts the basics behind the MVC pattern.
16
+ === 1. No need to namespace controllers when you have context
16
17
 
17
- This becomes very clear when you try build an interface with {Flex}[http://www.adobe.com/products/flex/]. What this forces you to do is separate your interface from your application. A flex interface communicates with your application via XML. So when you finish an app with a Flex interface you all of a sudden have a "production ready" API as well. Which feels really good. A proper API is the foundation for growing your application. Once this is done the sky is the limit.
18
+ Your interface should not dictate the structure of your controllers. Instead, your controllers should represent a collection of resources that your interface can use (AKA an API). The problem is that rails does so much magic, and makes so many assumptions, that sometimes it distorts the basics behind the MVC pattern.
18
19
 
19
- That being said, why can't we accomplish the same thing with an HTML interface? For one, an HTML interface isn't as cleanly decoupled from the controllers as a Flex interface. For two, rails doesn't give us all of the tools to properly accomplish this. Instead, people create work-arounds by namespacing controllers to represent context and scope. Instead, why not have a single controller that is *context aware*. Hence the name *"Contextual Development"*.
20
+ A good example is using {Flex}[http://www.adobe.com/products/flex/] to build your interface. This allows you to go back to the basics behind RESTful development by forcing you to separate your interface from your application. A flex interface communicates with your application via XML. As a result, when you complete your application you also have a "production ready" API. You basically completed two projects in one.
20
21
 
21
- This is great because now you have a single controller that *every* request for the resource must pass through. In the same manner that all database activity passes through ORM models. You can feel confident adding controller specific logic to that resource without having to worry about duplicating it across multiple controllers. Ex: storing the user's IP address with a record.
22
+ That being said, why can't we accomplish the same thing with an HTML interface? For one, an HTML interface isn't as cleanly decoupled from the controllers as a Flex interface. For two, rails doesn't give us all of the tools to properly accomplish this. Instead, people create work-arounds by namespacing controllers to represent context and/or scope (Ex: Admin::CommentsController). Why not have a single controller that is <b>context aware</b>? Hence the name <b>"Contextual Development"</b>. So instead of an Admin namespace, you can use the context to modify the interface / scope. Example:
22
23
 
23
- If it is any consolation, I am speaking from experience. I recently finished a very complex project using this method and I can say, without a doubt, it's the cleanest application I've built to date.
24
+ class CommentsController < ApplicationController
25
+ layout Proc.new { context == :admin ? "admin" : "application" }
26
+
27
+ private
28
+ # context is irrelevant when determining scope
29
+ def scope
30
+ current_user.admin? ? Comment : current_user.comments
31
+ end
32
+ end
33
+
34
+ === 2. Single point of access for resources
35
+
36
+ Having a <b>single</b> controller is great, because it represents a <b>single</b> resource. This means that <b>every</b> request for that resource must pass through that single controller. Think about it, what is an API essentially? Controlled access to your database. You don't go and create an entirely new model for every scope/context you use the model, do you? Why do the same for your API? Your API resources are the "models" for anyone using it, so it doesn't make sense to have /comments and /admin/comments.
37
+
38
+ To bring it all home, all requests for a resource should pass through a single controller, in the same manner that all database activity for a table must pass through a single model. You can feel confident adding controller specific logic to that resource without having to worry about duplicating it across multiple controllers. A very simple example: storing the user's IP address with a record.
39
+
40
+ === 3. Relative URL and path methods
41
+
42
+ Another big problem with using the same controller in various contexts is the need to specify relative paths instead of absolute paths. When you specify resources in your routes you get all kinds of useful <b>absolute</b> path methods, but where are the relative ones? Let's say you have the following paths:
43
+
44
+ 1. /articles
45
+ 2. /admin/articles
46
+
47
+ Articles have many comments. What if you want to link to that article's comments? Do you use <b>article_comments_path</b> or <b>admin_article_comments_path</b>? You could use the <b>context</b> method described above to determine this, but this will get messy and annoying very fast. Instead why not use <b>child_collection_path(:comments)</b>? The paths are relative based on a tree structure. So you also get <b>parent</b> and <b>sibling</b> paths. Now you can easily nest resources without having to worry about linking to them. See the documentation for more details on the various URL and path methods that are available to you.
24
48
 
25
49
  == Install and use
26
50
 
@@ -49,7 +73,7 @@ Your ResourceController should look something like:
49
73
  acts_as_resource
50
74
  end
51
75
 
52
- Now all of your controllers that are "resources" can extend this controller. Why do this? So you can set your default behavior for resources in one spot. This idea what brought over from the resource_controller plugin. The syntax resource_controller came up with is pretty cool:
76
+ Now all of your controllers that are "resources" can extend this controller. Why do this? So you can set your default behavior for resources in one spot. This idea what brought over from the resource_controller plugin. The DSL resource_controller came up with is pretty cool:
53
77
 
54
78
  class ResourceController < ApplicationController
55
79
  acts_as_resource
@@ -76,20 +100,10 @@ Instead of namespacing your controllers, give them context. For example, let's s
76
100
  Then in your controller use the context method to make adjustments:
77
101
 
78
102
  class CommentsController < ResourceController
79
- layout :layout_by_context
80
-
81
- private
82
- def layout_by_context
83
- case context
84
- when :admin
85
- "admin"
86
- else
87
- "application"
88
- end
89
- end
103
+ layout Proc.new { context == :admin ? "admin" : "application" }
90
104
  end
91
105
 
92
- You also have the same context method in your views. Lastly, if you feel your views are getting cluttered you can use the contextual_views option (See Resourcelogic::Context::Config for more info). This will change your default view path to a context subfolder. Ex:
106
+ You also have the same context method in your views. Lastly, if you feel your views are getting cluttered by trying to determine what context you are in, you can use the namespace_views_by_context option (See Resourcelogic::Context::Config for more info). This will change your default view path to a context subfolder. Ex:
93
107
 
94
108
  /comments
95
109
  /admin
@@ -100,36 +114,54 @@ See the Feature Highlights section below for more options, and the documentation
100
114
 
101
115
  == Feature highlights
102
116
 
103
- I don't want to repeat what is already in the documentation, but there are a lot of really nice configuration and utility methods. Here are just a few:
117
+ I don't want to repeat what is already in the documentation, but there are a lot of really nice configuration and utility methods. <b>Here are just a few</b>:
104
118
 
105
- *Class level methods*
119
+ <b>Class level methods</b>
106
120
 
107
121
  belongs_to :relationship_name # will check to see if the resource is being scoped by a parent and give you some nifty methods for this (see below). You can call this multiple times. Just like ActiveRecord.
108
- contextual_views true # will split up your views into subfolders: comments/context1, comments/context2, and will change your default view path to the respecive folder
122
+ contextual_views true # will split up your views into subfolders: comments/context1, comments/context2, and will change your default view path to the respective folder
123
+
124
+ <b>Instance level methods</b>
109
125
 
110
- *Instance level methods*
126
+ Lets pretend we are dealing with a products resource that belongs to a category.
111
127
 
112
128
  context # the name of the context you are in
113
129
 
114
- object # current object
115
- collection # current collection
116
- object_path # /comments/:id
117
- new_object_path # /comments/new
118
- collection_path # /comments
130
+ object # current product object, if any
131
+ collection # current collection of products
132
+ parent # current category object, if any
133
+
134
+ Now you have all of the "relative routes" to help you easily nest resources and use them in different contexts:
135
+
136
+ object_path # /products/:id
137
+ edit_object_path # /products/:id/edit
138
+ new_object_path # /products/new
139
+ collection_path # /products
119
140
 
120
- parent # current parent object
121
- parent_path # /parent_name/:parent_id
122
- parent_collection_path # /parent_name
123
- new_parent_path # /parent_name/new
141
+ parent_path # /categories/:parent_id
142
+ edit_parent_path # /categories/:parent_id/edit
143
+ parent_collection_path # /categories
144
+ new_parent_path # /categories/new
124
145
 
125
146
  sibling_path(sibling) # /sibling_name/:id
147
+ edit_sibling_path(sibling) # /sibling_name/:id/edit
126
148
  new_sibling_path(:sibling_name) # /sibling_name/new
127
149
  sibling_collection_path(:sibling_name) # /sibling_name
128
150
 
129
- child_path(child) # /sibling_name/:id
130
- new_child_path(:child_name) # /sibling_name/new
131
- child_collection_path(:child_name) # /sibling_name
132
-
133
- All of the above can end with _url instead of _path. See docs for a complete list of available methods.
151
+ child_path(child) # /products/:product_id/child_name/:id
152
+ edit_child_path(child) # /products/:product_id/child_name/:id/edit
153
+ new_child_path(:child_name) # /products/:product_id/child_name/new
154
+ child_collection_path(:child_name) # /products/:product_id/child_name
155
+
156
+ Notice you have the edit_* paths. The above paths are implemented in a very flexible manner. So you are not limited them, you can call your custom actions too:
157
+
158
+ map.resources :products, :member => [:approve], :collection => [:approve_all]
159
+ approve_object_path # /products/:id/approve
160
+ approve_all_collection_path # /products/approve
161
+
162
+ The above example is probably not the best one, but you get the point.
163
+
164
+ Lastly, all of the above can end with _url instead of _path. <b>See docs for a complete list of available methods.</b>
165
+
134
166
 
135
- Copyright (c) 2008 {Ben Johnson of Binary Logic}(http://www.binarylogic.com), released under the MIT license
167
+ Copyright (c) 2008 {Ben Johnson of Binary Logic}[http://www.binarylogic.com], released under the MIT license
data/Rakefile CHANGED
@@ -7,14 +7,14 @@ require File.dirname(__FILE__) << "/lib/resourcelogic/version"
7
7
  Hoe.new("Resourcelogic", Resourcelogic::Version::STRING) do |p|
8
8
  p.name = "resourcelogic"
9
9
  p.author = "Ben Johnson of Binary Logic"
10
- p.email = 'bjohnson@binarylogic.com'
11
- p.summary = "Making an API a byproduct of good design."
12
- p.description = "Making an API a byproduct of good design."
10
+ p.email = "bjohnson@binarylogic.com"
11
+ p.summary = "Removes the need to namespace controllers by adding context and relative url functions among other things."
12
+ p.description = "Removes the need to namespace controllers by adding context and relative url functions among other things."
13
13
  p.url = "http://github.com/binarylogic/resourcelogic"
14
14
  p.history_file = "CHANGELOG.rdoc"
15
15
  p.readme_file = "README.rdoc"
16
16
  p.extra_rdoc_files = ["CHANGELOG.rdoc", "README.rdoc"]
17
- p.remote_rdoc_dir = ''
17
+ p.remote_rdoc_dir = ""
18
18
  p.test_globs = ["test/*/test_*.rb", "test/*_test.rb", "test/*/*_test.rb"]
19
19
  p.extra_deps = %w(activesupport)
20
20
  end
data/lib/resourcelogic.rb CHANGED
@@ -2,13 +2,16 @@ require File.dirname(__FILE__) + "/resourcelogic/version"
2
2
  require File.dirname(__FILE__) + "/resourcelogic/accessors"
3
3
  require File.dirname(__FILE__) + "/resourcelogic/action_options"
4
4
  require File.dirname(__FILE__) + "/resourcelogic/actions"
5
+ require File.dirname(__FILE__) + "/resourcelogic/aliases"
5
6
  require File.dirname(__FILE__) + "/resourcelogic/child"
6
7
  require File.dirname(__FILE__) + "/resourcelogic/context"
7
8
  require File.dirname(__FILE__) + "/resourcelogic/failable_action_options"
8
9
  require File.dirname(__FILE__) + "/resourcelogic/parent"
9
10
  require File.dirname(__FILE__) + "/resourcelogic/response_collector"
11
+ require File.dirname(__FILE__) + "/resourcelogic/scope"
10
12
  require File.dirname(__FILE__) + "/resourcelogic/self"
11
13
  require File.dirname(__FILE__) + "/resourcelogic/sibling"
12
14
  require File.dirname(__FILE__) + "/resourcelogic/singleton"
15
+ require File.dirname(__FILE__) + "/resourcelogic/sub_views"
13
16
  require File.dirname(__FILE__) + "/resourcelogic/urligence"
14
17
  require File.dirname(__FILE__) + "/resourcelogic/base"
@@ -33,7 +33,7 @@ module Resourcelogic # :nodoc:
33
33
  write_inheritable_attribute accessor_name, start_value
34
34
 
35
35
  class_eval <<-"end_eval", __FILE__, __LINE__
36
- def self.#{accessor_name}(&block)
36
+ def self.#{accessor_name}(context = :root, &block)
37
37
  read_inheritable_attribute(:#{accessor_name}).instance_eval(&block) if block_given?
38
38
  read_inheritable_attribute(:#{accessor_name})
39
39
  end
@@ -1,48 +1,26 @@
1
1
  module Resourcelogic
2
2
  module Actions
3
3
  ACTIONS = [:index, :show, :new_action, :create, :edit, :update, :destroy].freeze
4
- FAILABLE_ACTIONS = ACTIONS - [:index, :new_action, :edit].freeze
4
+ FAILABLE_ACTIONS = ACTIONS - [:index, :new_action, :edit, :destroy].freeze
5
5
 
6
6
  def self.included(klass)
7
7
  klass.class_eval do
8
- extend Config
9
8
  ACTIONS.each do |action|
10
9
  class_scoping_reader action, FAILABLE_ACTIONS.include?(action) ? FailableActionOptions.new : ActionOptions.new
11
10
  end
11
+ class_scoping_reader :context, ContextOptions.new
12
12
  add_acts_as_resource_module(Methods)
13
13
  end
14
14
  end
15
15
 
16
- module Config
17
- def actions(*opts)
18
- config = {}
19
- config.merge!(opts.pop) if opts.last.is_a?(Hash)
20
-
21
- all_actions = (false && singleton? ? Resourcelogic::SINGLETON_ACTIONS : Resourcelogic::Actions::ACTIONS) - [:new_action] + [:new]
22
-
23
- actions_to_remove = []
24
- if opts.first == :none
25
- actions_to_remove = all_actions
26
- else
27
- actions_to_remove += all_actions - opts unless opts.first == :all
28
- actions_to_remove += [*config[:except]] if config[:except]
29
- actions_to_remove.uniq!
30
- end
31
-
32
- actions_to_remove.each { |action| undef_method(action) if method_defined?(action) }
33
- end
34
- end
35
-
36
16
  module Methods
37
17
  def new
38
- build_object
39
18
  load_object
40
19
  before :new_action
41
20
  response_for :new_action
42
21
  end
43
-
22
+
44
23
  def create
45
- build_object
46
24
  load_object
47
25
  before :create
48
26
  if object.save
@@ -55,16 +33,16 @@ module Resourcelogic
55
33
  response_for :create_fails
56
34
  end
57
35
  end
58
-
36
+
59
37
  def edit
60
38
  load_object
61
39
  before :edit
62
40
  response_for :edit
63
41
  end
64
-
42
+
65
43
  def update
66
44
  load_object
67
- object.attributes = object_params
45
+ object.attributes = attributes
68
46
  before :update
69
47
  if object.save
70
48
  after :update
@@ -76,7 +54,7 @@ module Resourcelogic
76
54
  response_for :update_fails
77
55
  end
78
56
  end
79
-
57
+
80
58
  def destroy
81
59
  load_object
82
60
  before :destroy
@@ -84,12 +62,8 @@ module Resourcelogic
84
62
  after :destroy
85
63
  set_flash :destroy
86
64
  response_for :destroy
87
- rescue DestroyNotAllowed
88
- after :destroy_fails
89
- set_flash :destroy_fails
90
- response_for :destroy_fails
91
65
  end
92
-
66
+
93
67
  def show
94
68
  load_object
95
69
  before :show
@@ -97,7 +71,7 @@ module Resourcelogic
97
71
  rescue ActiveRecord::RecordNotFound
98
72
  response_for :show_fails
99
73
  end
100
-
74
+
101
75
  def index
102
76
  load_collection
103
77
  before :index
@@ -121,26 +95,26 @@ module Resourcelogic
121
95
  rescue ActionController::DoubleRenderError
122
96
  end
123
97
  end
124
-
98
+
125
99
  # Calls the after callbacks for the action, if one is present.
126
100
  #
127
101
  def after(action)
128
102
  invoke_callbacks *options_for(action).after
129
103
  end
130
-
104
+
131
105
  # Calls the before block for the action, if one is present.
132
106
  #
133
107
  def before(action)
134
108
  invoke_callbacks *self.class.send(action).before
135
109
  end
136
-
110
+
137
111
  # Sets the flash and flash_now for the action, if it is present.
138
112
  #
139
113
  def set_flash(action)
140
114
  set_normal_flash(action)
141
115
  set_flash_now(action)
142
116
  end
143
-
117
+
144
118
  # Sets the regular flash (i.e. flash[:notice] = '...')
145
119
  #
146
120
  def set_normal_flash(action)
@@ -148,7 +122,7 @@ module Resourcelogic
148
122
  flash[:notice] = f.is_a?(Proc) ? instance_eval(&f) : options_for(action).flash
149
123
  end
150
124
  end
151
-
125
+
152
126
  # Sets the flash.now (i.e. flash.now[:notice] = '...')
153
127
  #
154
128
  def set_flash_now(action)
@@ -156,7 +130,7 @@ module Resourcelogic
156
130
  flash.now[:notice] = f.is_a?(Proc) ? instance_eval(&f) : options_for(action).flash_now
157
131
  end
158
132
  end
159
-
133
+
160
134
  # Returns the options for an action, which is a symbol.
161
135
  #
162
136
  # Manages splitting things like :create_fails.
@@ -165,14 +139,14 @@ module Resourcelogic
165
139
  action = action == :new_action ? [action] : "#{action}".split('_').map(&:to_sym)
166
140
  options = self.class.send(action.first)
167
141
  options = options.send(action.last == :fails ? :fails : :success) if Resourcelogic::Actions::FAILABLE_ACTIONS.include? action.first
168
-
142
+
169
143
  options
170
144
  end
171
-
145
+
172
146
  def invoke_callbacks(*callbacks)
173
147
  unless callbacks.empty?
174
148
  callbacks.select { |callback| callback.is_a? Symbol }.each { |symbol| send(symbol) }
175
-
149
+
176
150
  block = callbacks.detect { |callback| callback.is_a? Proc }
177
151
  instance_eval &block unless block.nil?
178
152
  end
@@ -182,20 +156,13 @@ module Resourcelogic
182
156
  instance_variable_set "@#{parent_model_name}", parent_object if parent?
183
157
  end
184
158
 
185
- def build_object
186
- return @object if @object
187
- @object = end_of_association_chain.send parent? ? :build : :new
188
- @object.attributes = object_params
189
- @object
190
- end
191
-
192
159
  # Used internally to load the member object in to an instance variable @#{model_name} (i.e. @post)
193
160
  #
194
161
  def load_object
195
162
  load_parent
196
163
  instance_variable_set "@#{object_name}", object
197
164
  end
198
-
165
+
199
166
  # Used internally to load the collection in to an instance variable @#{model_name.pluralize} (i.e. @posts)
200
167
  #
201
168
  def load_collection
@@ -3,11 +3,13 @@ module Resourcelogic # :nodoc:
3
3
  def self.included(klass)
4
4
  klass.class_eval do
5
5
  extend ClassMethods
6
+ include InstanceMethods
6
7
  end
7
8
  end
8
9
 
9
10
  module ClassMethods
10
11
  def acts_as_resource(&block)
12
+ resourceful(true)
11
13
  yield self if block_given?
12
14
  acts_as_resource_modules.each { |mod| include mod }
13
15
  init_default_actions
@@ -65,13 +67,21 @@ module Resourcelogic # :nodoc:
65
67
  acts_as_resource_modules
66
68
  end
67
69
 
70
+ def resourceful(value = nil)
71
+ rw_config(:resourceful, value, false)
72
+ end
73
+
74
+ def resourceful?
75
+ resourceful == true
76
+ end
77
+
68
78
  private
69
79
  def acts_as_resource_modules
70
80
  key = :acts_as_resource_modules
71
81
  inheritable_attributes.include?(key) ? read_inheritable_attribute(key) : []
72
82
  end
73
83
 
74
- def config(key, value, default_value = nil, read_value = nil)
84
+ def rw_config(key, value, default_value = nil, read_value = nil)
75
85
  if value == read_value
76
86
  inheritable_attributes.include?(key) ? read_inheritable_attribute(key) : default_value
77
87
  else
@@ -79,6 +89,21 @@ module Resourcelogic # :nodoc:
79
89
  end
80
90
  end
81
91
  end
92
+
93
+ module InstanceMethods
94
+ def self.included(klass)
95
+ klass.helper_method :resourceful?, :section
96
+ end
97
+
98
+ def resourceful?
99
+ self.class.resourceful?
100
+ end
101
+
102
+ def section
103
+ section = request.path.split("/")[1]
104
+ section && section.to_sym
105
+ end
106
+ end
82
107
  end
83
108
  end
84
109
 
@@ -90,11 +115,16 @@ if defined?(::ActionController)
90
115
  include Resourcelogic::Actions
91
116
  include Resourcelogic::Child
92
117
  include Resourcelogic::Context
93
- include Resourcelogic::Parent
118
+ include Resourcelogic::Scope
94
119
  include Resourcelogic::Self
95
120
  include Resourcelogic::Sibling
96
- include Resourcelogic::Singleton
121
+ include Resourcelogic::SubViews
97
122
  include Resourcelogic::Urligence
123
+
124
+ # Need to be loaded last to override methods
125
+ include Resourcelogic::Parent
126
+ include Resourcelogic::Aliases
127
+ include Resourcelogic::Singleton
98
128
  end
99
129
  end
100
130
  end