resourcelogic 0.0.12 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
File without changes
@@ -0,0 +1,21 @@
1
+ CHANGELOG.rdoc
2
+ MIT-LICENSE
3
+ Manifest.txt
4
+ README.rdoc
5
+ Rakefile
6
+ init.rb
7
+ lib/resourcelogic.rb
8
+ lib/resourcelogic/accessors.rb
9
+ lib/resourcelogic/action_options.rb
10
+ lib/resourcelogic/actions.rb
11
+ lib/resourcelogic/base.rb
12
+ lib/resourcelogic/child.rb
13
+ lib/resourcelogic/context.rb
14
+ lib/resourcelogic/failable_action_options.rb
15
+ lib/resourcelogic/parent.rb
16
+ lib/resourcelogic/response_collector.rb
17
+ lib/resourcelogic/self.rb
18
+ lib/resourcelogic/sibling.rb
19
+ lib/resourcelogic/singleton.rb
20
+ lib/resourcelogic/urligence.rb
21
+ lib/resourcelogic/version.rb
@@ -1,50 +1,26 @@
1
1
  = Resourcelogic
2
2
 
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>
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.*
4
4
 
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.
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.
6
6
 
7
7
  == Helpful links
8
8
 
9
- * <b>Documentation:</b> http://rdoc.info/projects/binarylogic/resourcelogic
10
- * <b>Bugs / feature suggestions:</b> http://binarylogic.lighthouseapp.com/projects/28581-resourcelogic
9
+ * <b>Documentation:</b> http://resourcelogic.rubyforge.org
11
10
 
12
11
  == Contextual Development
13
12
 
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".
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".
15
14
 
16
- === 1. No need to namespace controllers when you have context
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.
17
16
 
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.
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.
19
18
 
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.
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"*.
21
20
 
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:
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.
23
22
 
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.
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.
48
24
 
49
25
  == Install and use
50
26
 
@@ -73,7 +49,7 @@ Your ResourceController should look something like:
73
49
  acts_as_resource
74
50
  end
75
51
 
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:
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:
77
53
 
78
54
  class ResourceController < ApplicationController
79
55
  acts_as_resource
@@ -100,10 +76,20 @@ Instead of namespacing your controllers, give them context. For example, let's s
100
76
  Then in your controller use the context method to make adjustments:
101
77
 
102
78
  class CommentsController < ResourceController
103
- layout Proc.new { context == :admin ? "admin" : "application" }
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
104
90
  end
105
91
 
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:
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:
107
93
 
108
94
  /comments
109
95
  /admin
@@ -114,54 +100,36 @@ See the Feature Highlights section below for more options, and the documentation
114
100
 
115
101
  == Feature highlights
116
102
 
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>:
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:
118
104
 
119
- <b>Class level methods</b>
105
+ *Class level methods*
120
106
 
121
107
  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.
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>
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
125
109
 
126
- Lets pretend we are dealing with a products resource that belongs to a category.
110
+ *Instance level methods*
127
111
 
128
112
  context # the name of the context you are in
129
113
 
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
114
+ object # current object
115
+ collection # current collection
116
+ object_path # /comments/:id
117
+ new_object_path # /comments/new
118
+ collection_path # /comments
140
119
 
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
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
145
124
 
146
125
  sibling_path(sibling) # /sibling_name/:id
147
- edit_sibling_path(sibling) # /sibling_name/:id/edit
148
126
  new_sibling_path(:sibling_name) # /sibling_name/new
149
127
  sibling_collection_path(:sibling_name) # /sibling_name
150
128
 
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
-
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.
166
134
 
