I am building the data model for a small application, and would like to take advantage of eager loading in some methods - i.e. those where I know in advance that certain associations are going to get used.
I have read the Sequel::Model::Association guide to the .eager method, but it has confused me a little. A typical example might be:
Artist.eager( :albums => :tracks ).all
which loads all the Artist objects with all their albums fields preloaded, and all the tracks pre-loaded, using just three queries. So far, so good.
But say I want to load a single Artist by its primary key, and still have the albums + tracks pre-loaded (still three queries, potentially a lot less than following the associations for each album)? I cannot see any example of that. A little experimentation gives me
Artist.eager( :albums => :tracks ).where( :id => id ).all.first
which seems to at least work. I confirmed the eager loading by calling this, then switching off the db, and showing I could still access the associations.
However, I feel like I have missed something. The construct, having to pass in the primary key to the where clause, get a full dataset then ask for first item seems quite awkward. I am looking for something like this:
Artist.eager( :albums => :tracks )[ id ]
. . . a simple way of declaring I want to load a single object, and eager load some of its associations.
I have found that I can create a custom association like this:
def eager_albums
albums_dataset.eager( :tracks ).all
end
but that is awkward to use, because the code has to ask for the association in a different way.
My question: Does my construct Artist.eager( :albums => :tracks ).where( :id => id ).all.first do what I think it does in Sequel, and could I do better (simpler code)?
Calling Artist.eager( :albums => :tracks ).where( :id => id ).all.first fetches all artists with an id equal to 'id' and then extracts the first artist from the result array (calling all materializes the array). Typically this query will only be returning an array with a single artist.
It is not until you actually ask for the relationship artist.albums or associations beyond the album that additional queries are run.
You could just do Artist.eager( :albums => :tracks ).where( :id => id ).first
I use the tactical eager loading plugin so its pretty much unnecessary to use eager at all since it detects when an association is being used from an model instance which is part of a larger result set and does the eager load of related models for the entire result set automatically. Your query now becomes Artist[id], or Artist[id].albums.all
Another handy plugin I use is the association proxies plugin so I don't have to deal with using association.some_array_method vs association_dataset.some_ds_method
And finally the dataset associations plugin allows you to describe a query starting from some model or model class and chain through associations to a destination model whilst allowing you to define constraints (or not) along the way.
For example "find all tracks longer than 3 minutes on any album released in the past 2 weeks by artists born in the UK":
Artist.where(country_of_birth: 'UK').albums.where{ release_date > Date.today - 14}.tracks.where{ duration > 3.minutes }.all
Hope this sheds some light.
Related
I'm using Rails 5. I have this model
class MyObject < ActiveRecord::Base
...
belongs_to :distance_unit
and I notice when I have a line like below
distance = Distance.new({:distance => my_obj.distance, :distance_unit => my_obj.distance_unit})
it causes the following to be executed
SELECT "distance_units".* FROM "distance_units" WHERE "distance_units"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
Nothing unusual, but I have a cached method created in my DistanceUnit model
class DistanceUnit < ActiveRecord::Base
def self.cached_find_by_id(id)
Rails.cache.fetch("distanceunit-#{id}") do
puts "looking for id: #{id}"
find_by_id(id)
end
end
and I would like the "distance = Distance.new({:distance => my_obj.distance, :distance_unit => my_obj.distance_unit})" line to invoke my cached functionality instead of running off to the database. How can I achieve this?
Regarding to my comment, note that the memory_store is only used within a process, so each process will have its own store (consider MemCacheStore if that is an issue). Also the cache is gone when the process ends.
As a general note: The belongs_to association inherently triggers a lookup if the object hasn't been fetched yet. There are plenty of caches built in the Rails framework and in general I wouldn't worry too much about premature optimization in the beginning (and only optimize when you find queries are running too slow).
Also: Accessing the cache is also a call to a type of database, and an lookup based on an probably indexed id typically isn't really a heavy call. Also if there's just a few distance units, you maybe simply using a constant could work?
But to answer your question. There doesn't seem to be anything in your code that says cached_find_by_id (and while rails does a lot of things automagically, this isn't one of them).
You could create the following method in MyObject that overrides the 'getter' that is created by the belongs_to association:
def distance_unit
DistanceUnit.cached_find_by_id(distance_unit_id)
end
However, if you're simply initializing an ActiveRecord object and you don't need the DistanceUnit in this call you could also pass the id directly, since that is what is stored in the database.
Distance.new({:distance => my_obj.distance, :distance_unit_id => my_obj.distance_unit_id})
I'm writing my first app in Ruby on Rails (I've only went through railstutorial.org before ) which is simple Electronic Prescription Service and I run on one thing I can't cope with.
I want to create form for adding new prescription as well as adding some medicines which belong to this newly created prescription.
First problem is - how can I actually add medicines to the prescription when during filling the form prescription doesn't exist yet? What I did before was I first create prescription with #user.prescription.create(presc_attributes) and later #prescription.relations.create(medicine_id).
Now I need to do this probably on one form sending the whole thing using one button ( one HTML request, am I right? ) unless you guys have got better idea. I'm also struggling with an idea where to actually put method creating this prescription. Should it be in PrescriptionController#new or maybe connected to RelationsController#new as well?
I've read couple of articles about nested fields_for but they don't seem to match exactly what I need.
Im really new to RoR so sorry if I missed something important.
Here is my EER as well
http://i.stack.imgur.com/sa9CB.png
UPDATE---
ahhh i see, I think what you want is a relationship with an inverse_of.
If you are using a belongs_to on the join model, it is a good idea to set the :inverse_of option on the belongs_to, which will mean that the following example works correctly (where tags is a has_many :through association):
#post = Post.first
#tag = #post.tags.build name: "ruby"
#tag.save
The last line ought to save the through record (a Taggable). This will only work if the :inverse_of is set:
class Taggable < ActiveRecord::Base
belongs_to :post
belongs_to :tag, inverse_of: :taggings
end
If you do not set the :inverse_of record, the association will do its best to match itself up with the correct inverse. Automatic inverse detection only works on has_many, has_one, and belongs_to associations.
FROM http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
I have two models that are associated via a has_many relationship. E.g.
class Newspaper < ActiveRecord::Base
has_many :articles
end
class Article < ActiveRecord::Base
belongs_to :newspaper
validates :uid, presence: true,
uniqueness: { case_sensitive: true }
end
A newspaper is updated several times a day but we only want to construct and add articles to the association that do not already exist. The following code was my first cut of achieving this.
new_articles.each do |article|
unless newspaper.articles.exists? uid: article.uid
newspaper.articles.build(uid: article.uid)
end
end
The newspaper object is either new and unsaved, or retrieved with existing relationships at this point.
My tests indicate that I am able to add two articles to the newspaper that have the same UID using the code above and this is obviously not want I want.
I appears to me that my current code will result in a validation failure upon being saved as the validation looks at uniqueness across the entire articles table and not the association.
What I'm struggling to understand is how the exists? method behaves in this scenario (and why it's not saving my bacon as I planned). I'm using FactoryGirl to build a newspaper, add an article and then simulate an update containing an article with the same uid as the article I've already added. If the code works I should get only one associated article but instead I get two. Using either build or create makes no difference, thus whether the article record is already present in the database does not appear to change the outcome.
Can anyone shed some light on how I can achieve the desired result or why the exists? method is not doing what I expect?
Thanks
The association exists? actually creates a scoped query, as per the association. This is why your existing articles filter doesn't work.
unless newspaper.articles.exists? uid: article.uid
# `articles.exists?` here will produce this if the newspaper is new
# ... WHERE "articles"."newspaper_id" IS NULL AND "articles.uid" = '<your uid>'
# and this, if the newspaper is persisted (with an id of 1)
# ... WHERE "articles"."newspaper_id" = 1 AND "articles.uid" = '<your uid>'
The case of the new newspaper is clearly wrong, as it would only return articles with a nil newspaper ID. But the persisted case is probably undesirable as well, as it still unnecessarily filters against newspaper ID, when you real concern here is that the UID is unique.
Rather, you probably want simply against Article, rather than scoping the exists? through the association, like:
unless Article.exists? uid: article.uid
Concerning your other problem:
this appears to be a FactoryGirl problem where the create method isn't creating db entries in the same way I can in the irb.
FactoryGirl.create should still abide by validations. It might help to see your test.
I ignorantly named a model in my Rails app System, which is also a ruby core method. This model is in a relationship with another model Project, which I am trying to index.
Ideally, I am looking to setup my index like this:
define_index do
indexes :name, :sortable => true
indexes system(:name), :sortable => true, :as => :system_name
end
I could change the model name, but I'd call that a compromise, and I'm not convinced I need to. Is there a good work-around for this?
ruby 1.8.7, rails 3.0.7, thinking_sphinx 2.0.3
The good work around for naming variables or user-level Classes with reserved words (language keywords and platform-level methods/classes) is not do it in the first place.
The second best workaround is to use scoping :: to make sure the name you are calling is the one you want
::system() # calls the actual system method as defined by Ruby
APPNAME::MODEL_NAME # would call the model defined as `APPNAME::MODEL_NAME`
I can't really think of a workaround without namespacing your models (although knowing Ruby, its more than possible some functionality exists-- just never needed it myself). Prolly all of them tbh since it would get even more confusing if only half your models were namespaced. In the long run, its just more typing remembering to namespace everything.
For normal columns, you can get at them via the columns class method. However, associations may be named something quite different if the foreign_key option is set in the relationship method. For example, given
class Post
has_many :comments, :foreign_key => :message_id # this is a contrived example
end
if I did Post.column_names I could get at message_id, but is there any way to get comments?
Model.reflections gives information about a model's associations. It is a Hash keyed on the association name. e.g.
Post.reflections.keys # => ["comments"]
Here is an example of some of the information it can be used to access:
Post.reflections["comments"].table_name # => "comments"
Post.reflections["comments"].macro # => :has_many
Post.reflections["comments"].foreign_key # => "message_id"
Note: this answer has been updated to cover Rails 4.2 based on MCB's answer and the comments below. In earlier versions of Rails the reflection's foreign_key was accessed using primary_key_name instead, and the keys for the reflections may be symbols instead of strings depending on how the association was defined e.g. :comments instead of "comments".
For future Googlers in Rails 4 the answer would now be:
Post.reflections[:comments].foreign_key # => "message_id"
Taken from here: https://stackoverflow.com/a/15364743/2167965
EDIT:
reflections, as of 4.2, now takes strings instead of symbols which is a fun bug to track down. If you want to keep using symbols you should switch to reflect_on_association(:assoc_name). Also note reflections are actually the public api which will keep reporting things like HABTM, even though it's all has many through under the hood. The reflections Rails is actually using are now in _reflections
For an ActiveRecord object I use:
object._reflections
So, I can manipulate the Hash returned. For instance:
object._reflections.keys.each do |key|
object.public_send(key).destroy_all
end
The above example delete all the relationships from database.