I have a class variable that I initialize, that later magically becomes uninitialized by itself... When developing locally on rails' default server (WEBrick), there is no problem. This only happens on nginx on ec2. Here's some code..
def TestController < ApplicationController
##classVariable = ""
def index
##classVariable = "What's up homie"
log(##classVariable)
end
def callThisMethodViaAJAXFromJavascript
log("reached this method")
log(##classVariable)
end
def log(str)
File.write("aValidPath", str)
end
end
Here's what I do: When I load the page on test/index, the index method executes, and properly logs ##classVariable as:
"What's up homie"
But when I call the method callThisMethodViaAJAXFromJavascript via AJAX from the frontend, my log file looks like:
"reached this method"
""
Again, this ONLY occurs on nginx on, ec2 (OS is ubuntu). When I run locally on WEBrick, this NEVER happens.
Any ideas? Thank you very much.
You don't say how you're using nginx (as a reverse proxy to some unicorn instances, with passenger etc) but either way you would typically have multiple instances of your application. Each one is a separate process so setting a class variable in one process has no effect on the other process.
Nginx will balance requests between the rails instances - so the index page is served by one instance and the ajax action will frequently be served by another process where the clas variable is still the empty string.
In development with webrick there is only one rails instance so you don't encounter this problem. I'm not sure what you were trying to do but class variables are not a good way to preserve state across requests
Per #Frederick's answer, I used the rails session instead to store the variable.
As a rule of thumb, it seems like you should NOT use global variables nor class variables in rails if your variables are changing.
The values stored in the session object are the same from any process in rails. Though the pointer to the session may be different, the values will remain the same. Here's another post about sessions that goes more into detail:
How is rails session shared among unicorn workers?
Related
My app uses global variables like logos, app name, etc retrieved from the database and shown on different controllers and views. I put it in ApplicationController to be available to all, but I find that the individual controllers repeat the same query sometimes.
class ApplicationController < ActionController::Base
$image = Setting.find_by_name('image').value
$city = Setting.find_by_name('city').value
$currency = Setting.find_by_name('currency').value
end
Is there a way to make the same variables available to all controllers (and users) with just a one-time query with the variables saved on memory, such as when the app starts up?
You could attempt to use initializers.
Rail will load all files in config/initializers/ folder when the server starts up. This can work as a place to initialize application-wide variables. We could create a file inventory.rb file in the initializers directory:
at config/initializers/inventory.rb
module Inventory
class Count
Orders = Order.all
end
end
Inventory::Count::Orders
# => "[your orders will show here]"
These will only be loaded once when the server is started or restarted. As such this works well if the values you need won't change. If they will change I don't think there is a good way to avoid running multiple queries.
Whats about caching? Rails is using SQL Caching and you can use Low Level Caching. See the guides (1.7 and 1.8): https://guides.rubyonrails.org/caching_with_rails.html#low-level-caching
I am using the Apartment gem to switch the tenant (database) being used for a multi tenancy Rails application.
In my server logs I would like to output the current tenant (database) being used for every single line in the log file.
When I do rails s the server never actually starts with the code below that is in the initializers directory. The server just hangs... so odd. No error message and no running server. If I take out #{Apartment::Tenant.current} below everything is fine... but... I really want to know the current tenant (database) in my log files.
/initializers/log_formatting.rb:
class ActiveSupport::Logger::SimpleFormatter
def call(severity, time, progname, msg)
"#{Apartment::Tenant.current} #{msg.strip} (pid:#{$$})\n"
end
end
Any ideas on how to get the current tenant (database) being used output to every line of my log file?
Thank you!
I would suggest you to use log_tags.
From the rails documentation :
config.log_tags accepts a list of: methods that the request object responds to, a Proc that accepts the request object, or something that responds to to_s. This makes it easy to tag log lines with debug information like subdomain and request id - both very helpful in debugging multi-user production applications.
You can add this configuration in application.rb or production.rb whichever fits your need.
For ex: config.log_tags = [ :subdomain, :request_id, lambda { |request| request.headers["tenant_name"] } ]
Note: In case you are adding this for development environment and you are running on your_subdomain.localhost:3000 then subdomain won't be present as localhost doesn't support subdomains. There are some workarounds like modifying /etc/hosts file but i won't recommend it. The more cleaner solution is to use your_subdomain.lvh.me:3000
I am using the Apartment gem for a multi tenant Rails 5.2 app. I'm not sure that this even matters for my question but just giving some context.
Is there a way to override the Rails logger and redirect every single log entry to a file based on the tenant (database) that is being used?
Thinking... is there a method I can monkeypatch in Logger that will change the file written to dynamically?
Example: I want every error message directed to a file for that day. So at the end of a week there will be 7 dynamically generated files for errors that occurred on each specific day.
Another example: Before you write any server log message check if it is before 1pm. If it is before 1pm write it to /log/before_1.log ... if it is after 1pm write it to /log/after_1.log
Silly examples... but I want that kind of dynamic control before any line of log is written.
Thank you!
Usually the logger is usually configured per server (or per environment really) while apartment sets tenants per request - which means that in practice its not really going to work that well.
You can set the logger at any point by assigning Rails.logger to a logger instance.
Rails.logger = Logger.new(Rails.root.join('log/foo.log'), File::APPEND)
# or for multiple loggers
Rails.logger.extend(Logger.new(Rails.root.join('log/foo.log'), File::APPEND))
However its not that simple - you can't just throw that into ApplicationController and think that everything is hunky-dory - it will be called way to late and most of the entries with important stuff like the request or any errors that pop up before the controller will end up in the default log.
What you could do is write a custom piece of middleware that switches out the log:
# app/middleware/tenant_logger.rb
class TenantLogger
def initialize app
#app = app
end
def call(env)
file_name = "#{Appartment::Tenant.current}.log"
Rails.logger = Logger.new(Rails.root.join('log', file_name), File::APPEND)
#app.call(env)
end
end
And mount it after the "elevator" in the middleware stack:
Rails.application.config.middleware.insert_after Apartment::Elevators::Subdomain, TenantLogger
However as this is pretty far down in the middleware stack you will still miss quite a lot of important info logged by the middleware such as Rails::Rack::Logger.
Using the tagged logger as suggested by the Rails guides with a single file is a much better solution.
We are having a problem with the Rails ActionController::Base.view_paths. We are running a multi-tenant application and allow different themes for each tenant. now we have the problem that the theme view_paths dont reset on every request but stack up whenever we use prepend_view_path in the controller.
#paths=
[
...
#path="/app/themes/theme2/views",
...
#path="/app/themes/theme1/views",
]
we are using
ActionController::Base.prepend_view_path "app/themes/#{Theme.current}/views/"
in the controller.
Do you have an idea how we can force rails to generate view_paths new on every request?
We found the problem.
We used ApplicationController.renderer in a service class (outside of the controller). Manipulating the ApplicationController.view_paths outside the ApplicationController forced Rails to carry the view_path over to the next request.
The infrastructure I'm working with originally handled the URLs the way the Site Prism documentation suggested: the domain was handled by setting Capybara.app_host, and the pages themselves called set_url with a relative path.
It turns out that some pages on the production site have a subdomain, like members.mycompany.com rather than www.mycompany.com. The tests also need to be able to run on test environments with different domains entirely, and in the test environments there are no subdomains at all, even for those pages that do have the subdomain in the production site. So putting the full URL into each SitePrism::Page is not an option.
I believed that the best option was to stop using Capybara.app_host entirely, and create a URL builder that would use the state of the environment (i.e. :test or :production), the relative path to the page, and a flag indicating whether or not the page was part of the production subdomain. Something like:
def get_url(relative_path, subdomain_flag=false)
case Environment.get_environment
when :test
domain = "https://testmachine.localenvironment.com"
when :production
if subdomain_flag
domain = "https://members.mycompany.com"
else
domain = "https://www.mycompany.com"
end
end
return "#{domain}" + "#{relative_path}"
end
I had hoped that I could use get_url in each of my SitePrism::Page objects to generate the full URL without the need for Capybara.app_host, like so:
class LoginPage < SitePrism::Page
set_url get_url("/login")
...
end
class MemberPage < SitePrism::Page
set_url get_url("/mypage", true)
...
end
If the Environment were :test, I'd expect the LoginPage to load https://testmachine.localenvironment.com/login and MemberPage to load https://testmachine.localenvironment.com/mypage. If the Environment were :production, I'd expect LoginPage to load https://www.mycompany.com/login and MemberPage to load https://members.mycompany.com/mypage.
I believe the code in my get_url method does what I think it should do, and I also believe that the Environment object I created is having its state set to the proper environment prior to the pages' #load method being called in the test. Sadly, when I try to load my SitePrism::Page objects, the environment value that gets picked up by get_url is always the default value from the Environment object (which is :test). As a result, it ends up loading the test environment URL even if I'm trying to execute against production.
It feels like the set_url part of each SitePrism::Page object is evaluated before the SitePrism::Page objects are instantiated, and I can't seem to find a way to get my initialization of the Environment to happen prior to it. I'm new to Ruby so I'm hoping I'm just misunderstanding the order or hierarchy that these things are loading in, and that it's not difficult to correct. Can anyone identify what's wrong with my attempt, or, if there is a better way to handle this situation, point me to what that better way is?
Yes, those set_url calls are being evaluated when the class is being defined, in other words, when your file is first required.
The simple solution is to make sure that your Environment configuration is initialised before the page objects are required.
Note that the SitePrism docs say that set_url is only needed when navigating directly to a page:
Note that setting a URL is optional - you only need to set a url if
you want to be able to navigate directly to that page. It makes sense
to set the URL for a page model of a home page or a login page, but
probably not a search results page.
For verifying that you're on a page, you can use a regular expression instead. See https://github.com/natritmeyer/site_prism#verifying-that-a-particular-page-is-displayed for an example of running in multiple environments.