167
- Copyright (c) 2008 {Ben Johnson of Binary Logic}[http://www.binarylogic.com], released under the MIT license
135
+ Copyright (c) 2008 {Ben Johnson of Binary Logic}(http://www.binarylogic.com), released under the MIT license
data/Rakefile CHANGED
@@ -1,49 +1,20 @@
1
- require 'rubygems'
2
- require 'rake'
1
+ ENV['RDOCOPT'] = "-S -f html -T hanna"
3
2
 
4
- begin
5
- require 'jeweler'
6
- Jeweler::Tasks.new do |gem|
7
- gem.name = "resourcelogic"
8
- gem.summary = "Removes the need to namespace controllers by adding context and relative url functions among other things."
9
- gem.email = "bjohnson@binarylogic.com"
10
- gem.homepage = "http://github.com/binarylogic/resourcelogic"
11
- gem.authors = ["Ben Johnson of Binary Logic"]
12
- gem.rubyforge_project = "resourcelogic"
13
- gem.add_dependency "activesupport"
14
- end
15
- rescue LoadError
16
- puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
17
- end
3
+ require "rubygems"
4
+ require "hoe"
5
+ require File.dirname(__FILE__) << "/lib/resourcelogic/version"
18
6
 
19
- require 'rake/testtask'
20
- Rake::TestTask.new(:test) do |test|
21
- test.libs << 'lib' << 'test'
22
- test.pattern = 'test/**/*_test.rb'
23
- test.verbose = true
24
- end
25
-
26
- begin
27
- require 'rcov/rcovtask'
28
- Rcov::RcovTask.new do |test|
29
- test.libs << 'test'
30
- test.pattern = 'test/**/*_test.rb'
31
- test.verbose = true
32
- end
33
- rescue LoadError
34
- task :rcov do
35
- abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
36
- end
37
- end
38
-
39
- task :default => :test
40
-
41
- begin
42
- require 'rake/contrib/sshpublisher'
43
- namespace :rubyforge do
44
- desc "Release gem to RubyForge"
45
- task :release => ["rubyforge:release:gem"]
46
- end
47
- rescue LoadError
48
- puts "Rake SshDirPublisher is unavailable or your rubyforge environment is not configured."
49
- end
7
+ Hoe.new("Resourcelogic", Resourcelogic::Version::STRING) do |p|
8
+ p.name = "resourcelogic"
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."
13
+ p.url = "http://github.com/binarylogic/resourcelogic"
14
+ p.history_file = "CHANGELOG.rdoc"
15
+ p.readme_file = "README.rdoc"
16
+ p.extra_rdoc_files = ["CHANGELOG.rdoc", "README.rdoc"]
17
+ p.remote_rdoc_dir = ''
18
+ p.test_globs = ["test/*/test_*.rb", "test/*_test.rb", "test/*/*_test.rb"]
19
+ p.extra_deps = %w(activesupport)
20
+ end
@@ -1,16 +1,14 @@
1
+ require File.dirname(__FILE__) + "/resourcelogic/version"
1
2
  require File.dirname(__FILE__) + "/resourcelogic/accessors"
2
3
  require File.dirname(__FILE__) + "/resourcelogic/action_options"
3
4
  require File.dirname(__FILE__) + "/resourcelogic/actions"
4
- require File.dirname(__FILE__) + "/resourcelogic/aliases"
5
5
  require File.dirname(__FILE__) + "/resourcelogic/child"
6
6
  require File.dirname(__FILE__) + "/resourcelogic/context"
7
7
  require File.dirname(__FILE__) + "/resourcelogic/failable_action_options"
8
8
  require File.dirname(__FILE__) + "/resourcelogic/parent"
9
9
  require File.dirname(__FILE__) + "/resourcelogic/response_collector"
10
- require File.dirname(__FILE__) + "/resourcelogic/scope"
11
10
  require File.dirname(__FILE__) + "/resourcelogic/self"
12
11
  require File.dirname(__FILE__) + "/resourcelogic/sibling"
13
12
  require File.dirname(__FILE__) + "/resourcelogic/singleton"
14
- require File.dirname(__FILE__) + "/resourcelogic/sub_views"
15
13
  require File.dirname(__FILE__) + "/resourcelogic/urligence"
16
14
  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}(context = :root, &block)
36
+ def self.#{accessor_name}(&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,26 +1,48 @@
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, :destroy].freeze
4
+ FAILABLE_ACTIONS = ACTIONS - [:index, :new_action, :edit].freeze
5
5
 
6
6
  def self.included(klass)
7
7
  klass.class_eval do
8
+ extend Config
8
9
  ACTIONS.each do |action|
9
10
  class_scoping_reader action, FAILABLE_ACTIONS.include?(action) ? FailableActionOptions.new : ActionOptions.new
10
11
  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
+
16
36
  module Methods
17
37
  def new
38
+ build_object
18
39
  load_object
19
40
  before :new_action
20
41
  response_for :new_action
21
42
  end
22
-
43
+
23
44
  def create
45
+ build_object
24
46
  load_object
25
47
  before :create
26
48
  if object.save
@@ -33,16 +55,16 @@ module Resourcelogic
33
55
  response_for :create_fails
