Accessing application variables from a Rails plugin - ruby-on-rails

I have a variable that I need globally available throughout my app (so I've set #account in the applicationController).
However, a plugin that I have needs access to the same variable.
Note: This variable is distinct on each request.
What is the best way of creating this architecture?

Maybe something like this will work:
class Account
def self.current
#current
# or: Thread.current[:current_account]
end
def self.current=(a)
#current = a
# or: Thread.current[:current_account] = a # ..if you want to be thread-safe.
end
...
end
# your controller's before_filter:
def assign_account
...
Account.current = #account # But remember to set nil if none found!
end
# Any code in your app (view, model, almost anything):
<%= Account.current.name if Account.current %>

Setting #account in your app controller doesn't make it globally available throughout the app - models can't access it for example. Any instance var set in the controller will be available only in the controller or views. If the plugins have controller and view code then this code should be able to access the variable in the normal way, as long as the variable is set before the plugin controller code runs for example.
If you provide more details about what you want to do (ie where/how you want to access #account) then someone may be able to suggest a good approach.

Related

How to test Application_Controller that creates instance variables

My application controller looks like:
class ApplicationController
before_action :set_customer
def customer?
#customer.nil?
end
private
def set_customer
if request.host != "example.com"
#customer = Customer.find_by_domain("...")
else
if request.subdomain != "www"
#customer = Customer.find_by_sub("...")
end
end
end
end
How can I make tests for the ApplicationController?
I want to pass in URLS and then check if #customer is nil etc.
e.g URLS to test
example.com
www.example.com
google.com
hello.example.com
You shouldn't test instance variables. Move the code to a service object if needed.
assigns is going to be deprecated in Rails 5, so will be probably removed at some point. In controller tests, you want to check that the response is what you expect (response code) based on your user (authentication) and authorization, and possibly that the correct format is used.
In a different test (a "view" test, I mean the integration tests) you check also that the output in the view contains some value based on your instance variable.
In this case you can simply test that the service calls Customer.find_by_sub (or by_domain) based on your clause, and that's all you need to test, instance variable shouldn't be something you care about.

Preferred way to use sessions to avoid hitting the database in rails

I try to optimise a Rails app with big load that currently hit the databsae on every request. I try now to optimise this by saving some info on the session so I don't need to go the database every time. I'm currently doing something like this.
def set_audience
if current_user
session[:audience] ||= current_user.audience
else
session[:audience] ||= 'general'
end
end
And then calling session[:audience] anywhere on my controller and views. Seems fine except that I'm seeing on the New Relic logs that sometimes the session is not set and therefore the app get a nasty nil.
Im thinking better I should use instance variables, maybe more something like this.
def set_audience
if current_user
session[:audience] ||= current_user.audience
end
#audience = session[:audience]
#audience = 'general' if #audience.empty?
end
And then calling #audience in my app.
Is this correct? I would like to make sure I'm used the preferred approach to this.
I think the standard approach here would be to use a helper method on ApplicationContoller:
class ApplicationController < ActionController::Base
private
def current_audience
#current_audience ||= current_user.audience
end
helper_method :current_audience
end
This will work pretty much exactly like the current_user helper method in your controllers and views. Depending on the specifics of your application, you may want to add some more robust nil handling, but this is the basic idea.

Scoping authorization to current devise user?

My Rails 3.2 project has a devise-generated user and a set of models that all contain data that's specific to that user. I want a logged-in user to be able to access only his own data through the APIs exposed by the controllers.
Now, a brute-force way to enable this would be to change each and every controller from something like:
def index
#stuff = Stuff.all
to
def index
#stuff = Stuff.find_all_by_user_id current_user.id
And I have to repeat this for every single action of every single controller. Is there perhaps a more succinct and DRY way of achieving the same effect? The amount of boilerplate I have to write feels wrong.
Thanks!
Take a look at the CanCan gem.
a) You can have a before callback in application_controller.rb that looks something like
def find_stuff_from_current_user
#stuff = Stuff.find_all_by_user_id current_user.id
end
And than call this in every controller like this:
before_filter :find_stuff_from_current_user
Now you have #stuff variable available in every controller and in every action.
b) Or you can use scoping in stuff model.rb where you say something like:
scope :stuff_from_current_user, where(:user => current_user)

Why are my thread variables intermittent in Rails?

I have the following in my application controller:
before_filter :set_current_subdomain
protected
def set_current_subdomain
Thread.current[:current_subdomain] = current_subdomain
#account = Account.find_by_subdomain(current_subdomain)
end
def current_subdomain
request.subdomain
end
and then the following in some of my models:
default_scope :conditions => { :account_id => (Thread.current[:account].id unless Thread.current[:account].nil?) }
Now, this works - some of the time. I for instance load up an index method and get back a list of records with the scope applied, but also sometimes get an empty list as Thread.current[:account_id] is coming out as nil, even though queries earlier in the request are working using the same value.
Question is, why is this not working, and is there a better way to set a variable that's global to the current request?
Manipulating the Thread local variables is a really bad idea and is going to lead to nothing but sadness, heartache, and pain. There's no guarantee that different parts of the request processing will be handled by the same thread, and because of this, your variables might end up getting lost.
The Rails convention is to create instance variables in the context of ApplicationController. In simple terms, all you really do is this:
class ApplicationController < ActionController::Base
before_filter :set_current_subdomain
attr_reader :current_subdomain
helper_method :current_subdomain
protected
def set_current_subdomain
#current_subdomain = request.subdomain
#account = Account.find_by_subdomain(#current_subdomain)
end
end
Any #... type variables you create will be attached to the instance of the ApplicationController associated with the current request. It's important to note that each request will be issued a brand-new instance of the appropriate controller class.
You're free to create whatever instance variables you want provided they don't somehow conflict with those used by Rails itself but in general terms this doesn't happen very often and conflicts typically occur on method names instead.
Class-level instance variables will persist between requests in environments where the "cache classes" flag is enabled. In the development environment your controller class is re-loaded each time a request is made to ensure it reflects the current state of your source files.

What's the correct way to run one controller action from another controller action without an HTTP redirect?

I'd like to be able to dispatch from one controller action to another conditionally, based on a combination of query parameters and data in the database.
What I have right now is something like:
class OldController < ApplicationController
def old_controller_action
if should_use_new_controller
new_params = params.dup
new_params[:controller] = "new_controller_action"
redirect_to new_params
return
end
# rest of old and busted
end
end
class NewController < ApplicationController
def new_controller_action
# new hotness
end
end
This works just fine, but it issues an HTTP redirect, which is slow. I'd like to be able to do this same thing, but within the same HTTP request.
Is there a clean way to do this?
Edit: The bounty will go to someone who can show me a clean way to do this that leaves the controllers and their actions relatively untouched (other than the redirect code itself).
Instead of calling code across actions, extract the code to lib/ or something, and call that code from both controllers.
# lib/foo.rb
module Foo
def self.bar
# ...
end
end
# posts_controller
def index
Foo.bar
end
# things_controller
def index
Foo.bar
end
Create an instance of the controller class:
#my_other_controller = MyOtherController.new
Then call methods on it:
#my_other_controller.some_method(params[:id])
I prefer the module idea, but this should do the trick.
You can also pass parameters as a whole from another controller:
#my_other_controller.params = params
I suspect you want option 3, but lets go through the some alternatives first
Option 1 - Push the controller selection logic into a helper that inserts the right link into your view. Benifits - controllers remain clean, Cons - if decision logic depending on submitted values this approach won't work. If URL is being called by external websites then this won't work.
Option 2 - Push the logic back into your model. Pro's - keeps controller clean. Cons - doesn't work well if you've got lots of sesson, params or render / redirect_to interaction.
Option 3 - Stay within the same controller. I suspect you are trying to replace some existing functionality with some new functionality, but only in some cases. Pro's - Simple and have access to everything you need. Cons - only works if it makes sense to use the same controller i.e. you're working with the same entity such as user, place or company.
Lets look an an example for option 3. My links controller has totally diferent behavour for admins than other users ...
class LinksController < ApplicationController
#...
def new
#Check params and db values to make a choice here
admin? ? new_admin : new_user
end
#...
private
def new_admin
#All of the good stuff - can use params, flash, etc
render :action => 'new_admin'
end
def new_user
#All of the good stuff - can use params, flash, etc
render :action => 'new_user'
end
end
If two controllers are trying to do the same thing, there's a very good chance this should be in a model. Take a good look at your design and -- I'm sorry I don't know your experience level with MVC -- read up on thin controller techniques:
http://weblog.jamisbuck.org/2006/10/18/skinny-controller-fat-model
http://www.robbyonrails.com/articles/2007/06/19/put-your-controllers-on-a-diet-already
http://andrzejonsoftware.blogspot.com/2008/07/mvc-how-to-write-controllers.html
If the problem is that you need the other controller to do the render, then maybe the route should have pointed there to begin with, and still the skinny controller technique should save the day.
If extracting the common code between controllers into a module doesn't work for you, I would use Rack middleware. I haven't seen code that uses ActiveRecord within middleware but I don't know of any reason why it shouldn't be possible since people have used Redis and the like.
Otherwise I think your only option would be to restart processing of the request with something like (untested, pseudo example):
env['REQUEST_URI'] = new_controller_uri_with_your_params
call(env)
This is similar to how integration tests are implemented. But I don't know if everything from call until you hit a controller is idempotent and safe to rerun like this. You could trace through the source and see. But even if it's ok now, it might break in any future version of rails or rack.
Using middleware would avoid this by letting you intercept the request before it's been run. You should still be able to share code with your rails application by extracting it out into common modules included in both places.
Honestly I think just doing the simple thing of factoring the common controller code is likely cleaner, but it's hard to know without the details of your situation so I thought I'd go ahead and suggest this.
Do this:
class OldController < ApplicationController
def old_controller_action
if should_use_new_controller
new_controller_action
end
# rest of old and busted
end
end
and the new controller
class NewController < OldController
def new_controller_action
# new hotness
end
end

Resources