Rails ActiveRecord associations - ruby-on-rails

Let's say you have a one-to-many relationship between Users and Orders (where one user can have many orders). Is it possible to create a User object, add orders to it, and save it in one go? Or do you have to save the User object first so that an ID is generated before you can save the orders collection?

You can check Railscasts for that. Here's an example of a nested model - Nested Model Form Part 1

Since the User is a new record, orders will be saved automatically once the user is saved.

I was able to solve this by using the "build" method. From the ActiveRecord::Associations::ClassMethods documentation:
Returns one or more new objects of the
collection type that have been
instantiated with attributes and
linked to this object through a
foreign key, but have not yet been
saved.

Related

Overriding shovel method in rails in association case

According to the has_many documentation, the "shovel" method collection<<(object, …)
Adds one or more objects to the collection by setting their foreign keys to the collection's primary key. Note that this operation instantly fires update SQL without waiting for the save or update call on the parent object, unless the parent object is a new record.
If you want to construct a new-record without saving it to the database just yet, use collection.build.
Returns one or more new objects of the collection type that have been instantiated with attributes and linked to this object through a foreign key, but have not yet been saved.
Using Club and Member as example models:
club = Club.find(params[:id])
club.members.build(member_attributes) # member is not saved
club.save # saves club and members
BUT WHAT IF
I would like to use << to create associations and not firing SQL at all. How can I override << to behave like in scenario when the parent object is a new record?

How can I get a list of the associated models that were updated during the save of an instance of ActiveRecord?

I'm passing params to a model instance and saving it with update_attributes. It is associated with several other models and I've configured it to update some of these with accepts_nested_attributes_for.
This is very nice and clean as I only have to update the one model, but I'd like to get a list of the associated(nested) models that were also updated so that I can give the user feedback about some of the fields that have changed.
Is there a way to do this, or am I approaching the problem in the wrong way?
I've found a solution to my question, maybe not the best one but it will work.
For a list of models that are associated and have accepts_nested_attributes_for configured we go:
associations = ModelClass.reflect_on_all_autosave_associations()
Each of these association objects has a name attribute(the association name), which can be used to access the association on the instance, and then we can check whether this association has changed:
associations.each{|assoc|
model_instance.send(assoc.name).changed?
}
It should be noted that we cannot use update_attributes with this solution, as all the models are saved before we can check whether anything has changed. So we have to assign_attributes and save the model in separate steps:
model_instance.assign_attributes(params[:model_instance])
// check for changes on associations here
model_instance.save()

Displaying ModelChoiceFields on both sides of a foreignkey relationship

I have two models with a foreign key relationship between them. In the admin, the edit page for the model with the foreign key relationship described (Model No. 1) displays a ModelChoiceField. The page for the other side of the relationship (Model No. 2) displayed nothing, until I added the first model to the ModelAdmin as an inline. The inline gives me the option of creating a new object from Model No. 1.
I want to add a ModelChoiceField to the inline on Model No. 2 so that users can choose between creating a new object or selecting from a list of pre-existing ones.
Ideally, I would also be able to use a filter to populate the new ModelChoiceField for Model No. 1 objects.
Okay, asking this question got me nothing but the cool tumbleweed badge for my profile. I eventually discovered the following solution. It's simpler than I expected but it left me asking another question here because, once implemented, selecting from the ModelChoiceField on the admin page and saving does not create the foreign key relationship as expected.
Anyway, on the the solution:
My Art model contains the boolean field "has_storypak" to indicate whether it has a relationship to and instance of the Storypak model. Since I expected Art instances to only related to one Storypak while Storypaks could have many associated artworks, I wrote the following custom field to only contain instances for which the value for "has_storypak" was False.
class RuntimeArtSelectForm(forms.ModelForm):
storypak_orphan = forms.ModelChoiceField(label="Art",
queryset=Art.objects.filter(has_storypak=False))
class Meta:
model = Art
fields = ('storypak_orphan',)
Next I added this form to an inline form for the Art model...
class ArtInline(admin.TabularInline):
model = Art
form = RuntimeArtSelectForm
... and included the ArtInline in the ModelAdmin for Storypak. This gave me the drop-down containing the filtered list of model objects I was looking for. However I still have the problem mentioned above and this open question looking for a solution.

In Rails, how do you swap a new object for an existing one?

I have the following nested model relationship:
Countries (id, name)
Provinces (id, country_id, name)
Cities (id, province_id, name)
I have validates_uniqueness_of constraint on the name fields for each model in the relationship and a unique index on the name columns in the database.
I want to swap a new object created with the same name as an existing record at some point before it's validated. In other words, if a user attempts to add a city, province, country combination that has already been added, I want to country model to return a reference to the corresponding existing model records instead of failing validation before save.
I'm having trouble using the model callbacks (after_initialize, before_validation, etc.) and I wasn't able to get Country.find_or_initialize_by_name to work with the nested models... any suggestions?
What you are trying to do sounds pretty hard and will probably require you to know a lot of the internal implementation details of ActiveRecord::Base.
Instead, could you do something like this?
#country = Country.find_or_initialize_by_name(params[:name])
...
#country.save
EDIT:
ActiveRecord has find_or_create_by_XXX and find_or_initialize_by_XXX functions built in, so there is no need to add a function to the model. For more info see the "Dynamic attribute-based finders" section of http://api.rubyonrails.org/classes/ActiveRecord/Base.html

How does the Rails' single table inheritance works?

I have a user table, and a teacher that I newly created. The teacher is sub class of user, so, I use scaffold generator to generate the teacher table, than, I modify the model to do teacher is subclass of user. After all that, I did a db:migrate. Then, I go to
http://localhost:3000/teachers/new
It shows an error:
undefined method `teacherSalary' for #<Teacher:0x103331900>
So, my question is what did I do wrong? I want to create a page for doing user register, the user can ONLY be a teacher / student. But I can't add a teacher record ... ... Moreover, I go to
http://localhost:3000/users/new
I want to have a combo box that allow user register their user to be a "teacher" or a "student". But everything seems not work like I expected. What I need to do? Thank you very very much for your help.
Within your database you should have a single table called users. This table should have a string column which by default is called type. If you use another name for this column then you will have to set the inheritance column name manually using self.inheritance_column = "column_name"
Within your application you have three models, User, Student and Teacher. User inherits from ActiveRecord::Base as usual, Student and Teacher both inherit from User.
You should then be able to instantiate new Teacher and Student objects. Internally this works by writing the model name to the type field on the user tables and then when you use Student.find it adds a clause to the SQL to only return rows where the type = 'Student'
You can add shared behaviour to the User class, e.g. validations etc then add additional behaviour to the inherited classes.
A fuller description of how STI works can be found in Martin Fowlers Book(Patterns of Enterprise Application Architecture).
I found this definition really handy:
STI means one table contains the data of more than one model, usually differentiated by the "type" column. ("users" table contains data for the models "Teacher", ""Pupil", "Employee", "Assistant", etc.)
Keeps similar models in the same table instead of creating new ones.
A Polymorphic Association means that one model can be associated with more than one other model(Comment can belong to post, image, file, user_type...)
To prevent foreign key conflicts, the association is reperesented with the *_id and *_type columns instead of only *_id.
For what you have here , I am not sure if STI is the best way go . STI should generally be used when there is a OO like inheritance and the Models have the same Attribute but different behaviour . In your case Teacher and Student can sure have a few shared attributed , but they are also bound to have different ones as well .
You might want to experiment with a polymorphic association as well .

Resources