Rails - session request is nil error - ruby-on-rails

I have a method that I want to execute some search logic, and then save a Search object that has the searched string and user id of the person who did the search.
The search/save logic seems to be working fine otherwise, but when I try to get the current user (using a method from the application controller) it throws a runtime error that has to do with the session:
ActionController::Metal#session delegated to #_request.session, but #_request is nil: #<SearchController:0x1038e32e0 #action_has_layout=true, #view_context_class=nil, #_status=200, #_headers={"Content-Type"=>"text/html"}>
Here's the method in the search controller:
class SearchController < ApplicationController
...
def factualsearch(search)
if search
searchquery = Search.new
# this causes the error
if current_user
searchquery.user = current_user
end
searchquery.search_string = search
searchquery.save
...
end
#results
end
end
Here's the current_user method I'm trying to call from my application controller:
def current_user
return unless session[:user_id]
#current_user ||= User.find_by_id(session[:user_id])
end
helper_method :current_user
Here's the pages controller where I'm calling the method:
class PagesController < ApplicationController
...
def search
searchcontrol = SearchController.new
#results = searchcontrol.factualsearch(params[:search])
end
...
end

Not exactly a direct way to fix this problem, but I was able to get around it by have the function accept the user in addition to the search query, rather than trying to call the current_user method from inside it. The class that calls the action can access the current_user method just fine.

Related

Rails early return if before_action attribute set fails

I would like to perform a db lookup using the incoming request's remote_ip before any controller method is hit in order to set a particular controller class attribute. However if the lookup fails (if no object is linked to the request IP) I would like to immediately return a 404 response.
For example:
class Controller < ApplicationController
before_action :set_instance_or_404
def index
# do something with #instance
end
private
def set_instance_or_404
#instance = Model.find_by(ip_address: request.remote_ip)
# or if no instance is found, immediately return a 404 without hitting "index" method (or any other method for that matter)
end
end
Any help will be greatly appreciated!
You can raise an ActiveRecord::RecordNotFound exception, which will stop the action and return a 404. Or you can render or redirect which will also stop the action. See Rails Filters docs. Here are examples of each.
class Controller < ApplicationController
before_action :set_instance_or_404
def index
# do something with #instance
end
private
def set_instance_or_404
#instance = Model.find_by(ip_address: request.remote_ip)
raise ActiveRecord::RecordNotFound unless #instance # returns 404
end
end
class Controller < ApplicationController
before_action :set_instance_or_404
def index
# do something with #instance
end
private
def set_instance_or_404
#instance = Model.find_by(ip_address: request.remote_ip)
render(status: 404, inline: "Instance not found") unless #instance
end
end

Accessing current_user variable in another controller

