Check if association exists without incurring a database hit - ruby-on-rails

Is there a way to check if an ActiveRecord's belongs_to association exists without incurring a database query.
I'm using example_association.present? to check and it results in the association being loaded if it does.
All I want to know is if the association exists.

You could use reflect_on_all_associations as:
Foo.reflect_on_all_associations(:belongs_to).map(&:name).include?(:example_assoc)
Where :example_assoc is one of the belongs_to association.
Or if you have an instance of model class:
#foo.class.reflect_on_all_associations(:belongs_to).map(&:name).include?(:example_assoc)

class Article < ActiveRecord::Base
has_many :pages
end
class Page < ActiveRecord::Base
belongs_to :article
end
Check if the association exist by:
Article.reflect_on_association(:pages)
or
Page.reflect_on_association(:article)
If the association not exist, the return value of Page.reflect_on_association(:article) will be nil, otherwise it puts like :
#<ActiveRecord::Reflection::HasManyReflection:0x00007fbe099d9c10
#active_record=
Page(id: integer, name: string),
#association_scope_cache={},
#automatic_inverse_of=false,
#constructable=true,
#foreign_type="article_type",
#klass=nil,
#name=:article,
#options={:autosave=>true},
#plural_name="articles",
#scope=nil,
#scope_lock=#<Thread::Mutex:0x00007fbe099d9990>,
#type=nil>
It mains the association exist,and you can get more info about it.

If you're trying to minimise the number of queries perhaps you should consider using "include" to eager load the associations.
Eg.
foos = Foo.includes(:example_associations).all
And then later in a loop or something when invoking
foo.example_associations.present?
Should not invoke any additional database queries

Related

How to access has_many through relationship possible in rails?

