Rails Has Many Association - ruby-on-rails

I have few question that bugs me off and need to be answered. Everything is related to the following tutorial Two Many-to-Many
Question 1
Does the join table using has_many need to have an id? or its best practice to remove the id? and add an index and using the two other primary key and set it unique and together?
Question 2
How can it be done in the migration of creating a table?
Question 3
After doing these relationship model and updating the data. I would like to create a new set of data everytime it is updated (to preserve the data). How would a controller would look in the update, new, create model?
Question 4
In the the middle table, I would like to set attributes such has a visible true, or false, how can I set also not just the third table but also the second table arguments

First ... a word of caution: That railscast is very old. There may be syntactical things in that episode that have been dated by new versions of rails.
Question 1
If you are using the has_many through method then you have to have an id column in the join model because you are using a full blown model. As Ryan mentions in the episode, you'll choose this method if you need to track additional information. If you use the has_and_belongs_to_many method, you will not have an id column in your table.
If you want to achieve a check where you do not allow duplicates in your many-to-many association (ie allow the pairing of item a with item b and again allowing another record of item a to item b), you can use a simple validates line with a scope:
validates_uniqueness_of :model_a_id, :scope => [:model_b_id]
Question 2
You can add indices in your migrations with this code
add_index :table_name, [ :join_a_id, :join_b_id ], :unique => true, :name => 'by_a_and_b'
This would be inserted into the change block below your create_table statement (but not in that create_table block). Check out this question for some more details: In a join table, what's the best workaround for Rails' absence of a composite key?
Question 3
I'm not completely clear on what you're looking to accomplish but if you want to take some action every time a new record is inserted into the join model I would use the after_create active record hook. That would look something like this.
class YourJoinModel < ActiveRecord::Base
after_create :do_something
def do_something
puts "hello world"
end
end
That function, do_something, will be called each time a new record is created.
Question 4
Using the has_many through method will give you access to the additional attributes that you defined in that model on both sides of the relationship. For example, if you have this setup:
class Factory < ActiveRecord::Base
has_many :widgets, :through => :showcases
end
class Widget < ActiveRecord::Base
has_many :factories, :through => :showcases
end
class Showcases < ActiveRecord::Base
belongs_to :factory
belongs_to :widget
attr_accessiable :factory_id, :widget_id, :visible
end
You could say something like
widget = Widget.first
shown = widget.showcases
shown.first.visible
or
shown = widget.showcases.where( :visible=> true )
You can also reach to the other association:
shown.first.factory

The reason for having an id column in an association is it gives you a way of deleting that specific association without concerning yourself with the relationship it has. Without that identifier, associations are hard to define outside of specifying all foreign keys.
For a trivial case where you have only two components to your key, this isn't that big a differentiator, but often you will have three or more as part of your unique constraint and there's where things get tricky.
Having an id also makes the relationship a first-class model. This can be useful when you're manipulating elements that have associated meta-data. It also means you can add meta-data effortlessly at a later date. This is what you mean by your "Question 4". Add those attributes to the join model.
Generally the join model is created like you would any other model. The primary key is the id and you create a series of secondary keys:
create_table :example_things |t|
t.integer :example_id
t.integer :thing_id
end
add_index :example_joins, [ :example_id, :thing_id ], :unique => true
add_index :example_joins, :thing_id
The main unique index serves to prevent duplication and allows lookups of key-pairs. The secondary serves as a way of extracting all example_id for a given thing_id.
The usual way to manipulate meta-data on the join model is to fetch those directly:
#example_things = #example.example_things.includes(:thing)
This loads both the ExampleThing and Thing models associated with an Example.

Related

Rails 5.0 - How to check if record is associated on another table?

Before destroying a record, I'd like to check if there are any uses of it on other tables, even if the record itself has no knowledge of said uses.
For example, lets say I have a table of cost_centers, and I have a table of areas.
An area has a default cost_center. cost_centers have no connection with areas.
Which kind of validation can I use in order to prevent the user from destroying a cost_center, in order to keep an area consistent?
In other words, how can I search through the database to find out wether that record is a foreign key of some other record on any other tables?
When you designed your database, you've probably set all the references up.
In your migrations, it would look like this: t.references :cost_center.
If so, your Cost Center Model could have a has_one relationship to each table holding the reference which, in your example, would be has_one :area.
Then, to check if it is actually used, you could have a before_destroy callback to a method that checks if any has_one definition is not null:
class CostCenter < ActiveRecord::Base
has_one :area
before_destroy :check_usage
def check_usage
!self.area.nil?
end
end

