default scoping confusion - ruby-on-rails

UPDATED:
I am setting default scope for some models in a runtime which seems working locally in my development env and my code is given below.
SET_OF_MODELS = [Event, Group, User]
#account = Account.find_by_subdomain(account_subdomain)
SET_OF_MODELS.each { |m| m.set_default_scope(#account.id) }
def set_default_scope(account_id)
default_scope :conditions=> { :account_id => account_id }
end
If I execute this code in ruby console with say #account1, User.first returns #account1 user whereas if I repeat the code with #account2 then User.first returns #account1 user instead of #account2. And this problem is not revealed while running app in local server but in staging server.
My guess is towards their states if they are really cached but not sure. Can someone explain in depth.
Thanks in advance

default_scope will save state in its class. It's harmful in concurrent environment because it leads to race condition. So you must isolate scope state between requests.
You can use around_filter
class ApplicationController < ActionController::Base
around_filter :set_default_scope
def set_default_scope
#account = Account.find_by_subdomain(account_subdomain)
opts = :condition => {:account_id => #account.id}
Event.send(:with_scope, opts) do
Group.send(:with_scope, opts) do
User.send(:with_scope, opts) do
yield
end
end
end
end
end
You can refactor .send(:with_scope, opts) to a class method like with_account_scope(account_id)

Development differs from production. In production all classes are loaded once and cached, so you can't redefine the default scopes on each request.
In development the classes are loaded on each request, to allow easy development: each change you do in the code is visible/active on the next request.
If you really want to, you can disable this behaviour in production. This will make your complete site slower, but maybe that is not really an issue. To turn this off, you have edit your config/environments/production.rb, find the line containing
config.cache_classes = true
and switch that to false.
Hope this helps.

There is nothing wrong with the above code but the problem was with the server used i.e. thin server. It worked perfectly after replacing thin with mongrel. I think thin wasn't allowing to execute set_default_scope more than once except after loading the application.

Related

Creating New Tenant Apartment Gem - Always restart?

I have a rails 4.2 multi-tenant app using the Apartment gem which has been awesome.
Each company has their own subdomain. I'm using a custom "elevator" which looks at the full request host to determine which "Tenant" should be loaded.
When I create a new company I have an after_create hook to create the new tenant with the proper request host.
This always seems to require a restart of the server both in development and production otherwise I get a Tenant Not Found error.
It's using sqlite in development and postgres in production.
Do I really have to restart the server each time I create a new tenant? Is there an automated way to do this? Maybe just reloading the initializer will work, but I'm not sure how to do that/if that's possible?
I have been messing around with this for a month and haven't been able to find a solution that works. Please help!
initializers/apartment.rb
require 'apartment/elevators/host_hash'
config.tenant_names = lambda { Company.pluck :request_host }
Rails.application.config.middleware.use 'Apartment::Elevators::HostHash', Company.full_hosts_hash
initializers/host_hash.rb
require 'apartment/elevators/generic'
module Apartment
module Elevators
class HostHash < Generic
def initialize(app, hash = {}, processor = nil)
super app, processor
#hash = hash
end
def parse_tenant_name(request)
if request.host.split('.').first == "www"
nil
else
raise TenantNotFound,
"Cannot find tenant for host #{request.host}" unless #hash.has_key?(request.host)
#hash[request.host]
end
end
end
end
end
Company Model
after_create :create_tenant
def self.full_hosts_hash
Company.all.inject(Hash.new) do |hash, company|
hash[company.request_host] = company.request_host
hash
end
end
private
def create_tenant
Apartment::Tenant.create(request_host)
end
What ended up working
I changed the elevator configuration to get away from the HostHash one that's in the apartment gem and used a completely custom one. Mostly based off of an issue on the apartment gem github: https://github.com/influitive/apartment/issues/280
initializers/apartment.rb
Rails.application.config.middleware.use 'BaseSite::BaseElevator'
app/middleware/base_site.rb
require 'apartment/elevators/generic'
module BaseSite
class BaseElevator < Apartment::Elevators::Generic
def parse_tenant_name(request)
company = Company.find_by_request_host(request.host)
return company.request_host unless company.nil?
fail StandardError, "No website found at #{request.host} not found"
end
end
end
I think the problem could be that your host_hash.rb lives in the initializers directory. Shouldn't it be in a folder called "middleware"?, as per the Apartment gem ReadME you referenced in your comment. In that example they used app/middleware/my_custom_elevator.rb. Perhaps yours might look like app/middleware/host_hash.rb?
Right now the file is in initializers, so it's loading from there. But your apartment.rb references it by Rails.application.config.middleware.use. Just a hunch but in addition to loading it initially, it may be looking for it in a nonexistent middleware folder. I'd go ahead and create app/middleware, put the file in there instead, and see what happens. Not sure but you might need to alter require paths too.
Let us know if that helps.

Initializing Gem-Specific variables information in Ruby On Rails

I am working right now on a Rails 4.0 application (using Ruby 2.0.0).
I would like to interact with Jenkins using jenkins_api_client gem, from multiple pages of my Rails application.
This gem generally using a #client parameter, which is initialized to contain the credentials and other information of the Jenkins server.
This parameter in initialized using something like this:
#client = JenkinsApi::Client.new(:server_ip => '0.0.0.0',
:username => 'somename', :password => 'secret password')
Once initialized, I would like to access this parameter and run multiple sub-routines on it.
This initialization takes time, and I really want to avoid doing this process every time one of the clients would like to use this gem functionality, such as:
# Get a filtered list of jobs from the server
jobs_to_filter = "^test_job.*"
jobs = #client.job.list(jobs_to_filter)
So, I hope to do this only once- when the rails server starts.
I would like to use this parameter from multiple pages of my app, possibly with threaded solution further down the road (not critical at the moment).
Can anyone recommend how to achieve this?
I'd appreciate an answer which is consistent with Rails convention.
Thanks a lot!
as example you could create something like that:
module JenkinsApi
class Client
class << self
attr_reader :instance, :config
def configure(&block)
#config = OpenStruct.new
block.call #config
end
def instance
#instance ||= JenkinsApi::Client.new #config
end
end
end
end
which allow you write in initializer:
JenkinsApi::Client.configure do |config|
config.server_ip = '0.0.0.0'
config.username = 'somename'
config.password = 'secret password'
end
and then use it like: JenkinsApi::Client.instance.job.list(...

Why ActiveRecord caches find(:all) between requests?

I have this in my controller:
def frontpage
#sections = Section.all
end
In production environment, the query seems to be cached between requests. Until I restart an app, #sections is always the same. Why and how do I fix this? I should also mention, I use Postgres in production, could it be the reason?
I believe that in your production environment, you have enabled caching somewhere. While I'm not able to tell you why and where, I can give you a solution:
def frontpage
uncached do
#sections = find(:all)
end
end
At least it should do the trick
e: The benefit of this, is that it only affects the code inside the block. Everything else should still get cached.
Queries are not cached between requests, but actions could be. Do you have cache_action or other caching mechanism defined in your controller?
EDIT: to figure out if it's your action that's being cached, put this in your action:
def frontpage
return render :text => Time.now.to_s
...
end
And see it it changes.
Have a look at this answer from a related question. If your configuration is set up to cache, then you should switch that off (in development at least.) Otherwise, if you want to invalidate the cache of a specific page, you can look into defining a method to do so in your controller:
private
def invalidate_cache
Rails.cache.delete("/sections")
end
You would need to specify when that is called, but it would do the trick.

In Rails, a Sweeper isn't getting called in a Model-only setup

I'm working on a Rails app, where I'm using page caching to store static html output. The caching works fine. I'm having trouble expiring the caches, though.
I believe my problem is, in part, because I'm not expiring the cache from my controller. All of the actions necessary for this are being handled within the model. This seems like it should be doable, but all of the references to Model-based cache expiration that I'm finding seem to be out of date, or are otherwise not working.
In my environment.rb file, I'm calling
config.load_paths += %W( #{RAILS_ROOT}/app/sweepers )
And I have, in the /sweepers folder, a LinkSweeper file:
class LinkSweeper < ActionController::Caching::Sweeper
observe Link
def after_update(link)
clear_links_cache(link)
end
def clear_links_cache(link)
# expire_page :controller => 'links', :action => 'show', :md5 => link.md5
expire_page '/l/'+ link.md5 + '.html'
end
end
So ... why isn't it deleting the cached page when I update the model? (Process: using script/console, I'm selecting items from the database and saving them, but their corresponding pages aren't deleting from the cache), and I'm also calling the specific method in the Link model that would normally invoke the sweeper. Neither works.
If it matters, the cached file is an md5 hash off a key value in the Links table. The cached page is getting stored as something like /l/45ed4aade64d427...99919cba2bd90f.html.
Essentially, it seems as though the Sweeper isn't actually observing the Link. I also read (here) that it might be possible to simply add the sweeper to config.active_record.observers in environment.rb, but that didn't seem to do it (and I wasn't sure if the load_path of app/sweepers in environment.rb obviated that).
So I've tried a number of different approaches, to see what works, and what doesn't.
Again, to summarize the situation: My goal is to expire cached pages when an object updates, but to expire them without relying on a Controller action. Conventional sweepers use a line in the controller to notify the sweeper that it needs to function. In this case, I can't use a line in the controller, as the update is happening within the model. Normal sweeper tutorials aren't working, as they presume that your main interaction with the database object is through the controller.
If, in reading this, you see a way to tighten up my code, please comment and let me know.
First, let's look at the things that DO work, in case you're stuck on this, too, and need help.
Of all the things I tried, the only thing that really seemed to work was to declare an after_update command in the Observer for the model. In that command, I used the explicit command for the expire_page action, and included a path that had been declared in routes.rb.
So. This works:
In config/routes.rb:
map.link 'l/:md5.:format', :controller => 'links', :action => 'show'
In app/models/link_observer.rb:
def after_update(link)
ActionController::Base.expire_page(app.link_path(:md5 => link.md5))
end
Note that that "md5" is specific to my app. You might want to use :id or some other unique identifier.
I also found that declaring that ActionController::Base... line from the method in the model that's doing the updating worked. That is, within Link.rb, in the method that's actually updating the database, if I just stuck that whole line in, it worked. But since I might want to expire that page cache on other methods in the future, I'd rather have it extracted into the Observer.
Now, let's look at some things that DID NOT work, in case you're Googling around for this.
Calling "expire_page(...)" within the after_update(link) method within link_observer.rb did not work, as it returned an "undefined method `expire_page'" error
Creating a Sweeper file that observed the Model did not work. I couldn't find any error codes, but it just seemed to not even be aware that it had a job to do. This was after explicitly calling "config.load_paths += %W( #{RAILS_ROOT}/app/sweepers )" within environment.rb. Just in case I fat-fingered something in that code, here it is:
class LinkSweeper < ActionController::Caching::Sweeper
observe Link
def after_update(link)
clear_links_cache(link)
end
def clear_links_cache(link)
# DID NOT WORK expire_page :controller => 'links', :action => 'show', :md5 => link.md5
# DID NOT WORK expire_page '/l/'+ link.md5 + '.html'
# DID NOT WORK ActionController::Base.expire_page(app.link_path(:md5 => link.md5))
end
end
That above example had the link_sweeper.rb file in a directory, /app/sweepers. I also tried putting link_sweeper.rb within the app/models directory, and tried calling it with the config.active_record.observers command in environment.rb:
config.active_record.observers = :link_observer, :link_sweeper
But that didn't work, either.
So, yeah. It's quite possible that one of these methods would work, and that I messed up something in the code. But I think I did everything by the book.
Ultimately, to summarize: Rather than using a Sweeper to expire page caching, you want to set up an after_ callback in the model's Observer. You'll want to use the explicit path to the Base.expire_page method:
def after_update(<model>) # where <model> is the name of the model you're observing
ActionController::Base.expire_page(app.<model>_path(:id => <model>.id)) # where <model> is the name of the model you're observing
end
Hopefully this will help someone else down the road. Again, if you see anywhere in my not-working code where I should have done something differently, please let me know. If you see something in my working code that can be tighter, please let me know that, too.
Just a note: you can use cache_sweeper in ApplicationController.
class ApplicationController < ActionController::Base
cache_sweeper :my_sweeper
end
class MySweeper < ActionController::Caching::Sweeper
observe MyModel
def after_update(my_model)
expire_page(...)
end
end
I was experiencing the same problem when trying to do fragment caching (rails 3). Couldn't get the sweeper to observe, so I settled for the solution to make it an AR Observer as described above and calling ApplicationController.new.expire_fragment(...).
I did get this working. The only slight difference in my setup is that the sweeper is part of a Rails engine; which accounts for slight differences (loading the sweeper file with a require in the engine's init instead of adding it to the load path in environment.rb, etc).
So, the sweeper is loaded in the init.rb of the engine like this:
require File.join(File.dirname(__FILE__), 'app', 'sweepers', cached_category_count_sweeper')
I called it a sweeper because it "sweeps" the cache, but I guess its just an observer on the model:
class CachedCategoryCountSweeper < ActiveRecord::Observer
observe CategoryFeature
def before_save(cf)
expire_cache(cf.category_id_was) if cf.category_id_changed?
end
def after_save(cf)
expire_cache(cf.category_id)
end
def after_destroy(cf)
expire_cache(cf.category_id)
end
def expire_cache(c)
ApplicationController.expire_page("/categories/#{c}/counts.xml") if !c.nil?
end
end
Frankly, I don't like having to hard-code the path, but I tried adding:
include ActionController:UrlWriter
and then using the path method, but it only worked for me in development. It didn't work in production, because my production server uses a relative url root (instead of virtual hosts) and the internal method "page_cache_path" would consistently get the file path wrong so it couldn't expire.
Since this is an observer, I added to the environment.rb:
config.active_record.observers = :cached_category_count_sweeper
Finally the controller that uses the cache (doesn't expire it, that is done through the model observer):
class CachedCategoryCountsController < ApplicationController
caches_page :index
# GET /cached_category_counts.xml
def index
...
end
end
Anyhow, hope this helps.
Andres Montano
I've been able to get it to work, by way of adding
ActionController::Base.expire_page(app.link_path(:md5 => #link.md5))
to the method in the Model itself that's updating the database. This feels somewhat hacky, though, and I'd love to know if anyone can explain why it's not working with the normal sweeper setup, and if there's a more elegant way to handle this.
That snippet of code (apart from customizations I put in for my own app) came from this post on ruby-forum.com.
I wrote a bit about this topic here: Rails Cache Sweeper Confusion. Would love to hear your opinions.
Based on #moiristo and #ZoogieZork 's answers, I am guessing this would work (untested).
class LinkSweeper < ActiveRecord::Observer
include ActionController::Caching::Pages
# or if you want to expire fragments
#include ActionController::Caching::Fragments
observe Link
def after_update(link)
expire_page( ... )
#expire_fragment( ... )
end
end

Change value of request.remote_ip in Ruby on Rails

For test purposes I want to change the return value of request.remote_ip. While being on my development machine it returns always 127.0.0.1 as it should but I would like to give myself different fake IPs to test the correct behavior of my app without deploying it to an live server first!
Thank you.
If you want this functionality in your whole application, it might be better/easier to override the remote_ip method in your app/helpers/application_helper.rb:
class ActionDispatch::Request #rails 2: ActionController::Request
def remote_ip
'1.2.3.4'
end
end
And the 1.2.3.4 address is available everywhere
For integration tests, this works with rails 5:
get "/path", params: { }, headers: { "REMOTE_ADDR" => "1.2.3.4" }
You can modify the request object using:
request = ActionController::Request.new('REMOTE_ADDR' => '1.2.3.4')
request.remote_ip now returns 1.2.3.4
You can cheat a bit by making a mutator for the remote_ip value in the test environment which is normally not defined.
For instance, alter the class inside of test/test_helper.rb with the following:
class ActionController::TestRequest
def remote_ip=(value)
#env['REMOTE_ADDR'] = value.to_s
end
end
Then, during your testing you can reassign as required:
def test_something
#request.remote_ip = '1.2.3.4'
end
This can be done either in the individual test, or within your setup routine, wherever is appropriate.
I have had to use this before when writing functional tests that verify IP banning, geolocation, etc.
rails 4.0.1 rc. After hour of searching found this simple solution while digging to code :)
get '/', {}, { 'REMOTE_ADDR' => '1.2.3.4' }
What I ended up doing now was to put this code in the end of the config/environments/development.rb file to make sure it's only executed while in development
# fake IP for manuel testing
class ActionController::Request
def remote_ip
"1.2.3.4"
end
end
So this sets remote_ip to 1.2.3.4 when the server starts. Everytime you change the value you have to restart the server!
This answer is only works for rails3 (I found this answer when trying to answer a similar question for rails 3),
So I will post it here in case if someone is trying to do the same thing in Rails3 env
class ActionDispatch::Request
def remote_ip
'1.2.3.4'
end
end
HTH

Resources