Association on primary key not percolating to view - ruby-on-rails

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

Related

What is cost/benefit of object.models.find() vs Model.find() in Ruby on Rails (Mongoid)

I've seen code where instead of just calling Model.find(m_id) the code is using the fact that a model belongs_to some other object and form the call this way: object.models.find(m_id). If one already has the primary key, and especially if that primary key is indexed, it seems to be a bit redundant (and perhaps a bit more expensive) to use the "Model belongs_to object" relationship.
The only thing that comes to mind is that by limiting the collection the find operates upon has a security value but that seems a little tenuous. So, why would someone opt to use object.models.find(m_id) when one could just call Model.find(m_id)?
When you use Model.find(m_id) you simply find model by its primary key.
When you use object.models.find(m_id) you find model in object's collection by primary key.
For example, you have:
class User < ApplicationRecord
has_many :messages
end
class Message < ApplicationRecord
belongs_to :user
end
And you have:
#<Message id: 1, user_id: 1>
Now if you call Message.find(1) you will get that message.
But when you call User.find(2).messages.find(1) you get
ActiveRecord::RecordNotFound
Because user #1 has this message but user #2 hasn't.

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

In Rails, what do keywords belongs_to, has_many, etc, actually do?

I understand the concept of relational databases, primary/foreign keys, etc, however, I'm having trouble seeing the actual result of setting these properties up in my models. Do they generate helper functions or something similar? or do they simply work on a database level?
For example, if I have these two models (other properties omitted)
class Course < ActiveRecord::Base
has_and_belongs_to_many :schedules
has_many :sections
end
class Section < ActiveRecord::Base
belongs_to :course
end
I could simply get all sections for any given course like this:
Section.where(course_id: 1234)
However, I could do this without having set up the relations at all.
So my question is: Why do we do this?
Adding these methods let's you do things like this:
Section.find(5).course # <== returns a 'Course' model instance
Also it let's you join your queries more easily:
Section.joins(:course).where(course: {name: "English"}) # <== returns sections who have the 'english' course
Neither of these would be possible if you didn't set up the relations in the model.
Similarly:
Course.find(8).sections # returns an array of sections with 'course_id = 8'
It makes your calls more semantic, and makes things easier from a programmatic perspective :)
Relations are applied on instances of an object. So, these relations allow you to get related objects to an instance of another.
For example, say you have an instance of Section (called #section). You'd be able to get all Course objects for that section by doing:
#section.course if you have belongs_to :course set up.
Similarly, if you have an instance of Course, you can get all Section objects for that Course with:
#course.sections if you have has_many :sections.
TL;DR - these are helper scopes for instance variables of Course and Section.

How to save related Models in one transaction?

I have two models:
class Customer < ActiveRecord::Base
has_many :contacts
end
class Contact < ActiveRecord::Base
belongs_to :customer
validates :customer, presence: true
end
Then, in my controller, I would expect to be able to create both in
"one" sweep:
#customer = Customer.new
#customer.contacts.build
#customer.save
This, fails (unfortunately translations are on, It translates to
something like: Contact: customer cannot be blank.)
#customer.errors.messages #=> :contacts=>["translation missing: en.activerecord.errors.models.customer.attributes.contacts.invalid"]}
When inspecting the models, indeed, #customer.contacts.first.customer
is nil. Which, somehow, makes sense, since the #customer has not
been saved, and thus has no id.
How can I build such associated models, then save/create them, so that:
No models are persisted if one is invalid,
the errors can be read out in one list, rather then combining the
error-messages from all the models,
and keep my code concise?
From rails api doc
If you are going to modify the association (rather than just read from it), then it is a good idea to set the :inverse_of option on the source association on the join model. This allows associated records to be built which will automatically create the appropriate join model records when they are saved. (See the ‘Association Join Models’ section above.)
So simply add :inverse_of to relationship declaration (has_many, belongs_to etc) will make active_record save models in the right order.
The first thing that came to my mind - just get rid of that validation.
Second thing that came to mind - save the customer first and them build the contact.
Third thing: use :inverse_of when you declare the relationship. Might help as well.
You can save newly created related models in a single database transaction but not with a single call to save method. Some ORMs (e.g. LINQToSQL and Entity Framework) can do it but ActiveRecord can't. Just use ActiveRecord::Base.transaction method to make sure that either both models are saved or none of them. More about ActiveRecord and transactions here http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html

Resources