Reference scope by same name as serialization scope

By default, the serialization scope uses current_user, and you can
now reference the scope as "current_user" in the serializer. If you
override the scope using "serialization_scope" in your controller,
it will use that method name instead.
This commit is contained in:
beerlington 2013-03-16 17:58:17 -04:00
parent 41a389a900
commit 4a13f86961
6 changed files with 106 additions and 11 deletions

View File

@ -1,5 +1,9 @@
# unreleased # unreleased
* Add an alias for `scope` method to be the name of the context. By default
this is `current_user`. The name is automatically set when using
`serialization_scope` in the controller.
# VERSION 0.7 # VERSION 0.7

View File

@ -232,22 +232,22 @@ end
Within a serializer's methods, you can access the object being Within a serializer's methods, you can access the object being
serialized as `object`. serialized as `object`.
You can also access the `scope` method, which provides an You can also access the `current_user` method, which provides an
authorization context to your serializer. By default, scope authorization context to your serializer. By default, the context
is the current user of your application, but this is the current user of your application, but this
[can be customized](#customizing-scope). [can be customized](#customizing-scope).
Serializers will check for the presence of a method named Serializers will check for the presence of a method named
`include_[ATTRIBUTE]?` to determine whether a particular attribute should be `include_[ATTRIBUTE]?` to determine whether a particular attribute should be
included in the output. This is typically used to customize output included in the output. This is typically used to customize output
based on `scope`. For example: based on `current_user`. For example:
```ruby ```ruby
class PostSerializer < ActiveModel::Serializer class PostSerializer < ActiveModel::Serializer
attributes :id, :title, :body, :author attributes :id, :title, :body, :author
def include_author? def include_author?
scope.admin? current_user.admin?
end end
end end
``` ```
@ -325,7 +325,7 @@ class PersonSerializer < ActiveModel::Serializer
def attributes def attributes
hash = super hash = super
if scope.admin? if current_user.admin?
hash["ssn"] = object.ssn hash["ssn"] = object.ssn
hash["secret"] = object.mothers_maiden_name hash["secret"] = object.mothers_maiden_name
end end
@ -353,7 +353,7 @@ class PostSerializer < ActiveModel::Serializer
# only let the user see comments he created. # only let the user see comments he created.
def comments def comments
object.comments.where(:created_by => scope) object.comments.where(:created_by => current_user)
end end
end end
``` ```
@ -395,7 +395,7 @@ class PostSerializer < ActiveModel::Serializer
has_many :comments has_many :comments
def include_associations! def include_associations!
include! :author if scope.admin? include! :author if current_user.admin?
include! :comments unless object.comments_disabled? include! :comments unless object.comments_disabled?
end end
end end
@ -587,7 +587,7 @@ Ajax requests, you probably just want to use the default embedded behavior.
## Customizing Scope ## Customizing Scope
In a serializer, `scope` is the current authorization scope which the controller In a serializer, `current_user` is the current authorization scope which the controller
provides to the serializer when you call `render :json`. By default, this is provides to the serializer when you call `render :json`. By default, this is
`current_user`, but can be customized in your controller by calling `current_user`, but can be customized in your controller by calling
`serialization_scope`: `serialization_scope`:
@ -598,6 +598,9 @@ class ApplicationController < ActionController::Base
end end
``` ```
The above example will also change the scope name from `current_user` to
`current_admin`.
Please note that, until now, `serialization_scope` doesn't accept a second Please note that, until now, `serialization_scope` doesn't accept a second
object with options for specifying which actions should or should not take a object with options for specifying which actions should or should not take a
given scope in consideration. given scope in consideration.
@ -626,12 +629,12 @@ class CitiesController < ApplicationController
def show def show
@city = City.find(params[:id]) @city = City.find(params[:id])
render :json => @city, :scope => current_admin? render :json => @city, :scope => current_admin, :scope_name => :current_admin
end end
end end
``` ```
Assuming that the `current_admin?` method needs to make a query in the database Assuming that the `current_admin` method needs to make a query in the database
for the current user, the advantage of this approach is that, by setting for the current user, the advantage of this approach is that, by setting
`serialization_scope` to `nil`, the `index` action no longer will need to make `serialization_scope` to `nil`, the `index` action no longer will need to make
that query, only the `show` action will. that query, only the `show` action will.

View File

@ -261,6 +261,13 @@ module ActiveModel
def initialize(object, options={}) def initialize(object, options={})
@object, @options = object, options @object, @options = object, options
scope_name = @options[:scope_name]
if scope_name && !respond_to?(scope_name)
self.class.class_eval do
define_method scope_name, lambda { scope }
end
end
end end
def root_name def root_name

View File

@ -31,6 +31,7 @@ module ActiveModel
if serializer if serializer
serialization_scope = controller.send(:serialization_scope) serialization_scope = controller.send(:serialization_scope)
options[:scope] = serialization_scope unless options.has_key?(:scope) options[:scope] = serialization_scope unless options.has_key?(:scope)
options[:scope_name] = controller.send(:_serialization_scope)
options[:url_options] = controller.send(:url_options) options[:url_options] = controller.send(:url_options)
render(given_options.merge(self.options).merge(:json => serializer.new(resource, options))) render(given_options.merge(self.options).merge(:json => serializer.new(resource, options)))
else else
@ -40,4 +41,4 @@ module ActiveModel
end end
end end
end end
end end

View File

@ -0,0 +1,67 @@
require 'test_helper'
require 'pathname'
class DefaultScopeNameTest < ActionController::TestCase
TestUser = Struct.new(:name, :admin)
class UserSerializer < ActiveModel::Serializer
attributes :admin?
def admin?
current_user.admin
end
end
class UserTestController < ActionController::Base
protect_from_forgery
before_filter { request.format = :json }
def current_user
TestUser.new('Pete', false)
end
def render_new_user
respond_with TestUser.new('pete', false), :serializer => UserSerializer
end
end
tests UserTestController
def test_default_scope_name
get :render_new_user
assert_equal '{"user":{"admin":false}}', @response.body
end
end
class SerializationScopeNameTest < ActionController::TestCase
TestUser = Struct.new(:name, :admin)
class AdminUserSerializer < ActiveModel::Serializer
attributes :admin?
def admin?
current_admin.admin
end
end
class AdminUserTestController < ActionController::Base
protect_from_forgery
serialization_scope :current_admin
before_filter { request.format = :json }
def current_admin
TestUser.new('Bob', true)
end
def render_new_user
respond_with TestUser.new('pete', false), :serializer => AdminUserSerializer
end
end
tests AdminUserTestController
def test_override_scope_name_with_controller
get :render_new_user
assert_equal '{"admin_user":{"admin":true}}', @response.body
end
end

View File

@ -1338,5 +1338,18 @@ class SerializerTest < ActiveModel::TestCase
end end
def test_scope_name_method
serializer = Class.new(ActiveModel::Serializer) do
def has_permission?
current_user.super_user?
end
end
user = User.new
user.superuser = true
post = Post.new(:title => 'Foo')
a_serializer = serializer.new(post, :scope => user, :scope_name => :current_user)
assert a_serializer.has_permission?
end
end end