Caching bunch of simple queries in rails - ruby-on-rails

In my app there're objects, and they belong to countries, regions, cities, types, groups, companies and other sets. Every set is rather simple - it has id, name and sometimes some pointers to other sets, and it never changes. Some sets are small and I load them in before_filter like that:
#countries = Country.all
#regions = Region.all
But then I call, for example,
offer.country.name
or
region.country.name
and my app performs a separate db query-by-id, although I've already loaded them all. After that I perform query through :include, and this case ids, generated by eager loading, do not depend on either I've already loaded this data with another query-by-id or not.
So I want some cache. For example, I may generate hashes with keys as records-ids in my before_filter and then call #countries[offer.country_id].name. This case it seems I don't need eager loading and it's easy turn on Rails.cache here. But maybe there's some smart built-in rails solution that does not require to rewrite everything?

Caching lists of models like that won't cache individual instances of that exist in other model's associations.
The Rails team has worked on implementing Identity Maps in Rails 3.1 to solve this exact problem, but it is disabled by default for now. You can enable it and see if it works for your problem.

Related

How to handle reused object in rails application

Note: note sure if this is the best title for the question / open to suggestions to edit for future value *
I have a multi-tenant rails application, which allows clients to use their own custom TLDs. So I can have:
www.clientA.com
www.clientB.com
www.clientC.com
etc....
For better or worse, my database (postgres) has a tenants table, which has approximately 60 columns with various settings & configurations for each tenant. Some are simply flags, and some are large text values.
In application_controller.rb I have some logic to parse the URL, query the tenants table based on the domain and instantiate a #current_tenant object. That #current_tenant object is available throughout the lifecycle of the page. This happens on every. single. page. of my app.
While I have some heavy caching in place, this design still feels very wrong to me, and I am sure it can be improved.
Is there a best practice for this? What is the best pattern to handle the #current_tenant object? I am concerned about memory management.
your current_tenant is no different from current_user that is ubiquitous in Rails apps.
stop microoptimizing. unless you benchmarked it and it shows to be a major bottleneck (which it is not, I can assure you).
any minuscule performance improvement (if at all) will be offset by the increased complexity of code, caching problems and what not.
do. not. do. that. ;)
one thing though, do NOT do a before_filter that assigns #current_tenant, instead add a current_tenant method to the application_controller (or one of its concerns) that will cache the result for the remainder of the request:
def current_tenant
#current_tenant ||= ....
end
You could also cache the #current_tenant object using Rails cache, ie:
def current_tenant( host = request.host )
Rails.cache.fetch(host, expires_in: 5.minutes) do
Tenant.find_by(tld: host)
end
end
(Note: I haven't touched Rails for 3 years, so take this answer with the appropriate fistful of salt.)
Is there a best practice for this? What is the best pattern to handle the #current_tenant object?
The best way I've seen this kind of stuff implemented is in PHP, rather than Ruby on Rails. More specifically, in the Symfony framework.
In a nutshell, a Symfony app's layout is like so:
/app <-- app-specific Kernel, Console, config, cache, logs, etc.
/src <-- app-specific source files
/vendor <-- vendored source files
/web <-- public web folder for the app
To run multiple apps from the same code base, you'd basically go:
/app
/app2
/...
/src
/vendor
/web
/web2
/...
... and then point each domain to a different /web folder.
As I understand, it's possible to re-organize a Rail project's directory structure to something more or less equivalent. See this related answer in particular:
https://stackoverflow.com/a/10480207/417194
From there, you could theoretically boot up one instance of your app per tenant, each with their separate resources, hard-coded defaults, etc., while continuing to use a shared code base.
The next best option is, imo, what you're currently doing. Use memcached or equivalent to quickly map specific domain names to tenants, and fall back to a database query as necessary. I imagine you're doing this already, since you've "some heavy caching in place".
As an aside, you might find this related question, and the ones linked within it, interesting:
Multiple applications using a single code base in ruby
(And FWIW, I ended up not sticking with PHP for multi-tenant apps. There was no magic bullet last I played with Ruby or Rails.)
With respect to memory usage, methinks don't worry too much about it: strictly speaking, you're looking up and creating the tenant object a single time per request. Even if doing so was dreadfully slow, cursory monitoring of where your app is actually spending time will reveal that it's negligible compared to innocuous looking pieces of code that get run a gazillion times per request.
I designed an application that is doing basically exactly what you have described. Since you are loading a single object (even if on every page request), just make sure the query only returns the one row (current tenant) and doesn't do a crazy amount of joins. A single row query with a LIMIT applied to it is not going to bring down your site, even if requested hundreds of times a second. And regardless, if you are getting that type of traffic, you will have to scale your server anyways.
One thing that can be done to help, is to make sure your search column is indexed in the database. If you are finding the current tenant by url, index the url column.
Here is an example of what I did. I globalized the variable so this information is available in all controllers/models/views.
In my application controller:
before_filter :allocate_site
private
def allocate_site
url = request.host_with_port
url.slice! "www."
# load the current site by URL
$current_site = Site.find_by({:url => url, :deleted => false})
# if the current site doesn't exist, we are going to create a placeholder
# for the URL hitting the server.
if $current_site.nil?
$current_site = Site.new
$current_site.name = 'New Site'
$current_site.url = url
$current_site.save
end
end
If you don't mind adding extra gems to your application, I would recommend using apartment. This was done exactly to serve your purpose : handle rails application with multiple tenants.
This would present two advantages to your problem:
handling #current_tenant (or whatever you wish to name it) is done through a middleware, so you won't need to set it in your ApplicationController. Take a look at the Switch on domain section of the README to see how it is done. (Note: apartment uses Apartment.current_tenant to refer to your #current_tenant.
The best part: in most case, you will not need #current_tenant anymore since apartment will scope all requests on the appropriate postgresql schema.

Best practices regarding per-user settings and predefining options

I want to save settings for my users and some of them would be one out of a predefined list! Using https://github.com/ledermann/rails-settings ATM.
The setting for f.e. weight_unit would be out of [:kg, :lb].
I don't really want to hardcode that stuff into controller or view code.
It's kind of a common functionality, so I was wondering: Did anyone come up with some way of abstracting that business into class constants or the database in a DRY fashion?
Usually, when I have to store some not important information which I don't care to query individually, I store them on a serialized column.
In your case you could create a new column in your users table (for example call it "settings").
After that you add to user model
serialize :settings, Hash
from this moment you can put whatever you like into settings, for example
user.settings = {:weight_unit => :kg, :other_setting1 => 'foo', :other_setting2 => 'bar'}
and saving with user.save you will get, in settings column, the serialized data.
Rails does also de-serialize it so after fetching a user's record, calling user.settings, you will get all saved settings for the user.
To get more information on serialize() refer to docs: http://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/Serialization/ClassMethods.html#method-i-serialize
UPDATE1
To ensure that settings are in the predefined list you can use validations on your user model.
UPDATE2
Usually, if there are some pre-defined values it's a good habit to store them in a constant inside the related model, in this way you have access to them from model (inside and outside). Acceptable values does not change by instance so it makes sense to share them between all. An example which is more valuable than any word. Defining in your User model:
ALLOWED_SETTINGS = {:weight_unit => [:kg, :lb],
:eyes_color => [:green, :blue, :brows, :black],
:hair_length => [:short, :long]}
you can use it BOTH
outside the model itself, doing
User::ALLOWED_SETTINGS
inside your model (in validations, instance methods or wherever you want) using:
ALLOWED_SETTINGS
Based on your question, it sounds like these are more configuration options that a particular user will choose from that may be quite static, rather than dynamic in nature in that the options can change over time. For example, I doubt you'll be adding various other weight_units other than :kg and :lb, but it's possible I'm misreading your question.
If I am reading this correctly, I would recommend (and have used) a yml file in the config/ directory for values such as this. The yml file is accessible app wide and all your "settings" could live in one file. These could then be loaded into your models as constants, and serialized as #SDp suggests. However, I tend to err on the side of caution, especially when thinking that perhaps these "common values" may want to be queried some day, so I would prefer to have each of these as a column on a table rather than a single serialized value. The overhead isn't that much more, and you would gain a lot of additional built-in benefits from Rails having them be individual columns.
That said, I have personally used hstore with Postgres with great success, doing just what you are describing. However, the reason I chose to use an hstore over individual columns was because I was storing multiple different demographics, in which all of the demographics could change over time (e.g. some keys could be added, and more importantly, some keys could be removed.) It sounds like in your case it's highly unlikely you'll be removing keys as these are basic traits, but again, I could be wrong.
TL;DR - I feel that unless you have a compelling reason (such as regularly adding and/or removing keys/settings), these should be individual columns on a database table. If you strongly feel these should be stored in the database serialized, and you're using Postgres, check out hstore.
If you are using PostgreSQL, I think you can watch to HStore with Rails 4 + this gem https://github.com/devmynd/hstore_accessor

Rails: eager load on fetching associations, not on find

I've got three nested models: user has many plates and plate has many fruits. I also have a current_user helper method that runs in the before filter to provide authentication. So when I get to my controller, I already have my user object. How can I load all the user's plates and fruits at once?
In other words, I'd like to do something like:
#plates = current_user.plates(include: :fruits)
How can I achieve this?
I'm using Rails 3.1.3.
You will probably want to use the provided #includes method on your relation. DO NOT USE #all unless you intend to immediately work through the records, it will immediately defeat many forms of caching.
Perhaps something like: #plates = current_user.plates.includes(:fruits)
Unfortunately, there are portions of the Rails API that are not as well documented as they should be. I would recommend checking out the following resources if you have any further questions about the Rails query interface:
Query Interface Guide
ActiveRecord::Relation Walkthrough (screencast)
The query interface is possibly the most difficult part of the Rails stack to keep up with, especially with the changes made with Rails 3.0 and 3.1.
You can do
ActiveRecord::Associations::Preloader.new([current_user], :plates => :fruit).run
To eager load associations after current_user was loased. The second argument can be anything you would normally pass to includes: a symbol, an array of symbols, a hash etc
#plates = current_user.plates.all(:include => :fruits)
should do it.

Avoid loading associated relationship objects

I am using Ruby on Rails 3.0.7 and, for performance reason, I would like to avoid loading associated objects on retrieving a class obect. That is, if I have an Article class\model with a has_many :users statement I would like to not load associated User objects when I retrieve an Article object (I think this behavior depends on the Ruby on Rails "Convention over Configuration" principle).
How can I do that?
As noted by Yet Another Geek, Rails (ActiveRecord) doesn't load the relationship objects by default. Rather, it goes and gets them when you ask for them. If you don't need the objects of that relationship, it will never bother to load them, saving database time.
If you do need then, it will go retrieve them lazily (by default). If you know you'll need all (or many) of the objects of the relationship (assuming x-to-many), then you can use the :include modifier to your find to get them all up front (which will be a lot faster since it can do that with a single db call). Knowing and taking advantage of the ability to eagerly load relationship objects is an important thing.
#person = Person.find(params[:id], :include => :friends)
All that being said, the behavior you want (not loading the objects if you don't need them) is the default behavior and you should be all set. The rest of the answer was just some context that may be useful to you later.
Implied by this wiki article, loading is lazy by default. You have to include the :users relationship if you want it eagerly loaded.

Rails add custom eager load

I have a number of custom find_by_sql queries in Rails. I would like to use eager loading with them but there doesn't seem to be a good way to do this.
I have seen the eager_custom.rb file floating around and it doesn't seem to work with Rails now. It appear Rails does eager loading differently now, using 2 queries (the regular query plus a query where the 'id IN' the ids from the first query), instead of the single join query used in the past.
My question is if I do a custom SQL query, then do 'id IN' query, is there a way to add back associated objects into the initial query results?
For example I have topics loaded with find_by_sql, then I find topic images where the topic id is in the topics ids, is there a way to add the images manually back to the topics?
Thanks
As you noticed, in Rails 2.1 a new kind of eager/pre-loading was introduced which uses multiple queries with id IN (...). This method is usually faster, especially when there are multiple associations being pre-loaded. You can use this functionality manually with find_by_sql by using the preload_associations class method inherited from ActiveRecord (not recommended). For example:
class Person
def self.find_a_special_group
people = find_by_sql("...")
preload_associations(people, [:jobs, :addresses])
return people
end
end
The preload_associations method is protected, so you must call it from within the class, and it takes (1) an array of objects, (2) an array, hash, or symbol of associations (same format as find's :include option), and (3) an options hash. See the documentation for the ActiveRecord::AssociationPreload::ClassMethods module for more details.
However, having said all of that, this technique is certainly undesirable as the Rails documentation discourages programmers from using preload_associations directly. Are you sure you have to use find_by_sql? Are you sure you know all of the options find takes? (:select, :from, :joins, :group, :having, etc) I'm not saying you don't need find_by_sql, but it might be worth a few minutes to make sure.

Resources