I'm using Active Model Serializers.
I am trying to access #current_user which is defined inside ApplicationController like this:
class ApplicationController < ActionController::API
before_action :authenticate_request
private
def authenticate_request
auth_header = request.headers['Authorization']
regex = /^Bearer /
auth_header = auth_header.gsub(regex, '') if auth_header
begin
#current_user = AccessToken.get_user_from_token(auth_header)
rescue JWT::ExpiredSignature
return render json: {error: "Token expired"}, status: 401
end
render json: { error: 'Not Authorized' }, status: 401 unless #current_user
end
end
I can use #current_user anywhere I want except inside my ProjectSerializer, which looks like this:
class V1::ProjectSerializer < ActiveModel::Serializer
attributes(:id, :name, :key, :type, :category, :created_at)
attribute :is_favorited
belongs_to :user, key: :lead
def is_favorited
if object.favorited_by.where(user_id: #current_user.id).present?
return true
else
return false
end
end
end
ProjectSerializer is located in my app/ project tree structure:
app/
serializers/
v1/
project_serializer.rb
I get an error trying to access #current_user:
NoMethodError in V1::UsersController#get_current_user
undefined method `id' for nil:NilClass
This happens when I'm calling the function from UserController, which then goes to UserSerializer and then that serializer has has_many :projects field which calls ProjectSerializer.
You can access the variables using instance_options. I believe you can access #current_user in projects controller. For example :
def projects
#projects = Project.all
render_json: #projects, serializer: ProjectSerializer, current_user: #current_user
end
Inside the serializer you can access the current_user like wise:
def is_favorited
if object.favorited_by.where(user_id: #instance_options[:current_user].id).present?
return true
else
return false
end
end
Related
I wanted use Service Object in my Rails API. Inside my Service Object I want to save Model and return true if saving was successful, but if saving was unsuccessful then I want to return false and send error messages. My Service Object looks like that:
class ModelSaver
include ActiveModel::Validations
def initialize(params)
#params = params
end
def save_model
model ||= Model.new(params)
return false unless model.valid?
model.save!
rescue ActiveRecord::RecordNotSaved, ActiveRecord::RecordInvalid
model.errors.add(:base, 'here I want default error message')
false
end
private
attr_reader :params
end
The problem is that I don't know how to send errors messages in response. When I try to send service_object.errors.messages it displays an empty array to me. It looks like that in Controller:
class ModelController < ApplicationController
...
def create
service_object = ModelSaver.new(params)
if service_object.save_model
render json: service_object
else
render json: { errors: service_object.errors.messages }, status: :unprocessable_entity
end
end
...
end
So how can I get Model errors from Service Object inside Controller?
You can solve this by providing methods to your service object that allow returning the model or the errors even after save_model returned with false.
I would change the service object to
class ModelSaver
include ActiveModel::Validations
attr_reader :model
def initialize(params)
#params = params
end
def errors
#model.errors
end
def save_model
#model ||= Model.new(params)
return false unless model.valid?
#model.save!
rescue ActiveRecord::RecordNotSaved, ActiveRecord::RecordInvalid
#model.errors.add(:base, 'here I want default error message')
false
end
private
attr_reader :params
end
and the controller method to
def create
service_object = ModelSaver.new(params)
if service_object.save_model
render json: service_object.model
else
render json: { errors: service_object.errors.messages }, status: :unprocessable_entity
end
end
I would refactor the service object so that it just delegates to the underlying model:
class ModelSaver
attr_reader :model
delegate :errors, to: :model
def initialize(**params)
#model = Model.new(**params)
end
def save_model
if model.save
true
else
model.errors.add(:base, 'you forgot to frobnobize the whatchamacallit')
false
end
end
end
class ModelController < ApplicationController
def create
service_object = ModelSaver.new(**params)
if service_object.save_model
render json: service_object
else
render json: { errors: service_object.errors.messages }, status: :unprocessable_entity
end
end
end
I have the following namespaces ApiController
class Api::ApiController < ApplicationController
skip_before_action :verify_authenticity_token,
if: Proc.new { |c| c.request.content_type == 'application/json' }
before_action :authenticate
attr_reader :current_user
private
def authenticate
#current_user = AuthorizeApiRequest.call(request.headers).result
render json: { error: 'Not Authorized' }, status: 401 unless #current_user
end
end
On AuthorizeApiRequest.call, Rails complains that:
uninitialized constant Api::ApiController::AuthorizeApiRequest
My AuthorizeApiRequest class is defined under app/commands:
class AuthorizeApiRequest
prepend SimpleCommand
def initialize(headers = {})
#headers = headers
end
def call
user
end
private
attr_reader :headers
def user
#user ||= User.find(decoded_auth_token[:user_id]) if decoded_auth_token
#user || errors.add(:token, 'Invalid token') && nil
end
def decoded_auth_token
#decoded_auth_token ||= JsonWebToken.decode(http_auth_header)
end
def http_auth_header
if headers['Authorization'].present?
return headers['Authorization'].split(' ').last
else
errors.add(:token, 'Missing token')
end
nil
end
end
So it seems to not allow me to call AuthorizeApiRequest.call without added namespace to front. How to fix?
Your app/commands folder doesn't seem to be loaded into Rails at boot.
You need to include your app/commands in your autoload paths for this to work or require the file manually in your controller.
See: https://guides.rubyonrails.org/autoloading_and_reloading_constants.html#autoload-paths
So I am working on creating a playercard, which is basically a profile page for a user. The issue I am having on the backend is my private method playercard_params is only returning user_id, and not all the information inputted into the form...although regular params shows all the data needed to create the playercard. I thought the issue might be on the frontend, but working my way backwards came to the conclusion the issue is here on the backend.
Here is my controller:
class Api::V1::PlayercardController < ApplicationController
before_action :set_user
def index
if params[:user_id]
#playercard = #user.playercard
else
#playercard = Playercard.all
end
render json: #playercard
end
def show
#playercard = Playercard.find(params[:id])
render json: #playercard
end
def create
#playercard = Playercard.new(playercard_params)
binding.pry
if #playercard.save
render json: #user
else
render json: {
error: #playercard.errors.full_messages.to_sentence
}
end
end
def update
#playercard = Playercard.find(params[:id])
if #playercard.update(playercard_params)
render json: #playercard
else
render json: {
error: #playercard.errors.full_messages.to_sentence
}
end
end
private
def playercard_params
params.require(:playercard).permit(:player_nickname, :player_height_in_feet, :player_height_in_inches, :player_weight, :player_age, :player_fav_player, :user_id)
end
def set_user
#user = User.find(params[:user_id])
end
end
My playercard model:
class Playercard < ApplicationRecord
belongs_to :user
validates :player_nickname, :player_height_in_feet, :player_height_in_inches, :player_weight, :player_age, :player_fav_player, presence: true
end
and the serializer if that helps:
class PlayercardSerializer < ActiveModel::Serializer
attributes :id, :player_nickname, :player_height_in_feet, :player_height_in_inches, :player_weight, :player_age, :player_fav_player
belongs_to :user
end
Here are my params:
<ActionController::Parameters {"playerNickname"=>"white mamba", "playerHeightFeet"=>"6", "playerHeightInches"=>"3", "playerAge"=>"30", "playerWeight"=>"170", "playerFavPlayer"=>"Kobe", "user_id"=>"1", "controller"=>"api/v1/playercard", "action"=>"create", "playercard"=><ActionController::Parameters {"user_id"=>1} permitted: false>} permitted: false>
When I submit the form on the front end, I get errors saying each field is empty...in the pry, if I type playercard_params, only user_id shows up (with the correct id)
I solved the issue by lining up the naming convention for the attributes with the front-end and back-end. And it worked!
Thank you #jvillian for the insight!!
This section of Pundit section says that we could control which attributes are authorized to be updated. But it fails in case of the use of active_model_seriallizers gem:
def post_params
# originally geneated by scaffold
#params.require(:post).permit(:title, :body, :user_id)
#To deserialize with active_model_serializers
ActiveModelSerializers::Deserialization.jsonapi_parse!(
params,
only: [:title, :body, :user]
)
end
If I modify the PostsController update action as Pundit suggested:
def update
if #post.update(permitted_attributes(#post))
render jsonapi: #post
else
render jsonapi: #post.errors, status: :unprocessable_entity
end
end
it fails with error:
ActionController::ParameterMissing (param is missing or the value is empty: post):
app/controllers/posts_controller.rb:29:in `update'
I also create the PostPolicy as follows:
class PostPolicy < ApplicationPolicy
def permitted_attributes
if user.admin? || user.national?
[:title, :body]
else
[:body]
end
end
end
but it has no impact on the above error.
Any idea on how can we do that?
The solution I came to (thanks to #max for some tips and tricks) is as follows:
Add the following line to config/application.rb:
config.action_controller.action_on_unpermitted_parameters = :raise
Add the rescue_from either to the AplicationController or the one you are precisely interested:
class ApplicationController < ActionController::API
include ActionController::MimeResponds
include Pundit
rescue_from Pundit::NotAuthorizedError, ActionController::UnpermittedParameters, with: :user_not_authorized
...
private
def user_not_authorized
render jsonapi: errors_response, status: :unathorized
end
def errors_response
{
errors:
[
{ message: 'You are not authorized to perform this action.' }
]
}
end
end
Then add pundit_params_for method to the PostsController and change the update action (in my case I'd like to restrict some attributes in update action only:)
class PostsController < ApplicationController
...
def update
if #post.update(permitted_attributes(#post))
render jsonapi: #post
else
render jsonapi: #post.errors, status: :unprocessable_entity
end
end
private
def post_params
ActiveModelSerializers::Deserialization.jsonapi_parse!(
params,
only: [:title, :body, :user]
)
end
def pundit_params_for(_record)
params.fetch(:data, {}).fetch(:attributes, {})
end
end
VoilĂ . Now if an unpermitted attribute will be submitted for the update action, the response will have 500 status and contain the error as specified in ApplicationController#errors_response method.
ATTENTION: It still fails if you have some relations posted with the request (for example, you can have an Author as belongs_to relation with Post). Using pundit_params_for as before will fail to extract the corresponding author_id value. To see the way, here my another post where I explained how to use it.
Hope this helps.
In my controller i have an action which does not have a corresponding view. Precisely: the upload action for uploading images. However, i require the current users id to store the image url. But the current_user method always returns nil, as the action by itself does not have a view. In such scenarios how do i fetch the current_user? I am using authlogic. My application_controller.rb contains the following:
class ApplicationController < ActionController::Base
helper :all
helper_method :current_user_session, :current_user
filter_parameter_logging :password, :password_confirmation
protect_from_forgery
def correct_safari_and_ie_accept_headers
ajax_request_types = [ 'application/json', 'text/javascript', 'text/xml']
request.accepts.sort!{ |x, y| ajax_request_types.include?(y.to_s) ? 1 : -1 } if request.xhr?
end
private
def set_cache_buster
response.headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate"
response.headers["Pragma"] = "no-cache"
response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT"
end
def current_user_session
return #current_user_session if defined?(#current_user_session)
#current_user_session = UserSession.find
end
def current_user
return #current_user if defined?(#current_user)
#current_user = current_user_session && current_user_session.record
end
end
EDIT: All other actions in the controller are able to access the current_user helper method. Only the upload action is not able to. Code:
Controller:
class ImageStacksController < ApplicationController
def upload
# Using swfupload.
image_stack_params = {
:caption => params[:caption],
:swf_uploaded_data => params[:link]
}
# current_user returns nil, even with user logged in!!
# Occurs only in the upload action and no other action in this controller.
logger.info("Current User: #{current_user}") #Returns nil
#image_stack = current_user.image_stacks.create! image_stack_params
respond_to do |format|
format.js { render :json => #image_stack }
end
end
def edit
logger.info("Current User: #{current_user}") #Returns current user
end
def new
logger.info("Current User: #{current_user}") #Returns current user
end
def update
logger.info("Current User: #{current_user}") #Returns current user
end
def destroy
logger.info("Current User: #{current_user}") #Returns current user
end
end
Model:
class ImageStack < ActiveRecord::Base
belongs_to :user, :class_name => "User", :foreign_key => "user_id"
upload_image_to_s3 :link
def swf_uploaded_data=(data)
data.content_type = MIME::Types.type_for(data.original_filename)
self.link = data
end
end
The controller method is really just that, a class method. It does not require a view. My making it a private method the method is not available outside the class or other classes inheriting from it and as such it is correctly not available to the view. Your problem suggests that your user is not logged in or something else is wrong. Do you have a require_user method?
#application_controller
private
def require_user
unless current_user
store_location
flash[:notice] = t(:must_be_logged_in)
redirect_to user_login_path
return false
end
end
def store_location
session[:return_to] = request.request_uri
end
#user.rb
has_many :images
#image.rb
belongs_to :user
# image_controller.rb
before_filter :require_user
def create
#photo = #item.images.new(:photo => params[:photo], :user => current_user)
Edit:
Your current_user method is a ApplicationController method which is already inherited:
ImageStacksController < ApplicationController
This:
helper_method :current_user_session, :current_user
is providing the methods to the view.
The difference between the upload action and all the others is update is being called by javascript. I remember doing a similar uploader and having to pass the authenticity token. Is anything else being reported in the log?
This might be of use to you: http://github.com/JimNeath/swfupload---paperclip-example-app
Making the authenticity token available to js goes something like this:
- content_for :head do
= javascript_tag "var AUTH_TOKEN = #{form_authenticity_token.inspect};" if protect_against_forgery?
Now you add the field to swflupload the same way you added current_user.