ActiveRecord: Accessing owner association after building record - ruby-on-rails

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.

Related

Rails: creating nested attributes with manually set IDs

I have a rails API that injects primary key IDs from the client, as opposed to autogenerating through Rails. Specifically:
class ParentModel < ApplicationRecord
accepts_nested_attributes_for: child_models
has_many :child_models
end
class ChildModel < ApplicationRecord
belongs_to :parent_model
end
Nested data is created via:
#parent_object = ParentModel.find_or_initialize_by(id: parent_model_params[:id])
#parent_object.assign_attributes(parent_model_params)
#parent_object.save
If the ID for a child_object already exists in the database, the operation updates the child object as expected. However, if the child object is new, I get:
Couldnt find ChildModel with ID=b35e8f02... for ParentModel with ID=0c9b60f3...
In short: I'm trying to ensure rails creates child records (with the given IDs) when they don't exist, and continues to update existing child records if they do. Any thoughts on how to do that through nested attributes?
For those wondering, I couldn't find an elegant solution. Instead, I manually created a new child object for each new child before calling .assign_attributes. Eg:
parent_model_params[:child_model_attributes].each do |child_params|
next if #parent_object.child_ids.include?(child_params[:id])
#parent_object.child_objects << ChildModel.new(child_params)
end
#parent_object.assign_attributes(parent_model_params) # This no longer raises a RecordNotFound error

In Rails, can I filter one-to-many relationships without updating the database

I am using Rails 5 and I want to be able to filter a one-to-many relationship to only send a subset of the child items to the client. The data model is pretty standard, and looks something like this:
class Parent < ApplicationRecord
has_many :children, class_name: 'Child'
end
class Child < ApplicationRecord
belongs_to :parent
end
When the client makes a call, I only want to return some of the Child instances for a Parent.
This is also complicated because the logic about which Child objects should be returned is absurdly complicated, so I am doing it in Ruby instead of the database.
Whenever I execute something like the following, Rails is attempting to update the database to remove the association. I don't want the database to be updated. I just want to filter the results before they are sent to the client.
parent.children = parent.children.reject { |child| child.name.include?('foo') }
Is there a way to accomplish this?
Add an instance method in Parent model
def filtered_children
children.where.not("name like ?", '%foo%')
end
Call filtered_children wherever required, it doesn't make sense to reset the existing association instance variable. The same queries are cached so it doesn't matter if you call them one time or multiple times. But you can always memoize the output of a method to make sure the the method is not evaluated again second time onwards,
def filtered_children
#filtered_children ||= children.where.not("name like ?", '%foo%')
end
Hope that helps!
DB update is happening because filtered records are being assigned back to parent.children. Instead another variable can be used.
filtered_children = parent.children.reject { |child| child.name.include?('foo') }

Defining attributes at runtime based on data from related object

I'm building an application where users are part of an Organisation. An organisation has many Lists, which in turn have many ListItems.
Now, I would like for admin users to be able to specify which attributes are available on list items, based on the organisation they belong to (or rather, on the organisation their list belongs to), without having to touch any code.
So far, when defining attributes that are not bound to a specific column in the database, I have used document_serializable, a nifty little gem (based on virtus) which serializes virtual attributes to a JSONB column in the db. I like this approach, because I get all of virtus' goodies (types, coercion, validations, etc.), and because data ends up sitting in a JSONB column, meaning it can be loaded quickly, indexed, and searched through with relative ease.
I would like to keep using this approach when adding these user-defined attributes on the fly. So I'd like to do something like:
class ListItem < ApplicationRecord
belongs_to :list
delegate :organisation, to: :list
organisation.list_attributes.each do |a, t|
attribute a, t
end
end
Where Organisation#list_attributes returns the user-defined hash of attribute names and their associated types, which, for example, might look like:
{
name: String,
age: Integer
}
As you might have guessed, this does not work, because organisation.list_attributes.each actually runs in the context of ListItem, which is an instance of Class, and Class doesn't have an #organisation method. I hope that's worded in a way that makes sense1.
I've tried using after_initialize, but at that point in the object's lifecycle, #attribute is owned by ActiveRecord::AttributeMethods::Read and not DocumentSerializable::ClassMethods, so it's an entirely different method and I can't figure out wether I can still access the one I need, and wether that would even work.
Another alternative would be to find the organisation in question in some explicit way, Organisation#find-style, but I honestly don't know where I should store the information necessary to do so.
So, my question: at the moment of instantiating (initializing or loading2) a record, is there a way I can retrieve a hash stored in a database column of one of its relations? Or am I trying to build this in a completely misguided way, and if so, how else should I go about it?
1 To clarify, if I were to use the hash directly like so:
class ListItem < ApplicationRecord
belongs_to :list
delegate :organisation, to: :list
{
name: String,
age: Integer
}.each do |a, t|
attribute a, t
end
end
it would work, my issue is solely with getting a record's relation at this earlier point in time.
2 My understanding is that Rails runs a model's code whenever a record of that type is created or loaded from the database, meaning the virtual attributes are defined anew every time this happens, which is why I'm asking how to do this in both cases.
at the moment of instantiating (initializing or loading) a record, is
there a way I can retrieve a hash stored in a database column of one
of its relations?
Yes. This is fairly trivial as long as your relations are setup correctly / simply. Lets say we have these three models:
class ListItem < ApplicationRecord
belongs_to :list
end
class List < ApplicationRecord
belongs_to :organisation
has_many :list_items
end
class Organisation < ApplicationRecord
has_many :lists
end
We can instantiate a ListItem and then retrieve data from anyone of its parents.
#list_item = ListItem.find(5) # assume that the proper inherited
foreign_keys exist for this and
its parent
#list = #list_item.list
#hash = #list.organisation.special_hash_of_org
And if we wanted to do this at every instance of a ListItem, we can use Active Record Callbacks like this:
class ListItem < ApplicationRecord
belongs_to :list
# this is called on ListItem.new and whenever we pull from our DB
after_initialize do |list_item|
puts "You have initialized a ListItem!"
list = list_item.list
hash = list.organisation.special_hash_of_org
end
end
But after_initialize feels like a strange usage for this kind of thing. Maybe a helper method would be a better option!

Check if association exists without incurring a database hit

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

How to make ActiveRecord execute SQL to load association when the parent model object is not persisted yet?

I am doing things in a non-standard way. I am assigning IDS on object creation.
So, during before_save callbacks, which access a parent model's child association collections, I have this issue, where ActiveRecord won't actually execute the SQL to lookup the child association.
I can get the associated objects by doing a find on their class, as shown below, but is there any way to force the collection association methods to actually run the query and fetch the children when the parent itself has not been saved yet?
class Project < ActiveRecord::Base
has_many :tasks
end
class Task < ActiveRecord::Base
belongs_to :project
end
3.times do
Task.create(:project_id => 1)
end
Tasks.where(:project_id => 1).count
# 3
tasks = Tasks.where(:project_id => 1)
# SELECT * FROM tasks WHERE project_id = 1;
p = Project.new(:id => 1)
p.tasks # nil
# no SQL query executed
There is no SQL executed because you haven't saved your Project record yet. Try changing Project.new(:id => 1) to Project.create!(:id => 1) and then query your tasks.
In other words, your project doesn't exist, so why would it look for tasks underneath it?

Resources