rails inverse of using 'references' in generators

When using model and migration generators in rails, you can use 'references' keyword as it was a data type to indicate that that field is a foreign key to another model, that is another table.
In this example I do so with an author field inside a book table (actually the field won't be exactly 'author').
rails g model book title publication_date:date author:references
Looking at the migrations and models created, we can better understand what are the information that rails has guessed from the command (ok this is not a part of the question but more a summary of the logic and the state of the art of this command):
Migration
class CreateBooks < ActiveRecord::Migration
def change
create_table :books do |t|
t.string :title
t.references :author, index: true
t.date :publication_date
t.timestamps
end
end
end
Here rails uses again the 'references' method as it was a data type, but it actually means that we are a layer on top on the 'basic' data structure level, infact referencing an author means, at the data level, to add an 'author_id' and not an 'author' column . And it does right so (i've also checked on schema.rb to have confirmation).
In addition to this, it also puts an index: true that is a good practice from a performances point of view.
Model
Calling the generator in such a way also 'does something' on the model:
class Book < ActiveRecord::Base
belongs_to :author
end
that is, it adds the 'belongs_to :author'.
Notice that you can even create the book model and migration with the references clause to author before having author created. It enforces the consistence at the end (if it does at all, on such things).
Question
My question is on what rails doesn't do, that is: the has_many clause on the author model.
1) since normally it does exist before having created the book model, it should be easy for rails to add has_many to this. Is there any parameter to say rails to do so?
2) because of the fact that models can reference other models even if they are not yet created, another possibility would be declaring some kind of 'has_many' reference during author creation, is it possibile in some way?
There's no way of knowing where to put it, you'd have to specify it.
Such generators are designed to save time, and making such a thing saves too little or none. For instance, you'll have to specify explicitly the target class and whether it is has_one or has_many. That would subtract some of the usefulness of this, resulting in a very small positive or even a negative value.
I assume you mean that associations are available even on unsaved models. Yep, they are, but they're not too reliable. It works quite simple: an unsaved object has a collection for each association which stores temporary objects. When the main object is saved, new associated objects in these collections (not persisted yet) are created. An example:
a = Author.new(name: "Charles Dickens")
a.books << Book.new(title: "Oliver Twist")
a.save
That would save the new author to the database, and since his id would be known, it would then create a new book with that author_id. This behaves a little odd when it comes to has_many ... through: ... and possibly some other cases, so use this carefully.

Active Record joined-grouped query on PostgreSQL