34
56
  end
35
57
  end
36
-
58
+
37
59
  def edit
38
60
  load_object
39
61
  before :edit
40
62
  response_for :edit
41
63
  end
42
-
64
+
43
65
  def update
44
66
  load_object
45
- object.attributes = attributes
67
+ object.attributes = object_params
46
68
  before :update
47
69
  if object.save
48
70
  after :update
@@ -54,7 +76,7 @@ module Resourcelogic
54
76
  response_for :update_fails
55
77
  end
56
78
  end
57
-
79
+
58
80
  def destroy
59
81
  load_object
60
82
  before :destroy
@@ -62,8 +84,12 @@ module Resourcelogic
62
84
  after :destroy
63
85
  set_flash :destroy
64
86
  response_for :destroy
87
+ rescue DestroyNotAllowed
88
+ after :destroy_fails
89
+ set_flash :destroy_fails
90
+ response_for :destroy_fails
65
91
  end
66
-
92
+
67
93
  def show
68
94
  load_object
69
95
  before :show
@@ -71,7 +97,7 @@ module Resourcelogic
71
97
  rescue ActiveRecord::RecordNotFound
72
98
  response_for :show_fails
73
99
  end
74
-
100
+
75
101
  def index
76
102
  load_collection
77
103
  before :index
@@ -95,26 +121,26 @@ module Resourcelogic
95
121
  rescue ActionController::DoubleRenderError
96
122
  end
97
123
  end
98
-
124
+
99
125
  # Calls the after callbacks for the action, if one is present.
100
126
  #
101
127
  def after(action)
102
128
  invoke_callbacks *options_for(action).after
103
129
  end
104
-
130
+
105
131
  # Calls the before block for the action, if one is present.
106
132
  #
107
133
  def before(action)
108
134
  invoke_callbacks *self.class.send(action).before
109
135
  end
110
-
136
+
111
137
  # Sets the flash and flash_now for the action, if it is present.
112
138
  #
113
139
  def set_flash(action)
114
140
  set_normal_flash(action)
115
141
  set_flash_now(action)
116
142
  end
117
-
143
+
118
144
  # Sets the regular flash (i.e. flash[:notice] = '...')
119
145
  #
120
146
  def set_normal_flash(action)
@@ -122,7 +148,7 @@ module Resourcelogic
122
148
  flash[:notice] = f.is_a?(Proc) ? instance_eval(&f) : options_for(action).flash
123
149
  end
124
150
  end
125
-
151
+
126
152
  # Sets the flash.now (i.e. flash.now[:notice] = '...')
127
153
  #
128
154
  def set_flash_now(action)
@@ -130,7 +156,7 @@ module Resourcelogic
130
156
  flash.now[:notice] = f.is_a?(Proc) ? instance_eval(&f) : options_for(action).flash_now
131
157
  end
132
158
  end
133
-
159
+
134
160
  # Returns the options for an action, which is a symbol.
135
161
  #
136
162
  # Manages splitting things like :create_fails.
@@ -139,14 +165,14 @@ module Resourcelogic
139
165
  action = action == :new_action ? [action] : "#{action}".split('_').map(&:to_sym)
140
166
  options = self.class.send(action.first)
141
167
  options = options.send(action.last == :fails ? :fails : :success) if Resourcelogic::Actions::FAILABLE_ACTIONS.include? action.first
142
-
168
+
143
169
  options
144
170
  end
145
-
171
+
146
172
  def invoke_callbacks(*callbacks)
147
173
  unless callbacks.empty?
148
174
  callbacks.select { |callback| callback.is_a? Symbol }.each { |symbol| send(symbol) }
149
-
175
+
150
176
  block = callbacks.detect { |callback| callback.is_a? Proc }
151
177
  instance_eval &block unless block.nil?
152
178
  end
@@ -156,13 +182,20 @@ module Resourcelogic
156
182
  instance_variable_set "@#{parent_model_name}", parent_object if parent?
157
183
  end
158
184
 
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
+
159
192
  # Used internally to load the member object in to an instance variable @#{model_name} (i.e. @post)
160
193
  #
161
194
  def load_object
162
195
  load_parent
163
196
  instance_variable_set "@#{object_name}", object
164
197
  end
165
-
198
+
166
199
  # Used internally to load the collection in to an instance variable @#{model_name.pluralize} (i.e. @posts)
167
200
  #
168
201
  def load_collection