I am trying to access Devise's current_user variable inside a new instance of another controller. Here is my definition of GetsInterfaceController
class GetsInterfaceController < ApplicationController
def select_current_signed_in_user
#signed_in_user_here = current_user
end
end
Then I instantiate a new instance of GetsInterfaceController in ClientsController
class ClientsController < ApplicationController
def get_current_user
#gets_interface_controller = GetsInterfaceController.new
find_signed_in_user = #gets_interface_controller.select_current_signed_in_user
end
end
But I get null error on the #signed_in_user_here = current_user line in GetsInterfaceController when I try this. Anyway to get to the current_user attribute from inside GetsInterfaceController ?
I solved this by moving my code into a Module in lib directory. Works like a charm
current_user is not a variable - it is a helper method. Thus it is already available in all your helpers and views.
Additionally you never instantiate controllers in Rails. The router does that for you.
The only public methods in your controllers should be the actions which respond to HTTP requests.
If you want to reuse a method in several controllers you should be using inheritance, modules (concerns) or helpers. Never by calling a method on another controller.
To call an external service you want to create an API client class:
# adapted from https://github.com/jnunemaker/httparty
require 'httparty'
class StackExchangeClient
include HTTParty
base_uri 'api.stackexchange.com'
def initialize(service, page, user = nil)
#user = user
#options = { query: {site: service, page: page} }
end
def questions
self.class.get("/2.2/questions", #options)
end
def users
self.class.get("/2.2/users", #options)
end
end
Or if you need to call an external service and for example create several models with the data a Service Object:
class SomeService
def initialize(user, client: SomeClient)
#user = user
#client = client # for mocking
end
def call
response = #client.get('/foo')
response.each do |d|
#user.baz << d[:woo]
end
end
end
SomeService.new(current_user).call

How to call methods in ApplicationController from other controllers?

I have protected function that gets and sets the current_user.
class ApplicationController < ActionController::Base
protected
def current_user
#current_user ||= User.find_by_id(session[:user_id])
end
def current_user=(user)
#current_user = user
session[:user_id] = user.nil? ? user : user.id
end
end
However, when I sign in, the current_user= method isn't called.
class UsersController < ApplicationController
def sign_in
if (#mailbox = Mailbox.find_by(email: params[:email], password: params[:password]))
current_user= #mailbox.user
redirect_to mailboxes_path
else
redirect_to :root
end
end
end
Also, the value of current_user is nil in other classes.
How do I make sure that the methods defined in ApplicationController are called?
The current_user method is an instance method, not a class method. Therefore, to access it, you need to invoke the method on the instance of the controller, which is self:
self.current_user = #mailbox.user
For further illustration, compare the following:
# This is a class method
def self.foo
return 'bar'
end
# This is an instance method
def foo
return 'bar'
end
The former method is on the class, while the latter is on an instance of the class. Therefore, they are respectively invoked in the following way:
# Invoking the class method
foo
#=> bar
# Invoking the instance method
self.foo
#=> bar
You're not calling your method by doing
current_user = #mailbox.user
Instead, you're setting a local variable called current_user. If you want to call your controller method you need to say:
self.current_user = #mailbox.user
Use helper_method:
helper_method :current_user, :current_user=
and remove current_user method from private.

Ruby. Variable permission

I have a problem to access to variable in other method in this class. Here is my example:
class CustomersController < ApplicationController
def login
if params[:login].present? && params[:password].present?
**#cust_model** = Customers.new
redirect_to(:action => 'client_dashboard')
end
end
def client_dashboard
#cust_dashboard = **#cust_model**.dashboard(1)
end
end
My error is: undefined method dashboard
After redirection from login, #cust_model will naturally be nil in client_dashboard action. It can't be inherited from login action like this, and that's why dashboard is undefined for that Customer
Since you have #cust_model = Customer.new, you can then sufficiently use #cust_dashboard = Customer.new.dashboard(1), if this is what you intended to (according to your given code).
Cheers.

Rails 3 devise, current_user is not accessible in a Model ?

in my project.rb model, I'm trying to create a scope with a dynamic variable:
scope :instanceprojects, lambda {
where("projects.instance_id = ?", current_user.instance_id)
}
I get the following error:
undefined local variable or method `current_user' for #<Class:0x102fe3af0>
Where in the controller I can access current_user.instance_id... Is there a reason the model can't access it and a way to get access? Also, is this the right place to create a scope like the above, or does that belong in the controller?
This doesn't make much sense, as you already pointed. The current_user doesn't belong to model logic at all, it should be handled on the controller level.
But you can still create scope like that, just pass the parameter to it from the controller:
scope :instanceprojects, lambda { |user|
where("projects.instance_id = ?", user.instance_id)
}
Now you can call it in the controller:
Model.instanceprojects(current_user)
The already accepted answer provides a really correct way to achieve this.
But here's the thread-safe version of User.current_user trick.
class User
class << self
def current_user=(user)
Thread.current[:current_user] = user
end
def current_user
Thread.current[:current_user]
end
end
end
class ApplicationController
before_filter :set_current_user
def set_current_user
User.current_user = current_user
end
end
This works as expected, however it can be considered dirty, because we basically define a global variable here.
Ryan Bates lays out a pretty safe way to implement this kind of strategy in this railscast
You can browse the source code here
Here he creates a current_tenant method, but you could easily substitute current_user instead.
Here are the key bits of code...
#application_controller.rb
around_filter :scope_current_tenant
private
def current_tenant
Tenant.find_by_subdomain! request.subdomain
end
helper_method :current_tenant
def scope_current_tenant
Tenant.current_id = current_tenant.id
yield
ensure
Tenant.current_id = nil
end
#models/tenant.rb
def self.current_id=(id)
Thread.current[:tenant_id] = id
end
def self.current_id
Thread.current[:tenant_id]
end
Then in the model you can do something like...
default_scope { where(tenant_id: Tenant.current_id) }
You don't need to use scopes. If you have set the appropriate associations in models, following piece of code placed in controller should do the trick:
#projects = current_user.instance.projects

Resources