map_restfully 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gem 'rails', '~> 3.0.10'
4
+ gem 'rack-test'
5
+
6
+ group :development do
7
+ gem 'bundler', '~> 1.0.0'
8
+ gem 'jeweler', '~> 1.6.4'
9
+ end
data/README.rdoc ADDED
@@ -0,0 +1,135 @@
1
+ = map_restfully
2
+
3
+ The map_restfully plugin creates a more RESTful controller than the default in Rails. This facilitates
4
+ applications that are much closer to the architectural style REST, which leads to performance and convention
5
+ advantages.
6
+
7
+ == Example
8
+
9
+ In your routes.rb file, simply add:
10
+
11
+ map_restfully :chair
12
+
13
+ where 'chair' is the name of your resource.
14
+
15
+ This will automatically provide routes and named path helpers for GETS, POSTS, PUTS, DELETES, GET, POST, PUT, DELETE.
16
+ The grammatical number corresponds to the url generated and the path helpers.
17
+
18
+ Using the above example, we have the following.
19
+ *convenience helper* \*example paths* \*controller method*
20
+ chair_path(@chair) \chair, chair.html, chair/1, chair/1.html \ChairsController#get
21
+ chair_path(@chair, :method => :post) \chair, chair.xml, chair/1, chair/1.xml \ChairsController#post
22
+ chair_path(@chair, :method => :put) \chair, chair.js, chair/1, chair/1.js \ChairsController#put
23
+ chair_path(@chair, :method => :delete) \chair, chair.html, chair/1, chair/1.html \ChairsController#delete
24
+ chairs_path(@chairs) \chairs, chairs.html, chairs/1,2,3, chairs/1,2,3.html \ChairsController#gets
25
+ chairs_path(@chairs, :method => :post) \chairs, chairs.xml, chairs/1,2,3, chairs/1,2,3.xml \ChairsController#posts
26
+ chairs_path(@chairs, :method => :put) \chairs, chairs.js, chairs/1,2,3, chairs/1,2,3.js \ChairsController#puts
27
+ chairs_path(@chairs, :method => :delete) \chairs, chairs.html, chairs/1,2,3, chairs/1,2,3.html \ChairsController#deletes
28
+ Note the ‘s’ on the end of the controller method names in the plural case. The above are only examples. The paths take
29
+ the usual formats .xml, .js, etc. The id number in the singular case and the ids in the plural case are optional.
30
+
31
+
32
+ == Rationale
33
+
34
+ When Rails began flirting with ‘RESTful’ resources, there wasn’t much RESTful about Rails’ implementation other than
35
+ an added conversion layer between REST verbs and the default Rails actions. Those actions are Index (formerly List),
36
+ Show, New, Edit, Create, Update, and Destroy. We take the position that this added conversion layer is both
37
+ unnecessary and counter-productive.
38
+
39
+ We have demonstrated that this conversion layer, the current implementation of map.resource, is unnecessary
40
+ in this plugin. We suggest that the current implementation in Rails is counter-productive, for the reasons elaborated below.
41
+
42
+ == Breaking the Origin Barrier
43
+
44
+ RESTful architecture provides scalability and fault-tolerance in a network application. A default Rails
45
+ implementation overrides this with the response header “Cach-Control: private, max-age=0, must-revalidate.”
46
+ This tells every node between the server and the client that every request requires the entire page
47
+ to be sent on each request.
48
+
49
+ Imagine a situation where the index page to your site fairly static. Rather than sending the header above,
50
+ the server responds to the root request with “Cach-Control: max-age=28800.” Boom. What was that sound?
51
+ That was sound of your index page being served so fast that it moved faster than your server can spit
52
+ the bits out. Let’s call it ‘Breaking the Origin Barrier.’
53
+
54
+ Your Rails web site now responds much faster, because the client web browser knows not to bother the
55
+ origin server if 8 hours haven’t passed since the last time you looked at the index page. Client-side
56
+ caching is the best possible performance solution in applications that transfer state representations
57
+ across a network.
58
+
59
+ The next-best performance solution is if an intermediate server caches the web page. You now have that
60
+ advantage as well. Any caching servers set up between the origin server and the clients requesting that
61
+ resource (the index page) won’t bother passing the request along and waiting for a response. Instead,
62
+ they will simply and quickly reply with the cached page.
63
+
64
+ As you can see, this could drastically decrease your server load and bandwidth needs. This is how HTTP
65
+ was designed to work. This is one benefit of using a RESTful approach.
66
+
67
+ To get this caching benefit, you could add ‘expires_in’ to the example above:
68
+
69
+ class ChairsController < ApplicationController
70
+ def gets
71
+ …code…
72
+ expires_in 8.hours, :private => false
73
+ end
74
+ end
75
+
76
+ (Gets is the method that corresponds to the Index action in traditional Rails apps.)
77
+
78
+ == Tips
79
+
80
+ Rails typically treated Show, New, and Edit actions as get requests. In our experience, we found that
81
+ the edit view was always redundant with either the Show or the New view, so we discard it. In many cases,
82
+ we find that a properly scoped controller would have a form behind HTTP_AUTH (the RESTful alternative to
83
+ sessions). We also found that the form helpers obviate the New view, since we can just call
84
+ @record.new_record? in the view if we need to distinguish. So rather than maintain show(), edit(),
85
+ and new(), we recommend combining edit() and new() into one get.html.* form.
86
+
87
+ You can distinguish the editable representation of a resource either by the subject of the RESTful
88
+ sentence (normally the logged-in user), or by another parameter (like appending ?edit=true to the url).
89
+ In the former case we recommend putting the logged-in views and controllers under a separate namespace
90
+ in order to make it convenient to disable the caching for those GET requests. In the latter case you
91
+ can render a different template based on the presence of the edit parameter.
92
+
93
+ In typical ActiveRecord implementations, the id starts with the integer 1 and increments with each
94
+ record created. We use the id number 0 to represent a new ActiveRecord instance. Using that convention,
95
+ we can obviate the separate view and controller method for New.
96
+
97
+ == Contributing to map_restfully
98
+
99
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
100
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
101
+ * Fork the project
102
+ * Start a feature/bugfix branch
103
+ * Commit and push until you are happy with your contribution
104
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
105
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
106
+
107
+ == Turn Off Session
108
+
109
+ If you adhere to strick RESTful principles, you can separate your application controller by the subject
110
+ of the request: “I get the book…” or “Anonymous gets the book…” In this example, you can see that the
111
+ former case has the subject “I,” which is sometimes stored in the session cookie. You will get a
112
+ significant performance boost by turning sessions off. If you cannot identify the subject via a URL
113
+ parameter, then you are probably dealing with an issue of authentication. Consider using HTTP
114
+ authentication. This will populate your parameters with a ‘user’ and ‘password’ on every request,
115
+ which is automatically handled by the client web browser.
116
+
117
+ To turn off sessions, change the following default line in config/environment.rb from:
118
+
119
+ config.action_controller.session = {
120
+ :session_key => '_dummy_session',
121
+ :secret => 'someveryveryverylooooooooongrandomstring'
122
+ }
123
+
124
+ to:
125
+
126
+ config.action_controller.session = false
127
+
128
+ And remove the following line in ‘app/controllers/application.rb’:
129
+
130
+ protect_from_forgery #:secret => 'somerandomstring
131
+
132
+ == Copyright
133
+
134
+ Copyright (c) 2011 CLR. See LICENSE.txt for
135
+ further details.
data/Rakefile ADDED
@@ -0,0 +1,35 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
+ gem.name = "map_restfully"
16
+ gem.homepage = "http://github.com/clr/map_restfully"
17
+ gem.license = "MIT"
18
+ gem.summary = %Q{Provide HTTP-verb style routing to Rails.}
19
+ gem.description = %Q{Convenience method to provide controller actions that correspond to the HTTP verbs.}
20
+ gem.email = "clr@port49.com"
21
+ gem.authors = ["CLR"]
22
+ gem.add_dependency 'rails', '~> 3.0.10'
23
+ gem.add_dependency 'rack-test'
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rake/testtask'
29
+ Rake::TestTask.new(:test) do |test|
30
+ test.libs << 'lib' << 'test'
31
+ test.pattern = 'test/**/test_*.rb'
32
+ test.verbose = true
33
+ end
34
+
35
+ task :default => :test
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.5.0
@@ -0,0 +1,17 @@
1
+ module ActionDispatch
2
+ module Routing
3
+ class Mapper
4
+ module MapRestfully
5
+ def map_restfully(resource_name, options={})
6
+ singular = resource_name.to_s
7
+ plural = options[:plural] || singular.pluralize
8
+ %w(get post put delete).each do |verb|
9
+ match({"#{singular}(/:id(.:format))" => "#{plural}##{verb}", :via => verb, :as => singular}.merge(options))
10
+ match "#{plural}(/:ids(.:format))" => "#{plural}##{verb}s", :via => verb, :as => plural
11
+ end
12
+ end
13
+ end
14
+ include MapRestfully
15
+ end
16
+ end
17
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,69 @@
1
+ require 'rubygems'
2
+ # Set up gems listed in the Gemfile.
3
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
4
+ require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
5
+
6
+ require "action_controller/railtie"
7
+ require "rails/test_unit/railtie"
8
+ require 'rails/test_help'
9
+ require File.expand_path('../../lib/map_restfully', __FILE__)
10
+
11
+ module Test
12
+ class Application < Rails::Application
13
+ end
14
+ end
15
+ Test::Application.config.secret_token = ("testingframework" * 2)
16
+
17
+ def app
18
+ Test::Application.routes.draw do
19
+ map_restfully :chair
20
+ match("/url" => "controller#action", :as => :nothing)
21
+ end
22
+ Test::Application
23
+ end
24
+
25
+ # helper for a common assertion
26
+ def assert_ok
27
+ assert last_response.ok?, "Response was not ok: #{last_response.status}"
28
+ end
29
+
30
+ # helper for looping through the HTTP verbs
31
+ def each_http_verb(&block)
32
+ %w(GET POST PUT DELETE).each do |verb|
33
+ yield verb
34
+ end
35
+ end
36
+
37
+ class ChairsController < ActionController::Base
38
+ include Test::Application.routes.url_helpers
39
+
40
+ # singular actions
41
+ each_http_verb do |verb|
42
+ define_method verb.downcase.to_sym do
43
+ response = params[:id].nil? ? "new chair" : "chair number #{params[:id]}"
44
+ respond_to do |format|
45
+ format.html{render :text => "HTML #{verb} #{response}"}
46
+ format.js{ render :text => "JSON #{verb} #{response}"}
47
+ end
48
+ end
49
+ end
50
+ # plural actions
51
+ each_http_verb do |verb|
52
+ define_method "#{verb.downcase}s".to_sym do
53
+ response = params[:ids].nil? ? "all chairs" : "chairs numbered #{params[:ids].split(',').join(" and ")}"
54
+ respond_to do |format|
55
+ format.html{render :text => "HTML #{verb}S #{response}"}
56
+ format.js{ render :text => "JSON #{verb}S #{response}"}
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ class ActiveSupport::TestCase
63
+ # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order.
64
+ #
65
+ # Note: You'll currently still have to declare fixtures explicitly in integration tests
66
+ # -- they do not yet inherit this setting
67
+
68
+ # Add more helper methods to be used by all tests here...
69
+ end
data/test/test_rack.rb ADDED
@@ -0,0 +1,54 @@
1
+ require File.expand_path('../helper', __FILE__)
2
+ require 'rack/test'
3
+
4
+ class TestRack < ActiveSupport::TestCase
5
+ include Rack::Test::Methods
6
+
7
+ each_http_verb do |verb|
8
+ test "#{verb} request to singular resource" do
9
+ self.send(verb.downcase.to_sym, "/chair")
10
+ assert_ok
11
+ assert_equal "HTML #{verb} new chair", last_response.body
12
+ end
13
+ end
14
+
15
+ each_http_verb do |verb|
16
+ test "#{verb} request to singular resource by resources ID" do
17
+ self.send(verb.downcase.to_sym, "/chair/42")
18
+ assert_ok
19
+ assert_equal "HTML #{verb} chair number 42", last_response.body
20
+ end
21
+ end
22
+
23
+ each_http_verb do |verb|
24
+ test "#{verb} request to singular resource by resources ID in another format" do
25
+ self.send(verb.downcase.to_sym, "/chair/42.js")
26
+ assert_ok
27
+ assert_equal "JSON #{verb} chair number 42", last_response.body
28
+ end
29
+ end
30
+
31
+ each_http_verb do |verb|
32
+ test "#{verb} request to plural resource" do
33
+ self.send(verb.downcase.to_sym, "/chairs")
34
+ assert_ok
35
+ assert_equal "HTML #{verb}S all chairs", last_response.body
36
+ end
37
+ end
38
+
39
+ each_http_verb do |verb|
40
+ test "#{verb} request to plural resource by resources IDs" do
41
+ self.send(verb.downcase.to_sym, "/chairs/42,37")
42
+ assert_ok
43
+ assert_equal "HTML #{verb}S chairs numbered 42 and 37", last_response.body
44
+ end
45
+ end
46
+
47
+ each_http_verb do |verb|
48
+ test "#{verb} request to plural resource by resources IDs in another format" do
49
+ self.send(verb.downcase.to_sym, "/chairs/42,37.js")
50
+ assert_ok
51
+ assert_equal "JSON #{verb}S chairs numbered 42 and 37", last_response.body
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,23 @@
1
+ require File.expand_path('../helper', __FILE__)
2
+
3
+ class TestRoutes < ActiveSupport::TestCase
4
+
5
+ test "that a named routes exist for singular and plural resource" do
6
+ assert app.routes.named_routes[:chair].is_a?(ActionDispatch::Routing::Route), "Singular named route not found."
7
+ assert app.routes.named_routes[:chairs].is_a?(ActionDispatch::Routing::Route), "Plural named route not found."
8
+ end
9
+
10
+ each_http_verb do |verb|
11
+ test "that a map exists in the router for #{verb} resource singular" do
12
+ assert saved_route = app.routes.routes.detect{|r| r.verb == verb && r.path == "/chair(/:id(.:format))"}, "Route not found for #{verb} resource"
13
+ assert_equal({:controller => 'chairs', :action => verb.downcase}, saved_route.requirements)
14
+ end
15
+ end
16
+
17
+ each_http_verb do |verb|
18
+ test "that a map exists in the router for #{verb} resource plural" do
19
+ assert saved_route = app.routes.routes.detect{|r| r.verb == verb && r.path == "/chairs(/:ids(.:format))"}, "Route not found for #{verb} resource"
20
+ assert_equal({:controller => 'chairs', :action => "#{verb.downcase}s"}, saved_route.requirements)
21
+ end
22
+ end
23
+ end
metadata ADDED
@@ -0,0 +1,124 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: map_restfully
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - CLR
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-10-19 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rails
16
+ requirement: &18942540 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 3.0.10
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *18942540
25
+ - !ruby/object:Gem::Dependency
26
+ name: rack-test
27
+ requirement: &18941980 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *18941980
36
+ - !ruby/object:Gem::Dependency
37
+ name: bundler
38
+ requirement: &18941480 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: 1.0.0
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *18941480
47
+ - !ruby/object:Gem::Dependency
48
+ name: jeweler
49
+ requirement: &18940980 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 1.6.4
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *18940980
58
+ - !ruby/object:Gem::Dependency
59
+ name: rails
60
+ requirement: &18940480 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ~>
64
+ - !ruby/object:Gem::Version
65
+ version: 3.0.10
66
+ type: :runtime
67
+ prerelease: false
68
+ version_requirements: *18940480
69
+ - !ruby/object:Gem::Dependency
70
+ name: rack-test
71
+ requirement: &18939960 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: *18939960
80
+ description: Convenience method to provide controller actions that correspond to the
81
+ HTTP verbs.
82
+ email: clr@port49.com
83
+ executables: []
84
+ extensions: []
85
+ extra_rdoc_files:
86
+ - README.rdoc
87
+ files:
88
+ - Gemfile
89
+ - README.rdoc
90
+ - Rakefile
91
+ - VERSION
92
+ - lib/map_restfully.rb
93
+ - test/helper.rb
94
+ - test/test_rack.rb
95
+ - test/test_routes.rb
96
+ homepage: http://github.com/clr/map_restfully
97
+ licenses:
98
+ - MIT
99
+ post_install_message:
100
+ rdoc_options: []
101
+ require_paths:
102
+ - lib
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ none: false
105
+ requirements:
106
+ - - ! '>='
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ segments:
110
+ - 0
111
+ hash: 4251634958389231380
112
+ required_rubygems_version: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ requirements: []
119
+ rubyforge_project:
120
+ rubygems_version: 1.8.10
121
+ signing_key:
122
+ specification_version: 3
123
+ summary: Provide HTTP-verb style routing to Rails.
124
+ test_files: []