heimdallr-resource 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: