I'm trying to move some business logic out of one of my controllers, StoreController and into a new Store::CreateService service object. Learning about services recently, it doesn't seem to be much of an established pattern for implementing them. I'm running into an error trying to call a protected method. I can obviously move the logic from those protected methods directly into execute but my understanding was that this should be okay to Rails.
undefined local variable or method `find_and_set_account_id' for #<Store::CreateService:0x00007f832f8928f8>
Here is the Service object
module Store
class CreateService < BaseService
def initialize(user, params)
#current_user, #params = user, params.dup
end
def execute
#store = Store.new(params)
#store.creator = current_user
find_and_set_account_id
if #store.save
# Make sure that the user is allowed to use the specified visibility level
#store.members.create(
role: "owner",
user: current_user
)
end
after_create_actions if #store.persisted?
#store
end
end
protected
def after_create_actions
event_service.create_store(#store, current_user)
end
def find_and_set_account_id
loop do
#store.account_id = SecureRandom.random_number(10**7)
break unless Store.where(account_id: account_id).exists?
end
end
end
You have an extra end after def execute..end. That end closes the CreateService class. This means your protected methods are defined on the Store module.
Hence the missing method.
Related
In my Rails application I have a class that I want to initialize and then access it throughout my controllers. So the idea is that I set it via the application controller if it's not already been defined:
class ApplicationController < ActionController::Base
before_action :set_custom_class
# create an instance of customclass if doesn't exist
def set_custom_class
#custom_class ||= CustomClass.new
end
end
An example of the class:
class CustomClass
def initialize; end
def custom_method
#custom_method
end
def custom_method=(content)
#custom_method = content
end
end
If I then have a controller like:
class MyController < ApplicationController
def method_1
# set the custom_method value on my instance
#custom_class.custom_method('Some content')
# return the value I set above
#variable = #custom_class.custom_method
redirect_to :method_2
end
def method_2
# I should be able to retrieve the same value from that same instance
#variable = #custom_class.custom_method
end
end
What I'm finding is that when calling method_1 the #variable will return my content fine, but when calling method_2 AFTER method_1 (so the custom_method for the app wide #custom_class has been set) it's returning nil.
Why isn't the instance being retained? The #custom_class shouldn't be creating a new instance as it's already been set. So I can't understand why the value I have set gets lost when requesting it.
You witnessing such behaviour, because state of a controller is not preserved between requests. For example, imagine that current_user method sets #current_user for one request and returns the same user for another one.
Please, consider an option of using cookies or database for sharing state between requests.
Otherwise, a workaround would be setting a class variable of CustomClass, but I don't recommend to do it.
Looks like your before_action will re-instantiate the new object on every request. That means that since you aren't passing anything through to the class in Method2, it will come out as NULL.
Since you said app-wide, why not make it app-wide?
In config/application.rb,
module App
class Application < Rails::Application
def custom_class
#custom_class ||= CustomClass.new
end
end
end
in your application code,
Rails.application.custom_class
I'm trying to access to the current user outside of a controller and outside of a model. This is the architecture of the project
main_engine
|_bin
|_config
|_blorgh_engine
|_ —> this where devise is installed
|
|_ blorgh2_engine
|_app
|_assets
|_models
|_assets
|_queries
|_ filter_comments.rb -> Where I want to use current_user
module Blorgh2
# A class used to find comments for a commentable resource
class FilterComments < Rectify::Query
# How to get current_user here ?
...
end
end
I don't think there is a way to do it. If you have an idea, you are welcome.
If the engine is running in the same thread then perhaps you could store the current_user in the Thread.
class ApplicationController < ActionController::Base
around_action :store_current_user
def store_current_user
Thread.current[:current_user] = current_user
yield
ensure
Thread.current[:current_user] = nil
end
end
Then in your filter_comments.rb you can define a method
def current_user
Thread.current[:current_user]
end
The current_user variable is tied to the current request, and thus controller instance. In this case you should probably just parameterize your query with the user you want to filter for:
class FilterComments < Rectify::Query
def initialize(user)
#user = user
end
def query
# Query that can access user
end
end
Then, in your controller:
filtered_comments = FilterComments.new(current_user)
This makes it clear where it's coming from, allows you to reuse it with any user, and makes the query object testable, since you can just pass in any user in your test setup.
In my apps, I'm using variables that scoped to the thread currently executing. This is Rails 5 feature, and it really helping with such out of scope situations.
Idea in this blogpost.
Realisation based on Module#thread_mattr_accessor
Here example of code.
class AuthZoneController < ApplicationController
include Current
before_action :authenticate_user
around_action :set_current_user
private
def set_current_user
Current.user = current_user
yield
ensure
# to address the thread variable leak issues in Puma/Thin webserver
Current.user = nil
end
end
# /app/controllers/concerns/current.rb
module Current
thread_mattr_accessor :user
end
Now you can access Current.user in your current thread in all application scope.
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
I'm new with RoR and I have a controller (UsersController) where I wish to verify the existence of a certain session before anything. Since the session verification code is the same for several methods and I don't want to repeat myself, I decided to make a new method in my controller to check the sessions:
class UsersController < ApplicationController
def index
end
def show
end
def new
if self.has_register_session?
# Does something
else
# Does something else
end
end
def edit
end
def create
end
def update
end
def destroy
end
def self.has_register_session?
# true or false
end
end
And when I run the page /users/new, I got this error:
undefined method `has_register_session?' for #<UsersController:0x1036d9b48>
Any idea?
self when you define the method refers to the UsersController class object, but within the instance method new, self refers to the instance of UsersController.
You can either make your method an instance method:
def has_register_session?
# code
end
You can then get rid of the self when calling has_register_session? in new as well.
Or call the method on the class:
if UsersController.has_register_session?
# code
end
instead of referencing UsersController explicitly you could do self.class.
Note that you likely want the first solution: making has_register_session? an instance method.
By doing def self.blah you've created a class method whereas you want an instance method.
You might also want to make the method protected - all public methods are exposed as actions by default.
I'm using the facebooker gem which creates a variable called facebook_session in the controller scope (meaning when I can call facebook_session.user.name from the userscontroller section its okay). However when I'm rewriting the full_name function (located in my model) i can't access the facebook_session variable.
You'll have to pass the value into your model at some point, then store it if you need to access it regularly.
Models aren't allowed to pull data from controllers -- it would break things in console view, unit testing and in a few other situations.
The simplest answer is something like this:
class User
attr_accessor :facebook_name
before_create :update_full_name
def calculated_full_name
facebook_name || "not sure"
end
def update_full_name
full_name ||= calculated_full_name
end
end
class UsersController
def create
#user = User.new params[:user]
#user.facebook_name = facebook_session.user.name
#user.save
end
end