Low level caching for collection - ruby-on-rails

I want to use Redis to do some low level caching in my Rails app.
In a controller I normally us this to get all books:
class BooksController < ApplicationController
def index
#books = Book.order(:title)
end
end
And the view iterates over this:
<ul>
- #books.each do |book|
<li>= "#{book.title} - #{book.author}"</li>
</ul>
Now I want the exact same result but then cached. I have Redis setup and running. So should I use a cached_books method in the controller like this:
#books = Book.cached_books.order(:title)
And leave the view as it is, or use book.cached_title and book.cached_author in the view and leave the controller as it is?
And how would a cached_books method look like in the Book model?
class Book < ActiveRecord::Base
...
def cached_books
Rails.cache.fetch([self, "books"]) { books.to_a }
end
end
For simplicity sake I leave out expire strategies for now, but obviously they need to be there.

So should I use a cached_books method in the controller like this:
Yes, you can. Although there's some gotchas you have to be aware of.
Book is ActiveRecord. When you call Book.something (e.g. Book.all, or just even Book.order(:title) it returns you a ActiveRecord::Relation, which is basically wrapper for array of Books (this wrapper prevents of firing unnecessary queries, boosting perfomance).
You can't save the whole result of a query in Redis. Say, you can save a JSON string of an array of hashes with model attributes, e.g.
[{
id: 1,
title: 'How to make a sandwich",
author: 'Mr. cooker'
}, {
id: 2,
title: 'London's Bridge',
author: 'Fergie'
}]
And then you can 'decrypt' this thing into the array after. Something like
def cached_books(key)
# I suggest you to native wrapper
if result = $redis.hget 'books_cache', key
result.map do { |x| Book.new(x) }
end
end
And also, you will have to serialise attributes before putting them into the cache.
Ok, now you have collections which can be iterated in the view with same data, although you can't call order on a cached collection, as it is a plain array (you may call sort, but the idea is to cache already sorted data).
Well... does it worth it? Actually, not really. If you need to cache this piece – probably, best way is to cache a rendered page, not a query result.
Should you use cached_title and cached_author – that's the good question. First of all, it depends on what cached_title may be. If it is a string – there's nothing you can cache. You get a Book through DB request, or you get Book from cache – in any way the title will be presented in it, as it is a simple type. But let's look closer to the author. Most likely it is going to be a relation to another model Author and this is the place where cache suits perfectly great. You can re-define author method inside the book (or define new and avoid nasty effects Rails may have in complex queries in the future) and see if there's a cache. If yes, return the cache. If not – query the DB, save result to the cache and return it.
def author
Rails.cache.fetch("#{author_id}/info", expires_in: 12.hours) do
# block executed if cache is not founded
# it's better to alias original method and call it here
#instead of directly Author.find call though
Author.find(author_id)
end
end
Or less convenient, but more "safe":
def cached_author
Rails.cache.fetch("#{author_id}/info", expires_in: 12.hours) do
author
end
end

Related

Is it dangerous to return an unpersisted activerrecord with ruby graphql?

In rails, a common pattern I've used is to create a new unpersisted object to populate forms with default values, eg
class ContactsController < ApplicationController
def new
#contact = Contact.new
end
end
...
<%= form_for(#contact) %>
I've created a form using graphql ruby, and I would like to populate the default values using an unpersisted object
ie I have a createOrUpdate form populated by contractParts
query Contract($contractId: ID!) {
contract(id: $contractId) {
...contractParts
}
}
query DefaultContract($ownerId: ID!) {
defaultContract(ownerId: $ownerId) {
...contractParts
}
}
and in my query_types.rb
def default_contract(owner_id:)
owner = Owner.find(owner_id)
Contract.new(owner: owner)
end
I'm wondering if this pattern is ok, or if because I'm returning an object without an ID there will be problems.
I've looked through all the documentation and I can't find anything about this, is this not proper graphql?
Certainly you can do this. You can create an 'aggregate' of unsaved objects with whatever defaults you like:
# app/models/default_contract_aggregate.rb
class DefaultContractAggregate < Contract
def initialize
self.rel1 = DefaultRel1.new
# etc
end
end
# app/graphql/types/query_type.rb
def default_contract(owner_id:)
owner = Owner.find(owner_id)
DefaultContractAggregate.new(owner: owner)
end
This isn't the worst idea, but of course with graphql, depending on your schema, it would likely be possible to reach the boundary of your aggregate so you'll have to be careful with that. As to your other question, there's nothing inherently wrong with using an unsaved object or having an object with no ID. Do be cautious with associations that autosave on assignment as that can lead to pretty exciting and unexpected results.
Personally, I find that it doesn't take much complexity to outgrow the rails form generators and I prefer to build complex UIs on the front end, but if they're still working for you, there's nothing wrong with continuing to use them.

Ruby on Rails: caching data in an object

I've come up with an issue I can't figure out how to solve. I'm new to both Ruby and Rails, and sure there is a simple way to achieve what I'm looking for.
This is the ERB of the show view, showing two equal lines:
<p><%= #user.foo %></p>
<p><%= #user.foo %></p>
Imagine that foo is an intense computational method so I want to cache the result of the first call in order to use it in the second line without having to call foo again. The simplest option would be defining a variable and cache it:
<% foo_cache = #user.foo %>
<p><%= foo_cache %></p>
<p><%= foo_cache %></p>
But I don't want to clutter the "global" scope. A nicer way would be that foo itself could save a cache of the value it returns when it's called the first time:
def foo
return self.cached_foo if self.cached_foo #WARNING: pseudocode here!
#Not cached. Do stuff
...
self.cached_foo = computed_value
computed_value
end
My question is if it's possible to attach data to an object instance dynamically without interfering with the model behind (i.e. without making save and company functions deal with this attached data). Or maybe is there another better way to achieve what I'm looking for?
Thanks.
This is called memoization and it's a common idiom in ruby. It is usually expressed like this:
def foo
#cached_foo ||= begin
# do your heavy stuff here
end
end
#cached_foo should not interfere with ActiveRecord (like make it try to save cached_foo to the database).
What you are looking for is called memoization
def foo
#foo ||= calculate_foo
end
def calculate_foo
# heavy stuff
end
This works thanks to conditional assignment (the||=)
It's an extensive topic so I'll leave you a couple of links about it:
http://rails-bestpractices.com/posts/59-use-memoization
http://gavinmiller.io/2013/basics-of-ruby-memoization/
Plus advanced memoization in case you need to do more complicated stuff such as parameters, storing nil values
http://gavinmiller.io/2013/advanced-memoization-in-ruby/
In fact Active Support had memoizable but it was deprecated and then extracted into a gem
In case you want to use it check it out on:
https://github.com/matthewrudy/memoist
This should do it. And don't be afraid, the instance variable has no impact on the persistence layer.
def foo
#foo ||= compute_foo
end
private
def compute_foo
# ...
end

Organizing site navigation actions in Rails

I'm new to Rails (I've worked in MVC but not that much) and I'm trying to do things the "right" way but I'm a little confused here.
I have a site navigation with filters Items by different criteria, meaning:
Items.popular
Items.recommended
User.items
Brand.items # by the parent brand
Category.items # by a category
The problem is that I don't know how to deal with this in the controller, where each action does a similar logic for each collection of items (for example, store in session and respond to js)
Either I have an action in ItemsController for every filter (big controller) or I put it in ItemsController BrandsController, CategoriesController (repeated logic), but neither provides a "clean" controller.
But I don't know witch one is better or if I should do something else.
Thanks in advance!
You're asking two separate questions. Items.popular and Items.recommended are best achieved in your Item model as a named scope This abstracts what Xavier recommended into the model. Then in your ItemsController, you'd have something like
def popular
#items = Item.popular
end
def recommended
#items = Item.recommended
end
This isn't functionally different than what Xavier recommended, but to me, it is more understandable. (I always try to write my code for the version of me that will come to it in six months to not wonder what the guy clacking on the keyboard was thinking.)
The second thing you're asking is about nested resources. Assuming your code reads something like:
class User
has_many :items
end
then you can route through a user to that user's items by including
resources :users do
resources :items
end
in your routes.rb file. Repeat for the other nested resources.
The last thing you said is
The problem is that I don't know how to deal with this in the controller, where each action does a similar logic for each collection of items (for example, store in session and respond to js)
If what I've said above doesn't solve this for you (I think it would unless there's a piece you've left out.) this sounds like a case for subclassing. Put the common code in the superclass, do the specific stuff in the subclass and call super.
There's a pretty convenient way to handle this, actually - you just have to be careful and sanitize things, as it involves getting input from the browser pretty close to your database. Basically, in ItemsController, you have a function that looks a lot like this:
def search
#items = Item.where(params[:item_criteria])
end
Scary, no? But effective! For security, I recommend something like:
def search
searchable_attrs = [...] #Possibly load this straight from the model
conditions = params[:item_criteria].keep_if do |k, v|
searchable_attrs.contains? k
end
conditions[:must_be_false] = false
#items = Item.where(conditions)
end
Those first four lines used to be doable with ActiveSupport's Hash#slice method, but that's been deprecated. I assume there's a new version somewhere, since it's so useful, but I'm not sure what it is.
Hope that helps!
I think both answers(#Xaviers and #jxpx777's) is good but should be used in different situations. If your view is exactly the same for popular and recommended items then i think you should use the same action for them both. Especially if this is only a way to filter your index page, and you want a way to filter for both recommended and popular items at the same time. Or maybe popular items belonging to a specific users? However if the views are different then you should use different actions too.
The same applies to the nested resource (user's, brand's and category's items). So a complete index action could look something like this:
# Items controller
before_filter :parent_resource
def index
if #parent
#items = #parent.items
else
#items = Item.scoped
end
if params[:item_criteria]
#items = #items.where(params[:item_criteria])
end
end
private
def parent_resource
#parent = if params[:user_id]
User.find(params[:user_id])
elsif params[:brand_id]
Brand.find(params[:brand_id])
elsif params[:category_id]
Category.find(params[:category_id])
end
end

Scope vs Class Method in Rails 3

Based on the Rails 3 API, the difference between a scope and a class method is almost non-existent.
class Shipment < ActiveRecord::Base
def self.unshipped
where(:shipped => false)
end
end
is the same as
scope :unshipped, where(:shipped => false)
However, I'm finding that I'm sometimes getting different results using them.
While they both generate the same, correct SQL query, the scope doesn't always seem to return the correct values when called. It looks like this problem only occurs when its called the same way twice, albeit on a different shipment, in the method. The second time it's called, when using scope it returns the same thing it did the first time. Whereas if I use the class method it works correctly.
Is there some sort of query caching that occurs when using scope?
Edit:
order.line_items.unshipped
The line above is how the scope is being called. Orders have many line_items.
The generate_multiple_shipments method is being called twice because the test creates an order and generates the shipments to see how many there are. It then makes a change to the order and regenerates the shipments. However, group_by_ship_date returns the same results it did from the first iteration of the order.
def generate_multiple_shipments(order)
line_items_by_date = group_by_ship_date(order.line_items.unshipped)
line_items_by_date.keys.sort.map do |date|
shipment = clone_from_order(order)
shipment.ship_date = date
line_items_by_date[date].each { |line_item| shipment.line_items << line_item }
shipment
end
end
def group_by_ship_date(line_items)
hash = {}
line_items.each do |line_item|
hash[line_item.ship_date] ||= []
hash[line_item.ship_date] << line_item
end
hash
end
I think your invocation is incorrect. You should add so-called query method to execute the scope, such as all, first, last, i.e.:
order.line_items.unshipped.all
I've observed some inconsistencies, especially in rspec, that are avoided by adding the query method.
You didn't post your test code, so it's hard to say precisely, but my exeprience has been that after you modify associated records, you have to force a reload, as the query cache isn't always smart enough to detect a change. By passing true to the association, you can force the association to reload and the query to re-run:
order.line_items(true).unshipped.all
Assuming that you are referencing Rails 3.1, a scope can be affected by the default scope that may be defined on your model whereas a class method will not be.

I want to map my database lookup tables to a hash, good idea?

I am developing a Rails web application and am confused about how to utilize the lookup table values in my models. Here is an example model from my app:
table name: donations
id
amount
note
user_id
appeal_id
donation_status_id
donation_type_id
is_anonymous
created_at
updated_at
The fields *donation_status_id* and *donation_type_id* refer to lookup tables. So in my code I have several random places where I make calls like this:
my_donation = Donation.find(params[:id])
if my_donation.donation_status_id == DonationStatus.find_by_name("completed").id
#do something
end
To my inexperienced eyes, a one-off query to the DonationStatus table seems incredibly wasteful here, but I don't see any other good way to do it. The first idea I thought of was to read all my lookup tables into a hash at application startup and then just query against that when I need to.
But is there a better way to do what I am trying to do? Should I not worry about queries like this?
Thanks!
Since you have two models, you should use ActiveRecord Model Associations when building the models.
class Donation
has_one :donation_status
end
class DonationStatus
belongs_to :donation
end
Then when you do
my_donation = Donation.find(params[:id])
if my_donation.donation_status.status_name == 'complete'
#do something
end
For more information, you may want to read up how rails is doing the model associations http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html Don't worry about performance, rails has taken care of that for you if you follow how the way it should be done
How about putting it in a constant? For example, something like this:
class DonationStatus < ActiveRecord::Base
COMPLETED_DONATION_ID = DonationStatus.find_by_name("completed").id
PENDING_DONATION_ID = DonationStatus.find_by_name("pending").id
# ...
end
class DonationsController < ApplicationController
def some_action
my_donation = Donation.find(params[:id])
if my_donation.donation_status_id == DonationStatus::COMPLETED_DONATION_ID
#do something
end
end
This way, DonationStatus.find_by_name("pending").id gets executed exactly one. I'm assuming, of course, that this table won't change often.
BTW, I learned this trick in Dan Chak's book, Enterprise Rails.
EDIT: I forgot to mention: in practice, I declare constants like this:
COMPLETED_DONATION_ID = DonationStatus.find_by_name("completed").id rescue "Can't find 'completed' in donation_statuses table"
What you could do is add this method to Donation:
# Donation.rb
def completed?
self.donation_status.name == 'completed' ? true : false
end
And then just do my_donation.completed?. If this is called a second time, Rails will look to cache instead of going to the DB.
You could add memcached if you want, or use Rails' caching further, and do:
def completed?
return Rails.cache.fetch("status_#{self.donation_status_id}_complete") do
self.donation_status.name == 'completed' ? true : false
end
end
What that will do is make a hash key called (for example) "status_1_complete" and if it's not defined the first time, will evaluate the block and set the value. Otherwise, it will just return the value. That way, if you had 1,000,000,000 donations and each of them had donation_status 1, it would go directly to the cache. memcached is quite fast and popular.

Resources