From 4a13f86961c557b4e9e8ea4965f176863f190283 Mon Sep 17 00:00:00 2001 From: beerlington Date: Sat, 16 Mar 2013 17:58:17 -0400 Subject: [PATCH] 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. --- CHANGELOG.md | 4 ++ README.md | 23 ++++---- lib/active_model/serializer.rb | 7 +++ lib/active_model/serializer/responder.rb | 3 +- test/serialization_scope_name_test.rb | 67 ++++++++++++++++++++++++ test/serializer_test.rb | 13 +++++ 6 files changed, 106 insertions(+), 11 deletions(-) create mode 100644 test/serialization_scope_name_test.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index eeb1d4af..3037c336 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # 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 diff --git a/README.md b/README.md index 343fb945..6bd446fd 100644 --- a/README.md +++ b/README.md @@ -232,22 +232,22 @@ end Within a serializer's methods, you can access the object being serialized as `object`. -You can also access the `scope` method, which provides an -authorization context to your serializer. By default, scope +You can also access the `current_user` method, which provides an +authorization context to your serializer. By default, the context is the current user of your application, but this [can be customized](#customizing-scope). Serializers will check for the presence of a method named `include_[ATTRIBUTE]?` to determine whether a particular attribute should be included in the output. This is typically used to customize output -based on `scope`. For example: +based on `current_user`. For example: ```ruby class PostSerializer < ActiveModel::Serializer attributes :id, :title, :body, :author def include_author? - scope.admin? + current_user.admin? end end ``` @@ -325,7 +325,7 @@ class PersonSerializer < ActiveModel::Serializer def attributes hash = super - if scope.admin? + if current_user.admin? hash["ssn"] = object.ssn hash["secret"] = object.mothers_maiden_name end @@ -353,7 +353,7 @@ class PostSerializer < ActiveModel::Serializer # only let the user see comments he created. def comments - object.comments.where(:created_by => scope) + object.comments.where(:created_by => current_user) end end ``` @@ -395,7 +395,7 @@ class PostSerializer < ActiveModel::Serializer has_many :comments def include_associations! - include! :author if scope.admin? + include! :author if current_user.admin? include! :comments unless object.comments_disabled? end end @@ -587,7 +587,7 @@ Ajax requests, you probably just want to use the default embedded behavior. ## 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 `current_user`, but can be customized in your controller by calling `serialization_scope`: @@ -598,6 +598,9 @@ class ApplicationController < ActionController::Base 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 object with options for specifying which actions should or should not take a given scope in consideration. @@ -626,12 +629,12 @@ class CitiesController < ApplicationController def show @city = City.find(params[:id]) - render :json => @city, :scope => current_admin? + render :json => @city, :scope => current_admin, :scope_name => :current_admin 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 `serialization_scope` to `nil`, the `index` action no longer will need to make that query, only the `show` action will. diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index dab683cf..7f397a06 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -261,6 +261,13 @@ module ActiveModel def initialize(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 def root_name diff --git a/lib/active_model/serializer/responder.rb b/lib/active_model/serializer/responder.rb index 2b3c282e..46a1d91f 100644 --- a/lib/active_model/serializer/responder.rb +++ b/lib/active_model/serializer/responder.rb @@ -31,6 +31,7 @@ module ActiveModel if serializer serialization_scope = controller.send(:serialization_scope) options[:scope] = serialization_scope unless options.has_key?(:scope) + options[:scope_name] = controller.send(:_serialization_scope) options[:url_options] = controller.send(:url_options) render(given_options.merge(self.options).merge(:json => serializer.new(resource, options))) else @@ -40,4 +41,4 @@ module ActiveModel end end end -end \ No newline at end of file +end diff --git a/test/serialization_scope_name_test.rb b/test/serialization_scope_name_test.rb new file mode 100644 index 00000000..07d2de8b --- /dev/null +++ b/test/serialization_scope_name_test.rb @@ -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 diff --git a/test/serializer_test.rb b/test/serializer_test.rb index 85681374..b45e270c 100644 --- a/test/serializer_test.rb +++ b/test/serializer_test.rb @@ -1338,5 +1338,18 @@ class SerializerTest < ActiveModel::TestCase 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