heimdallr-resource 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,7 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.2
4
+
5
+ * Additional methods resources assignation [@whitequark][]
6
+
3
7
  ## 1.0.1
4
8
 
5
9
  * load_and_authorize_resource accepts just one hash parameter from now [@_inossidabile][]
6
10
 
7
11
  [@_inossidabile]: http://twitter.com/#!/_inossidabile
12
+ [@whitequark]: http://twitter.com/#!/whitequark
data/README.md CHANGED
@@ -5,43 +5,101 @@ Heimdallr Resource is a gem which provides CanCan-like interface for writing sec
5
5
  controllers on top of [Heimdallr](http://github.com/roundlake/heimdallr)-protected
6
6
  models.
7
7
 
8
- ``` ruby
8
+ Overview
9
+ --------
10
+
11
+ API of Heimdallr Resource basically consists of two methods, `load_resource` and `authorize_resource`.
12
+ Both work by adding a filter in standard Rails filter chain and obey the `:only` and `:except` options.
13
+
14
+ `load_resource` loads a record or scope and wraps it in a Heimadllr proxy. For `index` action, a scope is loaded. For `show`, `new`, `create`, `edit`, `update` and `destroy` a record is loaded. No further action is performed by Heimdallr Resource.
15
+
16
+ `authorize_resource` verifies if the current security context allows for creating or updating the records. The checks are performed for `new`, `create`, `edit` and `update` actions. `index` will simply follow the defined `:fetch` scope.
17
+
18
+ ```ruby
9
19
  class CricketController < ApplicationController
10
20
  include Heimdallr::Resource
11
21
 
12
22
  load_and_authorize_resource
13
23
 
14
- # or set the name explicitly:
15
- #
16
- # load_and_authorize_resource :resource => :cricket
17
-
18
- # if nested:
19
- #
20
- # routes.rb:
21
- # resources :categories do
22
- # resources :crickets
23
- # end
24
- #
25
- # load_and_authorize_resource :through => :category
26
-
27
24
  def index
28
25
  # @crickets is loaded and secured here
29
26
  end
27
+
28
+ def show
29
+ # @cricket is loaded by .find(params[:id]) and secured here
30
+ end
31
+
32
+ def create
33
+ # @cricket is created, filled with params[:cricket] and secured here
34
+ end
35
+
36
+ def update
37
+ # @cricket is loaded by .find(params[:id]) and secured here.
38
+ # Fields from params[:cricket] won't be applied automatically!
39
+ end
40
+
41
+ def show
42
+ # @cricket is loaded by .find(params[:id]) and secured here.
43
+ end
44
+
45
+ def destroy
46
+ # @cricket is loaded by .find(params[:id]) and secured here.
47
+ end
30
48
  end
31
49
  ```
32
50
 
33
- Overview
34
- --------
51
+ Custom entity
52
+ -------------
35
53
 
36
- API of Heimdallr Resource basically consists of two methods, `load_resource` and `authorize_resource`.
37
- Both work by adding a filter in standard Rails filter chain and obey the `:only` and `:except` options.
54
+ To explicitly specify which class should be used as a Heimdallr model you can use the following option:
55
+
56
+ ```ruby
57
+ # This will use the Entity class
58
+ load_and_authorize :resource => :'entity'
59
+ # This will use the Namespace::OtherEntity class
60
+ load_and_authorize :resource => :'namespace/other_entity'
61
+ ```
62
+
63
+ Namespaces
64
+ ----------
65
+ By default Heimdallr Resource will seek for the namespace just like it does with the class. So for `Foo::Bars` controller it will try to bind to `Foo::Bar` model.
66
+
67
+ Custom methods (besides CRUD)
68
+ -----------------------------
69
+
70
+ By default Heimdallr Resource will consider non-CRUD methods a `:record` methods (like `show`). So it will try to find entity using `params[:id]`. To modify this behavior to make it work like `index` or `create`, you can explicitly define the way it should handle the methods.
71
+
72
+ ```ruby
73
+ load_and_authorize :collection => [:search], :new_record => [:special_create], :record => [:attack]
74
+ ```
75
+
76
+ Inlined resources
77
+ -----------------
78
+ If you have inlined resource with such routing:
38
79
 
39
- `load_resource` loads a record or scope and wraps it in a Heimadllr proxy. For `index` action, a scope is
40
- loaded. For `show`, `new`, `create`, `edit`, `update` and `destroy` a record is loaded. No further action
41
- is performed by Heimdallr Resource.
80
+ ```ruby
81
+ resources :foos do
82
+ resources :bars do
83
+ resources :bazs
84
+ end
85
+ end
86
+ ```
87
+
88
+ Rails will provide `params[:foo_id]` and `params[:bar_id]` inside `BazsController`. To make Heimdallr search through and assign the parent entities you can use this syntax:
89
+
90
+ ```ruby
91
+ load_and_authorize_resource :through => :foo
92
+ # or even
93
+ load_and_authorize_resource :through => [:foo, :bar]
94
+ ```
95
+
96
+ If the whole path or some if its parts are optional, you can specify the `:shallow` option.
97
+
98
+ ```ruby
99
+ load_and_authorize_resource :through => [:foo, :bar], :shallow => true
100
+ ```
42
101
 
43
- `authorize_resource` verifies if the current security context allows for creating or updating the records.
44
- The checks are performed for `new`, `create`, `edit` and `update` actions.
102
+ In the latter case it will work from any route, the direct or inlined one.
45
103
 
46
104
  Credits
47
105
  -------
@@ -3,7 +3,7 @@ $:.push File.expand_path("../lib", __FILE__)
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "heimdallr-resource"
6
- s.version = "1.0.1"
6
+ s.version = "1.0.2"
7
7
  s.authors = ["Peter Zotov", "Boris Staal"]
8
8
  s.email = ["whitequark@whitequark.org", "boris@roundlake.ru"]
9
9
  s.homepage = "http://github.com/roundlake/heimdallr-resource"
@@ -5,7 +5,7 @@ module Heimdallr
5
5
  module ResourceImplementation
6
6
  class << self
7
7
  def prepare_options(klass, options)
8
- options.merge! :resource => (options[:resource] || klass.name.sub(/Controller$/, '').underscore).to_s
8
+ options = options.merge :resource => (options[:resource] || klass.name.sub(/Controller$/, '').underscore).to_s
9
9
 
10
10
  filter_options = {}
11
11
  filter_options[:only] = options.delete(:only) if options.has_key?(:only)
@@ -17,60 +17,125 @@ module Heimdallr
17
17
  def load(controller, options)
18
18
  unless controller.instance_variable_defined?(ivar_name(controller, options))
19
19
  if options.has_key? :through
20
- if options[:singleton]
21
- scope = controller.instance_variable_get(:"@#{options[:through]}").
22
- send(:"#{options[:resource]}")
20
+ target = load_target(controller, options)
21
+
22
+ if target
23
+ if options[:singleton]
24
+ scope = target.send(:"#{options[:resource].parameterize('_')}")
25
+ else
26
+ scope = target.send(:"#{options[:resource].parameterize('_').pluralize}")
27
+ end
28
+ elsif options[:shallow]
29
+ scope = options[:resource].classify.constantize.scoped
23
30
  else
24
- scope = controller.instance_variable_get(:"@#{options[:through]}").
25
- send(:"#{options[:resource].pluralize}")
31
+ raise "Cannot fetch #{options[:resource]} via #{options[:through]}"
26
32
  end
27
33
  else
28
- scope = options[:resource].camelize.constantize.scoped
34
+ scope = options[:resource].classify.constantize.scoped
29
35
  end
30
36
 
31
- case controller.params[:action]
32
- when 'index'
33
- controller.instance_variable_set(ivar_name(controller, options), scope)
34
- when 'new', 'create'
35
- controller.instance_variable_set(ivar_name(controller, options),
36
- scope.new(controller.params[options[:resource]]))
37
- when 'show', 'edit', 'update', 'destroy'
38
- controller.instance_variable_set(ivar_name(controller, options),
39
- scope.find(controller.params[:"#{options[:resource]}_id"] ||
40
- controller.params[:id]))
41
- end
37
+ loaders = {
38
+ collection: -> {
39
+ controller.instance_variable_set(ivar_name(controller, options), scope)
40
+ },
41
+
42
+ new_record: -> {
43
+ controller.instance_variable_set(ivar_name(controller, options),
44
+ scope.new(controller.params[options[:resource].split('/').last]))
45
+ },
46
+
47
+ record: -> {
48
+ controller.instance_variable_set(ivar_name(controller, options),
49
+ scope.find(controller.params[:"#{options[:resource]}_id"] ||
50
+ controller.params[:id]))
51
+ },
52
+
53
+ related_record: -> {
54
+ if controller.params[:"#{options[:resource]}_id"]
55
+ controller.instance_variable_set(ivar_name(controller, options),
56
+ scope.find(controller.params[:"#{options[:resource]}_id"]))
57
+ end
58
+ }
59
+ }
60
+
61
+ loaders[action_type(controller.params[:action], options)].()
42
62
  end
43
63
  end
44
64
 
45
65
  def authorize(controller, options)
46
- controller.instance_variable_set(ivar_name(controller, options.merge(:insecure => true)),
47
- controller.instance_variable_get(ivar_name(controller, options)))
66
+ value = controller.instance_variable_get(ivar_name(controller, options))
67
+ return unless value
68
+
69
+ controller.instance_variable_set(ivar_name(controller, options.merge(:insecure => true)), value)
48
70
 
49
- value = controller.instance_variable_get(ivar_name(controller, options)).
50
- restrict(controller.security_context)
71
+ value = value.restrict(controller.security_context)
51
72
  controller.instance_variable_set(ivar_name(controller, options), value)
52
73
 
53
74
  case controller.params[:action]
54
75
  when 'new', 'create'
76
+ value.assign_attributes(value.reflect_on_security[:restrictions].fixtures[:create])
77
+
55
78
  unless value.reflect_on_security[:operations].include? :create
56
79
  raise Heimdallr::AccessDenied, "Cannot create model"
57
80
  end
81
+
58
82
  when 'edit', 'update'
83
+ value.assign_attributes(value.reflect_on_security[:restrictions].fixtures[:update])
84
+
59
85
  unless value.reflect_on_security[:operations].include? :update
60
86
  raise Heimdallr::AccessDenied, "Cannot update model"
61
87
  end
88
+
62
89
  when 'destroy'
63
90
  unless value.destroyable?
64
91
  raise Heimdallr::AccessDenied, "Cannot delete model"
65
92
  end
66
- end
93
+ end unless options[:related]
94
+ end
95
+
96
+ def load_target(controller, options)
97
+ Array.wrap(options[:through]).map do |parent|
98
+ loaded = controller.instance_variable_get(:"@#{parent}")
99
+ unless loaded
100
+ load(controller, :resource => parent.to_s, :related => true)
101
+ loaded = controller.instance_variable_get(:"@#{parent}")
102
+ end
103
+ if loaded && options[:authorize_chain]
104
+ authorize(controller, :resource => parent.to_s, :related => true)
105
+ end
106
+ controller.instance_variable_get(:"@#{parent}")
107
+ end.reject(&:nil?).first
67
108
  end
68
109
 
69
110
  def ivar_name(controller, options)
70
- if controller.params[:action] == 'index'
71
- :"@#{options[:resource].pluralize}"
111
+ if action_type(controller.params[:action], options) == :collection
112
+ :"@#{options[:resource].parameterize('_').pluralize}"
72
113
  else
73
- :"@#{options[:resource]}"
114
+ :"@#{options[:resource].parameterize('_')}"
115
+ end
116
+ end
117
+
118
+ def action_type(action, options)
119
+ if options[:related]
120
+ :related_record
121
+ else
122
+ action = action.to_sym
123
+ case action
124
+ when :index
125
+ :collection
126
+ when :new, :create
127
+ :new_record
128
+ when :show, :edit, :update, :destroy
129
+ :record
130
+ else
131
+ if options[:collection] && options[:collection].include?(action)
132
+ :collection
133
+ elsif options[:new] && options[:new].include?(action)
134
+ :new_record
135
+ else
136
+ :record
137
+ end
138
+ end
74
139
  end
75
140
  end
76
141
  end
@@ -82,12 +147,14 @@ module Heimdallr
82
147
 
83
148
  module ClassMethods
84
149
  def load_and_authorize_resource(options={})
150
+ options[:authorize_chain] = true
85
151
  load_resource(options)
86
152
  authorize_resource(options)
87
153
  end
88
154
 
89
155
  def load_resource(options={})
90
156
  options, filter_options = Heimdallr::ResourceImplementation.prepare_options(self, options)
157
+ self.own_heimdallr_options = options
91
158
 
92
159
  before_filter filter_options do |controller|
93
160
  Heimdallr::ResourceImplementation.load(controller, options)
@@ -96,11 +163,19 @@ module Heimdallr
96
163
 
97
164
  def authorize_resource(options={})
98
165
  options, filter_options = Heimdallr::ResourceImplementation.prepare_options(self, options)
166
+ self.own_heimdallr_options = options
99
167
 
100
168
  before_filter filter_options do |controller|
101
169
  Heimdallr::ResourceImplementation.authorize(controller, options)
102
170
  end
103
171
  end
172
+
173
+ protected
174
+
175
+ def own_heimdallr_options=(options)
176
+ cattr_accessor :heimdallr_options
177
+ self.heimdallr_options = options
178
+ end
104
179
  end
105
180
  end
106
181
  end
@@ -26,4 +26,8 @@ class EntityController < ApplicationController
26
26
  def destroy
27
27
  render :nothing => true
28
28
  end
29
+
30
+ def penetrate
31
+ render :nothing => true
32
+ end
29
33
  end
@@ -1,4 +1,8 @@
1
1
  Dummy::Application.routes.draw do
2
- resources :entity
2
+ resources :entity do
3
+ member do
4
+ post :penetrate
5
+ end
6
+ end
3
7
  resources :fluffies
4
8
  end
@@ -69,5 +69,13 @@ describe EntityController, :type => :controller do
69
69
  User.mock @maria
70
70
  expect { post :destroy, {:id => @public.id} }.should raise_error
71
71
  end
72
+
73
+ it "assigns the custom methods" do
74
+ User.mock @john
75
+ post :penetrate, {:id => @public.id}
76
+
77
+ assigns(:entity).should be_kind_of Heimdallr::Proxy::Record
78
+ assigns(:entity).id.should == @public.id
79
+ end
72
80
  end
73
81
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: heimdallr-resource
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.0.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,11 +10,11 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2012-04-08 00:00:00.000000000 Z
13
+ date: 2012-04-12 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rspec-rails
17
- requirement: &70267893225580 !ruby/object:Gem::Requirement
17
+ requirement: &70169121125720 !ruby/object:Gem::Requirement
18
18
  none: false
19
19
  requirements:
20
20
  - - ! '>='
@@ -22,10 +22,10 @@ dependencies:
22
22
  version: '0'
23
23
  type: :development
24
24
  prerelease: false
25
- version_requirements: *70267893225580
25
+ version_requirements: *70169121125720
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: activerecord
28
- requirement: &70267893201220 !ruby/object:Gem::Requirement
28
+ requirement: &70169121125180 !ruby/object:Gem::Requirement
29
29
  none: false
30
30
  requirements:
31
31
  - - ! '>='
@@ -33,10 +33,10 @@ dependencies:
33
33
  version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
- version_requirements: *70267893201220
36
+ version_requirements: *70169121125180
37
37
  - !ruby/object:Gem::Dependency
38
38
  name: sqlite3
39
- requirement: &70267893200560 !ruby/object:Gem::Requirement
39
+ requirement: &70169121124440 !ruby/object:Gem::Requirement
40
40
  none: false
41
41
  requirements:
42
42
  - - ! '>='
@@ -44,10 +44,10 @@ dependencies:
44
44
  version: '0'
45
45
  type: :development
46
46
  prerelease: false
47
- version_requirements: *70267893200560
47
+ version_requirements: *70169121124440
48
48
  - !ruby/object:Gem::Dependency
49
49
  name: tzinfo
50
- requirement: &70267893199740 !ruby/object:Gem::Requirement
50
+ requirement: &70169121116540 !ruby/object:Gem::Requirement
51
51
  none: false
52
52
  requirements:
53
53
  - - ! '>='
@@ -55,10 +55,10 @@ dependencies:
55
55
  version: '0'
56
56
  type: :development
57
57
  prerelease: false
58
- version_requirements: *70267893199740
58
+ version_requirements: *70169121116540
59
59
  - !ruby/object:Gem::Dependency
60
60
  name: heimdallr
61
- requirement: &70267893198820 !ruby/object:Gem::Requirement
61
+ requirement: &70169121115860 !ruby/object:Gem::Requirement
62
62
  none: false
63
63
  requirements:
64
64
  - - ! '>='
@@ -66,7 +66,7 @@ dependencies:
66
66
  version: '0'
67
67
  type: :runtime
68
68
  prerelease: false
69
- version_requirements: *70267893198820
69
+ version_requirements: *70169121115860
70
70
  description: Heimdallr-Resource provides CanCan-like interface for Heimdallr-secured
71
71
  objects.
72
72
  email: