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.
Related
hi i got existing project within a model (let call at a) that call a model (let call at b) i need from the the last model a.k.a get the ip of the client the issue is i kinda bound with restrictions.
the way the model beed add if from include mean it not for reall passed from controller
one of the restrictions is make the few changes as possible
there for my question is simple let say i need get the ip not from the controller but from the model and it not save anywhere so what way i can get the ip.
p.s i did try request.remote_ip but it don't know request
if possible can u show me link or example code so i will understand how to do.
You can do the following for getting the remote ip of the client, this is using a controller.
class TestsController < ApplicationController
def get_ip
ip = request.remote_ip
Test.use_ip(ip)
end
end
Assuming you have a model. I'm assuming it as Test
class Test < ActiveRecord::Base
def self.use_ip(ip)
puts ip
end
end
As per your requirement, which is going against convention of Rails (which is not a very good practice)
define the following in application_controller.rb
before_filter :pass_request_around
def pass_request_around
Thread.current[:request] = request
end
In model, request object should be available now
def get_ip
request = Thread.current[:request]
ip = request.remote_ip
end
When using the Rails method, find_or_create_by, does it belong in the model or the controller? I am having a hard time understanding how to actually implement this method.
I want my rails application to accept JSON messages from users. The users will be sending data back to the server so it can be saved in the database. That being said, I would assume the user would have to use the 'POST' or 'PATCH method to store or update the data on my server. When I look at my routes the 'POST' method is used by the create action.
I have read the following Rails documentation but it didn't clarify anything to me. http://guides.rubyonrails.org/active_record_querying.html#find-or-create-by
Would I place the find_or_create_by method in my create action like so? Or does it belong somewhere else? It doesn't seem right in the create action...
class WifiNetworksController < ApplicationController
def create
#wifi_network = WifiNetwork.find_or_create_by(bssid: params[:bssid],
ssid: params[:ssid],
channel: params[:channel], etc...)
end
end
Ultimately I want:
Users to save new networks via JSON if it doesn't exist
Users to update existing networks via JSON if certain attributes have improved (like signal strength)
Thank you for your time!
Final Update - Thanks for the great advice everyone! I had to take a bit of everybody's advice to get it to work! Below is what I ended up doing.. Seems to work well with no errors.
def create
respond_to do |format|
if #wifi_network = WifiNetwork.find_by(bssid: wifi_network_params[:bssid])
# Logic for checking whether to update the record or not
#wifi_network.update_attributes(wifi_network_params) if #wifi_network.rssi < params[:rssi]
format.json { render :nothing => true }
else
# Must be a new wifi network, create it
#wifi_network = WifiNetwork.create(wifi_network_params)
format.json { render :nothing => true }
end
end
end
If you use strong params you can do this in your controller:
def create
#wifi_network = WifiNetwork.find_or_create_by(bssid: wifi_network_params[:bssid])
#wifi_network.update_attributes(wifi_network_params)
end
Then when a user makes a curl like:
curl -X POST localhost:3000/wifi_networks -d "wifi_network[bssid]=bssid1&wifi_network[ssid]=ssid1&wifi_network[channel]=channel1"
your create action will look up a WifiNetwork by it's bssid and update the ssid and channel attributes, or if it doesn't exist it will create a WifiNetwork with the bssid param and then update the newly created record with the rest of the atts. Be careful because if the wifi_network_params for the other attrs are empty they will update the params to nil.
I think it may be good to take a step back and really think about the interface of your application. Is there any particular reason why you need to use find_or_create_by and do everything in one controller action?
Why not simplify things and adhere to REST by having separate 'create' and 'update' actions on your WifiNetworksController:
class WifiNetworksController < ApplicationController
def create
#wifi_network = WifiNetwork.new(wifi_network_params)
if #wifi_network.save
# success response
else
# failure response
end
end
def update
# params[:id] won't work here if the client sending the request doesn't know the id of the
# wifi network, so replace it with the attribute you expect to be able to
# uniquely identify a WifiNetwork with.
if #wifi_network = WifiNetwork.find(params[:id])
# Logic for deciding whether to update or not
#wifi_network.update_attributes(wifi_network_params) if #wifi_network.signal_strength < params[:signal_strength]
else
# wifi_network not found, respond accordingly
end
end
private
# strong_parameters for Rails 4
def wifi_network_params
params.require(:wifi_network).permit(:ssid, :channel,...)
end
end
You could then have validations on your WifiNetwork model to ensure that certain attributes are unique, in order to avoid duplicates.
Or, if you really wanted to, you could combine both create and update into a single action, but create probably isn't the best name semantically.
EDIT: After your comment gave some background info, there probably isn't any benefit to using find_or_create_by, since you won't be able to tell if the record returned was 'created' or 'retrieved', which would allow you to avoid redundant update operations on it.
Assuming the bssid attribute is always a unique parameter:
def create
if #wifi_network = WifiNetwork.find(params[:bssid])
# Logic for checking whether to update the record or not
#wifi_network.update_attributes(wifi_network_params) if #wifi_network.signal_strength < params[:signal_strength]
else
# Must be a new wifi network, create it
#wifi_network = WifiNetwork.create(wifi_network_params)
end
end
I have to write a threaded Rails app because I am running it atop of Neo4j.rb, which embeds a Neo4j graph database inside the Rails process, and thus I have to serve multiple requests from the same process. Yeah, it'd be cool if connecting to a Neo4j database worked like SQL databases, but it doesn't, so I'll quit complaining and just use it.
I'm quite worried about the implications of writing concurrent code (as I should be), and just need some advice on how to handle common a common scenario - a controller sets an instance variable or a variable in the session hash, then some stuff happens. Consider the following crude code to demonstrate what I mean:
# THIS IS NOT REAL PRODUCTION CODE
# I don't do this in real life, it is just to help me ask my question, I
# know about one-way hashing, etc.!
class SessionsController
def create
user = User.find_by_email_and_password(params[:email], params[:password])
raise 'auth error' unless user
session[:current_user_id] = user.id
redirect_to :controller => 'current_user', :action => 'show'
end
end
class CurrentUserController
def show
#current_user = User.find(session[:current_user_id])
render :action => :show # .html.erb file that uses #current_user
end
end
The question: Are there any race conditions in this code?
In SessionsController, are the session hash and the params hash thread-local? Say the same browser session makes multiple requests to /sessions#create (to borrow Rails route syntax) with different credentials, the user that is logged in should be the request that hit the line session[:current_user_id] = user.id last? Or should I wrap a mutex lock around the controller action?
In the CurrentUserController, if the show action is hit simultaneously by two requests with different sessions, will the same #current_user variable be set by both? I.e. will the first request, as it is processing the .html.erb file, find that it's #current_user instance variable has suddenly been changed by the second thread?
Thanks
Each request gets a new instance of your controller. As a consequence controller instance variables are thread safe. params and session are also backed by controller instance variables (or the request object itself) and so are also safe.
It's important to know what is shared between threads and what isn't.
Now back to your specific example. Two requests hit CurrentUserController#show simultaneously, hence they are handled by two concurrent threads. The key here is that each thread has its own instance of CurrentUserController, so there are two #current_user variables which don't interfere. So there's no race condition around #current_user.
An example of race condition would be this:
class ApplicationController < ActionController::Base
before_each :set_current_user
cattr_accessor :current_user
def set_current_user
self.class.current_user = User.find_by_id(session[:current_user_id])
end
end
# model
class LogMessage < ActiveRecord::Base
belongs_to :user
def self.log_action(attrs)
log_message = new(attrs)
log_message.user = ApplicationController.current_user
log_message.save
end
end
On more general note, because of GIL (Global Interpreter Lock) benefits from using threads in MRI ruby are rather limited. There are implementation which are free from GIL (jruby).
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.
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.