kabuki-escort-mission 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 kabuki
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,78 @@
1
+ = escort-mission
2
+
3
+ Escort missions. They're like Take Your Child to Work Day, only your job involves
4
+ getting shot at and your child is a mental deficient with a lousy sense of
5
+ direction and giant target painted on his back.
6
+
7
+ Unfortunately, no matter how much story is piled on top of these scenarios, the
8
+ missions rarely feel like more than a chore, leaving you feeling less like the
9
+ savior of the universe and more like a hired thug with little else to do but
10
+ watch other people do more interesting, less violent work; the Blackwater
11
+ mercenary of the videogame land, escorting pencil pushers to the Green Zone.
12
+
13
+ http://www.escapistmagazine.com/articles/view/issues/issue_124/2644-Escort-Missions-Suck
14
+
15
+ Escort Mission provides a well-mannered escort for your models. Typically, this
16
+ is how you might scope and filter models in a standard rails app:
17
+
18
+ def index
19
+ if admin?
20
+ @posts = Post.all
21
+ json = @posts.to_json
22
+ else
23
+ @posts = Post.published
24
+ json = @posts.to_json(:except => [:secret_token, :editor_id])
25
+ end
26
+ render :json => json, :callback => params[:callback]
27
+ end
28
+
29
+ You can replace some of the logic in an Escort class:
30
+
31
+ def index
32
+ @escort = Post::Escort.new(self)
33
+
34
+ #scope automatically adds published scope if user is not an admin
35
+ @posts = @escort.scope.all
36
+
37
+ #filter adds the :except hash if the user is not an admin
38
+ render :json => @posts.to_json(@escort.filter), :callback => params[:callback]
39
+ end
40
+
41
+ You can use the #api method to wrap up the rendering for you.
42
+
43
+ def index
44
+ @escort = Post::Escort.new(self)
45
+ @posts = @escort.api.all
46
+ end
47
+
48
+ Here's an example of what the Post escort might look like:
49
+
50
+ class Post::Escort < EscortMission
51
+ include EscortMission::ActiveRecord, EscortMission::ActionPack
52
+ attr_reader :user
53
+
54
+ def initialize(*args)
55
+ super
56
+ @user = @controller.send(:current_user)
57
+ end
58
+
59
+ def scope
60
+ @scope ||= begin
61
+ s = Post
62
+ s = s.published if @user.admin?
63
+ s
64
+ end
65
+ end
66
+
67
+ def filter
68
+ if @user.admin?
69
+ {}
70
+ else
71
+ {:except => [:secret_token, :editor_id]}
72
+ end
73
+ end
74
+ end
75
+
76
+ == Copyright
77
+
78
+ Copyright (c) 2009 kabuki. See LICENSE for details.
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 1
4
+ :patch: 0
@@ -0,0 +1,47 @@
1
+ class EscortMission
2
+ attr_reader :controller
3
+
4
+ def initialize(controller)
5
+ @controller = controller
6
+ end
7
+
8
+ # Responsible for scoping the model to the current user. The result should be
9
+ # either a model or a named scope. Possible methods it needs to respond to are
10
+ # find/all/first/paginate.
11
+ def scope
12
+ raise NotImplementedError
13
+ end
14
+
15
+ # Responsible for filtering the serialization format for the current user.
16
+ # Some model attributes should be stripped depending on the rule of the given user.
17
+ # The return value will be passed to the model's #to_json or #to_xml methods.
18
+ def filter
19
+ {}
20
+ end
21
+
22
+ # Uses the given escort's controller to respond to the current request. This
23
+ # could involve rendering a conventional template or returning a serialized
24
+ # output of the model.
25
+ class Responder
26
+ attr_reader :escort, :controller
27
+
28
+ def initialize(escort)
29
+ @escort = escort
30
+ @controller = escort.controller
31
+ end
32
+
33
+ # Override this to provide controller-specific details for responding to the
34
+ # controller request in a certain way.
35
+ def display(records)
36
+ raise NotImplementedError
37
+ end
38
+
39
+ # Passes the given method to the escort to fetch the records, and then calls
40
+ # #display to respond to the controller request.
41
+ def method_missing(method_name, *args)
42
+ records = @escort.send(method_name, *args)
43
+ display(records)
44
+ records
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,36 @@
1
+ require 'escort_mission'
2
+ class EscortMission
3
+ module ActiveRecord
4
+ def self.included(base)
5
+ base.delegate :find, :to => :scope
6
+ base.delegate :first, :to => :scope
7
+ base.delegate :all, :to => :scope
8
+ base.delegate :paginate, :to => :scope
9
+ end
10
+ end
11
+
12
+ module ActionPack
13
+ class ApiResponder < Responder
14
+ delegate :params, :to => :controller
15
+ delegate :render, :to => :controller
16
+
17
+ def format
18
+ @controller.request.format
19
+ end
20
+
21
+ def display(records)
22
+ format_sym = format.to_sym
23
+ render format_sym => records.send("to_#{format_sym}", @escort.filter), :callback => params[:callback]
24
+ end
25
+ end
26
+
27
+ # Call this to fetch records and render the response as an API call.
28
+ def api
29
+ EscortMission::ActionPack::ApiResponder.new(self)
30
+ end
31
+ end
32
+ end
33
+
34
+ class EscortMission::Rails < EscortMission
35
+ include EscortMission::ActiveRecord, EscortMission::ActionPack
36
+ end
@@ -0,0 +1,38 @@
1
+ require 'rails_test_helper'
2
+
3
+ class PostsController < ActionController::Base
4
+ def index
5
+ @escort = Post::Escort.new(self, User.new)
6
+ @posts = @escort.api.all
7
+ end
8
+ end
9
+
10
+ class ActionPackTest < ActionController::TestCase
11
+ tests PostsController
12
+
13
+ before :all do
14
+ @published, @unpublished = Post.all
15
+ end
16
+
17
+ describe "ApiResponder" do
18
+ it "renders JSON" do
19
+ get :index, :format => 'json'
20
+ @response.body.should == %([{"title": "a"}])
21
+ end
22
+
23
+ it "renders JSON with callback" do
24
+ get :index, :format => 'json', :callback => 'escort'
25
+ @response.body.should == %(escort([{"title": "a"}]))
26
+ end
27
+
28
+ it "renders XML" do
29
+ get :index, :format => 'xml'
30
+ assert_select 'posts > post > title'
31
+ end
32
+
33
+ it "sets records ivar" do
34
+ get :index, :format => 'json'
35
+ assigns(:posts).should == [@published]
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,26 @@
1
+ require 'rails_test_helper'
2
+
3
+ class ActiveRecordTest < Test::Unit::TestCase
4
+ before :all do
5
+ @user = User.new
6
+ @published, @unpublished = Post.all
7
+ end
8
+
9
+ before { @escort = Post::Escort.new(nil, @user) }
10
+
11
+ it "scopes data with #all according to given user" do
12
+ @escort.scope.all.should == [@published]
13
+ end
14
+
15
+ it "scopes data with #find according to given user" do
16
+ @escort.scope.find(:all).should == [@published]
17
+ end
18
+
19
+ it "scopes data with #first according to given user" do
20
+ @escort.scope.first.should == @published
21
+ end
22
+
23
+ it "filters json data according to the given user" do
24
+ @published.to_json(@escort.filter).should == %({"title": "a"})
25
+ end
26
+ end
@@ -0,0 +1,23 @@
1
+ require 'test_helper'
2
+
3
+ class EscortMissionTest < Test::Unit::TestCase
4
+ before :all do
5
+ @user = User.new
6
+ @array = [1,2,3,4,5]
7
+ end
8
+
9
+ before { @escort = Array::Escort.new(nil, @user, @array) }
10
+
11
+ it "scopes data according to given user" do
12
+ @escort.scope.should == [2,3,4,5]
13
+ end
14
+
15
+ it "filters data according to the given user" do
16
+ @array[@escort.filter].should == [2,3,4,5]
17
+ end
18
+
19
+ it "filters data and creates a response" do
20
+ @escort.test.first.should == 2
21
+ @escort.output.string.should == "[2]"
22
+ end
23
+ end
@@ -0,0 +1,52 @@
1
+ require 'test_helper'
2
+ require 'active_record'
3
+ require 'action_controller'
4
+ require 'escort_mission/rails'
5
+
6
+ ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :dbfile => ':memory:'
7
+
8
+ class User < ActiveRecord::Base
9
+ end
10
+
11
+ class Post < ActiveRecord::Base
12
+ named_scope :published, :conditions => 'published_at is not null'
13
+
14
+ class Escort < EscortMission
15
+ include EscortMission::ActiveRecord, EscortMission::ActionPack
16
+
17
+ def initialize(controller, user)
18
+ super(controller)
19
+ @user = user
20
+ end
21
+
22
+ def scope
23
+ @scope ||= begin
24
+ s = Post
25
+ s = s.published unless @user.admin?
26
+ s
27
+ end
28
+ end
29
+
30
+ def filter
31
+ @user.admin? ? {} : {:only => :title}
32
+ end
33
+ end
34
+ end
35
+
36
+ ActionController::Routing::Routes.draw do |map|
37
+ map.resources :posts
38
+ end
39
+
40
+ ActiveRecord::Schema.define(:version => 0) do
41
+ create_table :users, :force => true do |t|
42
+ t.boolean :admin
43
+ end
44
+
45
+ create_table :posts, :force => true do |t|
46
+ t.string :title
47
+ t.datetime :published_at
48
+ end
49
+ end
50
+
51
+ Post.create! :title => 'a', :published_at => Time.now.utc
52
+ Post.create! :title => 'b'
@@ -0,0 +1,55 @@
1
+ require 'rubygems'
2
+ require 'context'
3
+ require 'matchy'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'escort_mission'
8
+ require 'stringio'
9
+
10
+ class Test::Unit::TestCase
11
+ class TestResponder < EscortMission::Responder
12
+ def display(records)
13
+ records = [records]
14
+ records.flatten!
15
+ @escort.output << "[#{records.join(",")}]"
16
+ end
17
+ end
18
+
19
+ class User
20
+ def admin?
21
+ false
22
+ end
23
+ end
24
+ end
25
+
26
+ class Array::Escort < EscortMission
27
+ attr_reader :user, :array
28
+
29
+ def initialize(controller, user, array)
30
+ super(controller)
31
+ @user, @array = user, array
32
+ end
33
+
34
+ def first
35
+ scope.first
36
+ end
37
+
38
+ def scope
39
+ @scope ||= @user.admin? ? @array : @array[1..-1]
40
+ end
41
+
42
+ def filter
43
+ (@user.admin? ? 0 : 1)..-1
44
+ end
45
+
46
+ # used to capture the output from the test responder
47
+ attr_writer :output
48
+ def output
49
+ @output ||= StringIO.new
50
+ end
51
+
52
+ def test
53
+ @test_responder ||= Test::Unit::TestCase::TestResponder.new(self)
54
+ end
55
+ end
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kabuki-escort-mission
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - kabuki
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-05-28 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: kabukiruby@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.rdoc
24
+ - LICENSE
25
+ files:
26
+ - README.rdoc
27
+ - VERSION.yml
28
+ - lib/escort_mission
29
+ - lib/escort_mission/rails.rb
30
+ - lib/escort_mission.rb
31
+ - test/action_pack_test.rb
32
+ - test/active_record_test.rb
33
+ - test/escort_mission_test.rb
34
+ - test/rails_test_helper.rb
35
+ - test/test_helper.rb
36
+ - LICENSE
37
+ has_rdoc: false
38
+ homepage: http://github.com/kabuki/escort-mission
39
+ post_install_message:
40
+ rdoc_options:
41
+ - --inline-source
42
+ - --charset=UTF-8
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: "0"
50
+ version:
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: "0"
56
+ version:
57
+ requirements: []
58
+
59
+ rubyforge_project:
60
+ rubygems_version: 1.2.0
61
+ signing_key:
62
+ specification_version: 3
63
+ summary: Escort Mission provides a well-mannered escort for your models.
64
+ test_files: []
65
+