How can I access my related records?
class Post < ActiveRecord::Base
has_many :post_categories
has_many :categories, through: :post_categories
class Categories < ActiveRecord::Base
has_many :post_categories
has_many :post, through: :post_categories
class PostCategories < ActiveRecord::Base
belongs_to :post
belongs_to :category
PostCategories table has id, posts_id, and categories_id columns.
id | posts_id | categories_id
1. | 2 | 3
2. | 2 | 4
What I want is: to get posts related to a category. like: all Posts where in x category.
Yep, this is an easy one.
one_or_more_categories = # Category.find... or Category.where...
posts = Post.joins(:categories).where(category: one_or_more_categories)
Rails is clever enough to take either a model or a query that would find some data and turn that into an efficient appropriate query, that might be a subquery. Trying things out in the Rails console (bundle exec rails c) is a good way to see the generated SQL and better understand what's going on.
(EDIT: As another answer points out, if you've already retrieved a specific Category instance then you can just reference category.posts and work with that relationship directly, including chaining in .order, .limit and so-on).
Another way to write it 'lower level' would be:
Post.joins(:categories).where(category: {id: one_or_more_category_ids})
...which is in essence what Rails will be doing under the hood when given an ActiveRecord model instance or an ActiveRecord::Relation. If you already knew the e.g. category "name", or some other indexed text column that you could search on, then you'd adjust the above accordingly:
Post.joins(:categories).where(category: {name: name_of_category})
The pattern of joins and where taking a Hash where the join table name is used as a key with values nested under there can be taken as deep as you like (e.g. if categories had-many subcategories) and you can find more about that in Rails Guides or appropriate web searches. The only gotcha is the tortuous singular/plural stuff, which Rails uses to try and make things more "English-y" but sometimes - as in this case - just creates an additional cognitive burden of needing to remember which parts should be singular and which plural.
Not sure if this answers it but in ActiveRecord your Post will have direct access to your Category model and vice versa. So you could identify the category you want the posts from in a variable or an instance variable, and query #specific_category.posts. If you are doing this in your controller, you could even do it in before_action filter. If you are using it in serializers its not much different.
You could also create a scope in your Post model and use either active record or raw SQL to query specific parameters.
You also have an error in your Category model. Has many is always plural so it would be has_many :posts, through: :post_categories
Get the category object and you can directly fetch the related posts. Please see the following
category = Category.find(id)
posts = category.posts
Since you have already configured the has_many_through relation, rails will fetch post records related the category.

Association on primary key not percolating to view

In a rails view
<%= packageoffer.id %> <%= packageoffer.structure.nil? %>
returns 3817 true
Yet, when queried in console
al = Available.find(3817)
al.structure
returns an object. #<Structure id: "CAF401100", product_code: "CAF401100", [...]
Thus packageoffer.structure.nil? in the view ought to return false!
Attempting to figure out what is going on... The classes are defined in such a way to create the association, as proven by the console query:
class Available < ActiveRecord::Base
belongs_to :structure, class_name: 'Structure', primary_key: 'product_code'
class Structure < ActiveRecord::Base
self.primary_key = 'product_code'
has_many :availables, primary_key: :product_code
The controller action is a bit complex, assembling two arrays originating from different classes:
#availables = Available.where(['event_id = ?', #event.id]).to_a
#packageoffers = (#packageoffers_a + #availables).sort_by(&:cost)
and the view then conditionally processes the assembled array
#packageoffers.each do |packageoffer|
if #availables.include?(packageoffer)
which then tries to invoke the relationship
packageoffer.structure.nil?
why is the association not accessible at this point?
update one line of thought is that being a sum of two arrays, the sumed array #packageoffers has no structural knowledge about the relationship of Class Available. How can that be re-established in this case?
You say that after restarting the console you are seeing a nil each time you call .structure on this object. In that case you have not yet successfully saved a structure foreign key to this the object.
If you were attempting to set this association via the console, you may be a victim of the misleading fact that there are circumstances under which associations you set in the console are not actually written to the database and don't persist. If so, this discussion may be helpful to you: Create association between two instancied objects
Lesson 1:
when defining these non-standard associations, restart console after EACH change, to avoid being sent on a garden path.
Lesson 2:
Do not confuse foreign and primary keys. The issue got resolved by defining the association as:
belongs_to :structure, class_name: 'Structure', foreign_key: 'product_code

ORDER BY and DISTINCT ON (...) in Rails

I am trying to ORDER by created_at and then get a DISTINCT set based on a foreign key.
The other part is to somehow use this is ActiveModelSerializer. Specifically I want to be able to declare:
has_many :somethings
In the serializer. Let me explain further...
I am able to get the results I need with this custom sql:
def latest_product_levels
sql = "SELECT DISTINCT ON (product_id) client_product_levels.product_id,
client_product_levels.* FROM client_product_levels WHERE client_product_levels.client_id = #{id} ORDER BY product_id,
client_product_levels.created_at DESC";
results = ActiveRecord::Base.connection.execute(sql)
end
Is there any possible way to get this result but as a condition on a has_many relationship so that I can use it in AMS?
In pseudo code: #client.products_levels
Would do something like: #client.order(created_at: :desc).select(:product_id).distinct
That of course fails for reasons that are beyond me.
Any help would be great.
Thank you.
A good way to structure this is to split your query into two parts: the first part manages the filtering of rows so that you get only your latest client product levels. The second part uses a standard has_many association to connect Client with ClientProductLevel.
Starting with your ClientProductLevel model, you can create a scope to do the latest filtering:
class ClientProductLevel < ActiveRecord::Base
scope :latest, -> {
select("distinct on(product_id) client_product_levels.product_id,
client_product_levels.*").
order("product_id, created_at desc")
}
end
You can use this scope anywhere that you have a query that returns a list of ClientProductLevel objects, e.g., ClientProductLevel.latest or ClientProductLevel.where("created_at < ?", 1.week.ago).latest, etc.
If you haven't already done so, set up your Client class with a has_many relationship:
class Client < ActiveRecord::Base
has_many :client_product_levels
end
Then in your ActiveModelSerializer try this:
class ClientSerializer < ActiveModel::Serializer
has_many :client_product_levels
def client_product_levels
object.client_product_levels.latest
end
end
When you invoke the ClientSerializer to serialize a Client object, the serializer sees the has_many declaration, which it would ordinarily forward to your Client object, but since we've got a locally defined method by that name, it invokes that method instead. (Note that this has_many declaration is not the same as an ActiveRecord has_many, which specifies a relationship between tables: in this case, it's just saying that the serializer should present an array of serialized objects under the key `client_product_levels'.)
The ClientSerializer#client_product_levels method in turn invokes the has_many association from the client object, and then applies the latest scope to it. The most powerful thing about ActiveRecord is the way it allows you to chain together disparate components into a single query. Here, the has_many generates the `where client_id = $X' portion, and the scope generates the rest of the query. Et voila!
In terms of simplification: ActiveRecord doesn't have native support for distinct on, so you're stuck with that part of the custom sql. I don't know whether you need to include client_product_levels.product_id explicitly in your select clause, as it's already being included by the *. You might try dumping it.

ActiveRecord: Accessing owner association after building record

Using Rails 2.3.14, I'm looking for way to access the owner of an ActiveRecord object after it was build (but before save) to get some values from the owner. Seems to be simple, but my approach always fires an unnecessary database query.
Example:
class Parent < ActiveRecord::Base
has_many :children
end
class Child < ActiveRecord::Base
belongs_to :parent
def after_initialize
self.some_value = parent.some_value
# This fires an additional database query to get the parent
end
end
parent = Parent.find(1)
# SELECT * FROM `parents` WHERE (`parents`.`id` = 1)
child = parent.children.build
# Same SELECT query is fired again, but of course not needed
I'm looking for a way to access the association object (here: parent) without doing an additional database access. How can this be done?
In Rails 3, there's a new option, :inverse_of, for belongs_to/has_many to do this, but not in Rails 2. Maybe you have to implement similar function by yourself.

Model association changes in production environment, specifically converting a model to polymorphic?

I was hoping I could get feedback on major changes to how a model works in an app that is in production already.
In my case I have a model Record, that has_many PhoneNumbers.
Currently it is a typical has_many belongs_to association with a record having many PhoneNumbers.
Of course, I now have a feature of adding temporary, user generated records and these records will have PhoneNumbers too.
I 'could' just add the user_record_id to the PhoneNumber model, but wouldn't it be better for this to be a polymorphic association?
And if so, if you change how a model associates, how in the heck would I update the production database without breaking everything? >.<
Anyway, just looking for best practices in a situation like this.
Thanks!
There's two approaches that might help you with this.
One is to introduce an intermediate model which handles collections of phone numbers. This way your Record and UserRecord can both belong_to this collection model and from there phone numbers and other contact information can be associated. You end up with a relationship that looks like this:
class Record < ActiveRecord::Base
belongs_to :address_book
delegate :phone_numbers, :to => :address_book
end
class UserRecord < ActiveRecord::Base
belongs_to :address_book
delegate :phone_numbers, :to => :address_book
end
class AddressBook < ActiveRecord::Base
has_many :phone_numbers
end
This kind of re-working can be done with a migration and a bit of SQL to populate the columns in the address_books table based on what is already present in records.
The alternative is to make UserRecord an STI derived type of Record so you don't need to deal with two different tables when defining the associations.
class Record < ActiveRecord::Base
has_many :phone_numbers
end
class UserRecord < Record
end
Normally all you need to do is introduce a 'type' string column into your schema and you can use STI. If UserRecord entries are supposed to expire after a certain time, it is easy to scope their removal using something like:
UserRecord.destroy_all([ 'created_at<=?', 7.days.ago ])
Using the STI approach you will have to be careful to scope your selects so that you are retrieving only permanent or temporary records depending on what you're intending to do. As UserRecord is derived from Record you will find they get loaded as well during default loads such as:
#records = Record.find(:all)
If this causes a problem, you can always use Record as an abstract base class and make a derived PermanentRecord class to fix this:
class PermanentRecord < Record
end
Update during your migration using something like:
add_column :records, :type, :string
execute "UPDATE records SET type='PermanentRecord'"
Then you can use PermanentRecord in place of Record for all your existing code and it should not retrieve UserRecord entries inadvertently.
Maintenance page is your answer.
Generate migration which updates table structure and updates existing data. If you're against data updates in migrations - use rake task.
Disable web access (create maintenance page)
Deploy new code
Run pending migrations
Update data
Enable web access (remove maintenance page).

Resources