I am working on a rails project for a while and I juste encountered (what I think is) a small problem.
To make it simple, I have 3 model like this:
class House < ActiveRecord::Base
has_many :house_style_relationships
end
class HouseStyleRelationship < ActiveRecord::Base
attr_accessible :percentage
belongs_to :style
belongs_to :house
end
class Style < ActiveRecord::Base
has_many :house_style_relationships
end
So the HouseStyleRelationship model simply let us know the style percentage of an House.
For example an House can be 70% Modern and 30% Classic.
What I need to do is to get the average of each styles for all the houses.
To do that I used this working query under SQLite:
houses_count = Houses.count
HouseStyleRelationship.joins(:style).select("style.name, SUM(house_style_relationships.percentage)/#{houses_count} as percentage").group("styles.id")
But when I decided to push all this stuff on Heroku, I got a problem with PostgreSQL.
In fact, to create an HouseStyleRelationship object (I don't even need), ActiveRecord ask for a HouseStyleRelationship.id which make the Group By crashing (with the query).
(PostgreSQL don't want to group records with different ids)
Do you have a solution to prevent ActiveRecord to generate model instances as answer and so remove the HouseStyleRelationship.id from the query ?
(I can't use SELECT DISTINCT since I need the SUM() calculation)
Thanks in advance !
You need to create your join table such that it has no primary key. The create_table statement in your migration should look something like this.
create_table :houses_style_relationships, :id => false do |t|

How can I stop collection<<(object, …) from adding duplicates?

Given a Dinner model that has many Vegetable models, I would prefer that
dinner.vegetables << carrot
not add the carrot if
dinner.vegetables.exists? carrot
Yet it does. It will add a duplicate record every time << is called.
There is a :uniq option you can set on the association, but it only FETCHES AND RETURNS one result if there are multiples, it doesn't ENFORCE unique values.
I could check for exists? every time I add an obj to a collection, but that is tedious and error-prone.
How can I use << freely and not worry about errors and not check for already existing collection members every time?
The best way is to use Set instead of Array:
set = Set.new
set << "a"
set << "a"
set.count -> returns 1
You can add an ActiveRecord unique constraint if you have a join model representing a many-to-many relationship between dinners and vegetables. That's one reason I use join models and has_many :through as opposed to has_and_belongs_to_many. It's important to add a uniqueness constraint at the database level if possible.
UPDATE:
To use a join model to enforce constraint you would need an additional table in your database.
class Dinner
has_many :dinner_vegetables
has_many :vegetables, :through => :dinner_vegetables
end
class Vegetable
has_many :dinner_vegetables
has_many :dinners, :through => :dinner_vegetables
end
class DinnerVegetable
belongs_to :dinner
belongs_to :vegetable
validates :dinner_id, :uniqueness => {:scope => :vegetable_id} # You should also set up a matching DB constraint
end
The other posters' ideas are fine, but as another option you can also enforce this on the database level using e.g. the UNIQUE constraint in MySQL.
After a lot of digging, I've discovered something cool: before_add, which is an association callback, which I never knew even existed. So I could do something like this:
has_many :vegetables, :before_add => :enforce_unique
def enforce_unique(assoc)
if exists? assoc
...
end
Doing this at the DB level is a great idea if you REALLY NEED this to be unique, but in the case that it's not mission critical the solution above is enough for me.
It's mostly to avoid the icky feeling of having extra records lying around in the db...

Mongoid: 'update_attribute' with [models, ...] vs with [model_ids, ...]

I was writing some tests and I ran into something I'm trying to understand.
What is the difference underneath when calling:
.update_attributes(:group_ids, [group1.id, group2.id])
vs
.update_attributes(:groups, [group1, group2])
These 2 models in question:
group.rb
class Group
include Mongoid::Document
has_and_belongs_to_many :users, class_name: "Users", inverse_of: :groups
end
user.rb
class User
include Mongoid::Document
has_and_belongs_to_many :groups, class_name: "Group", inverse_of: :users
end
test code in question:
g1 = create(:group)
u1 = create(:user, groups: [g1])
g1.update_attribute(:users, [u1])
# at this point all the associations look good
u1.update_attribute(:group_ids, [g1.id])
# associations looks good on both sides when i do u1.reload and g1.reload
u1.update_attribute(:groups, [g1])
# g1.reload, this is when g1.users is empty and u1 still has the association
Hope I made sense, thanks
Are all your attributes white listed properly?
Without schema for the models, your join object, and the actual tests I'm grasping at straws, but based purely on that example my guess would be that the first model contains an attribute that is mapping to an unintended field on your second model, and overwriting it when you pass an entire object, but not when you specify the attribute you want updated. Here's an example: (I'm not assuming you forgot your join table, I'm just using that because its the first thing that comes to mind)
so we create 2 models, each that have a field that maps to user_id
group.create(id:1, user_id:null)
group_user.create(id:1, group_id: 1, user_id:null)
group.update_attributes(user_id: (group_user.id))
So at this point, when you call group.users, it checks for a user with the id of 1, because that's the id of the group_user you just created & passed it, and assuming you have a User with that ID in your database, the test passes.
group_user.update_attributes(group_id: group.id)
In this case the method ONLY updates group_id, so everything still works.
group_user.update_attributes(group_id: group, user_id: group)
In this case you pass an entire object through, and leave it up to the method to decide what fields get updated. My guess is that some attribute from your group model is overwriting the relevant attribute from your user model, causing it to break ONLY when NO user_ids match whatever the new value is.
Or an attribute isn't white listed, or your test is wonky.

Resources