Rails updating a many to many record - ruby-on-rails

I'm getting stuck at trying to update an existing many to many record.
Project model:
class Project < ActiveRecord::Base
belongs_to :assignment
belongs_to :programmer
end
Programmer model:
class Programmer < ActiveRecord::Base
has_many :projects
has_many :assignments, :through => :projects
end
Assignment model:
class Assignment < ActiveRecord::Base
has_many :projects
has_many :programmers, :through => :projects
end
so I have data linked up like so:
p = Programmer.create(:name => "Mike")
p.assignments.create(:name => "homework4")
p.assignments[0] = Assignment.find_or_create_by_name("homework1")
p.save
so as you can see, I'm trying to update the association of Mike's first hw to "homework1". All the homework assignments are already in the assignments table so it shoud just find "homework1" and assign it to mike. unfortunately, when I type the third line there are no errors, but it doesn't update it. In memory, p.assignments == homework1, but in the DB it's still the same(even after p.save). The project's join table isn't changed at all.
the logs of mysql show this command being generated whenever I enter the 3rd line.
SELECT "assignments".* FROM "assignments" WHERE "assignments"."name" = 'homework1' LIMIT 1
there's no Update anywhere.... what am I doing wrong?
UPDATE
So I found out that I could just reference the join table directly to edit the links. Something along the lines of:
proj = p.projects.first
proj.assignment_id = 12
proj.save!

If you just want a reference to the object, then you need to edit your migration scripts (db/migrate). An example:
def self.up
create_table :configurations do |t|
t.string :name
t.references :project # This store just the id of the object.
t.timestamps
end
end
Don't forget to type:
rake db:migrate

Related

How can I migrate a has_many / belongs_to relationship in rails to has_and_belongs_to_many?

I have an existing rails 6 application where I have two models:
class Reservation << ApplicationRecord
# ...
has_many :charges
# ...
end
class Charge << ApplicationRecord
# ...
belongs_to :reservation
# ...
end
I want to refactor it to this:
class Reservation << ApplicationRecord
# ...
has_and_belongs_to_many :charges
# ...
end
class Charge << ApplicationRecord
# ...
has_and_belongs_to_many :reservation
# ...
end
What I want to know is how to write that migration? There's already data in the table, so I need to retain existing charges whose reservation IDs are set and keep the link.
Be careful here, and make sure you can revert if there's a mistake, so you don't lose your data!
First you need to create the join table with a migration. You can create the migration from the command-line with:
rails g migration create_charges_reservations
this should create the template of the migration for you in db/migrate, which you'll populate according to your need like this:
class CreateChargesReservations < ActiveRecord::Migration[6.0]
def change
create_table charges_reservations do |t|
t.integer :charge_id
t.integer :reservation_id
end
end
end
run the migration from the command line:
rails db:migrate
Now make a join model:
# app/models/charges_reservation.rb
class ChargesReservation < ApplicationRecord
belongs_to :charge
belongs_to :reservation
end
Now you have to migrate the existing data, so from the rails console:
Charge.all.each{|c| ChargesReservation.create(charge_id: c.id, reservation_id:c.reservation_id)}
And finally change the associations to habtm associations as you have indicated in your question
# charge.rb
has_and_belongs_to_many :reservations
#reservation.rb
has_and_belongs_to_many :charges
Oh and you can delete the reservation_id column in the charges table with another migration, once you are sure everything is working correctly. This is the point where you could create a problem b/c you're destroying data, so be sure that the join table was correctly populated.
You actually don't need the join model any longer either, it was just a convenient way to populate the join table. So you can delete the charges_reservation.rb model.

inserting data through HABTM

I have a HABTM association between Users and Workspaces, which also has groups. The problem is I can't insert data intro groups from users.
class User < ActiveRecord::Base
has_and_belongs_to_many :workspaces #, dependent: :destroy
has_many :groups, through: :workspaces
end
class Workspace < ActiveRecord::Base
has_and_belongs_to_many :users, dependent: :destroy
has_many :groups, dependent: :destroy
end
This is the join table migration:
class CreateUsersAndWorkspaces < ActiveRecord::Migration
def change
create_table :users_workspaces, id: false do |t|
t.belongs_to :user, index: true
t.belongs_to :workspace, index: true
end
end
end
In rails console, when I try to create new group:
u.groups.create!(name: "txt", workspace_id: 1 )
(0.1ms) begin transaction
(0.2ms) rollback transaction
ActiveRecord::HasManyThroughNestedAssociationsAreReadonly:
Cannot modify association 'User#groups' because it goes
through more than one other association.
Is there a way to create groups from the user?
EDIT:
With help from #evedovelli I could make it work. but since user.workspace was a ActiveRecord_Associations_CollectionProxy, it was not a workspace class, but a collection. appending 'first', solved the problem, so the final code is:
u.workspaces(id: 1).first.groups.create!(name: "txt")
Now suppose I have more associations:
u.workspaces(id: 1).first.groups(id: 2).first.modelN(id:3).first.modelNplus1.create!(name: "txt")
my final question is, is this the correct way?
The problem is that Rails cannot find out to what user's workspace the group should be added to (even if you to specify the worspace_id on creation as you did).
As mentioned in the error message, the nested associations for HasManyThrough are read only. So you can read groups directly from an user, but you can't create them.
Since you known the workspace_id when creating the group, you can simply create the group with:
u.workspaces.find_by_id(1).groups.create!(name: "txt")
This way it will create the group for the right user's workspace.
And you should be able to keep going with that through other associations like this:
u.workspaces.find_by_id(1).groups.find_by_id(2).modelN.find_by_id(3).modelNplus1.create!(name: "txt")

Change reference to different model rails migration

How can I migrate the Referral table to point to point to the PatientProfile id instead of the User id for an existing database (i.e. would like to migrate all existing rows)?
class User
belongs_to :profile, polymorphic: true
has_many :referrals
class PatientProfile
has_one :user, as: :profile
class Referral
belongs_to :user
I could accomplish this by simply making a migration and renaming the user_id column to be patient_profile_id and changing the reference in the model, however that would not migrate over existing records.
I wouldn't simply change the column name. I'd do something like this
class AddPatientProfileToReferrals < ActiveRecord::Migration
def up
add_column :referrals, :patient_profile_id
add_index :referrals, :patient_profile_id
Referral.reset_column_information
Referral.includes(:user).find_each do |referral|
referral.update_attribute(:patient_profile_id, referral.user.profile_id)
end
remove_column :referrals, :user_id
end
end
You probably want to make sure the referrals have a user, and you can simply reverse the process to write the down method.

Associations and (multiple) foreign keys in rails (3.2) : how to describe them in the model, and write up migrations

I have 3 models: Question, Option, Rule
Question has_many options;
Option needs a foreign key for question_id
Rule table consists of 3 foreign_keys:
2 columns/references to question_ids -> foreign keys named as 'assumption_question_id' and 'consequent_question_id'
1 column/reference to option_id -> foreign key named as option_id or condition_id
Associations for Rule:
Question has_many rules; and
Option has_one rule
I want to understand how to write up migrations for this, and how that associates to the 'has_many'/'belongs_to' statements I write up in my model, and the ':foreign_key' option I can include in my model.
I had this for my Option migration, but I'm not sure how the "add_index" statement works in terms of foreign keys, and how I can use it for my Rule migration: (my Question and Options models have appropriate has_many and belongs_to statements - and work fine)
class CreateOptions < ActiveRecord::Migration
def change
create_table :options do |t|
t.integer :question_id
t.string :name
t.integer :order
t.timestamps
end
add_index :options, :question_id
end
end
Thank you for the help!
Note: I have found this way to solve the problem.Kindness from China.
If you have RailsAdmin with you,you may notice that you can see all rules of one question as long as one field of both question fields(assumption_question_id,consequent_question_id) equals to id of the question.
I have done detailed test on this and found out that Rails always generates a condition "question_id = [current_id]" which make to_sql outputs
SELECT `rules`.* FROM `rules` WHERE `rules`.`question_id` = 170
And the reason that the following model
class Question < ActiveRecord::Base
has_many :options
# Notice ↓
has_many :rules, ->(question) { where("assumption_question_id = ? OR consequent_question_id = ?", question.id, question.id) }, class_name: 'Rule'
# Notice ↑
end
makes Question.take.rules.to_sql be like this
SELECT `rules`.* FROM `rules` WHERE `rules`.`question_id` = 170 AND (assumption_question_id = 170 OR consequent_question_id = 170)
Is that we have not yet get ride of the annoy question_id so no matter how we describe or condition properly, our condition follows that "AND".
Then,we need to get ride of it.How?
Click here and you will know how,Find sector 8.1,and you can see
Article.where(id: 10, trashed: false).unscope(where: :id)
# SELECT "articles".* FROM "articles" WHERE trashed = 0
Then lets do it:
class Question < ActiveRecord::Base
has_many :options
# Notice ↓
has_many :rules, ->(question) { unscope(where: :question_id).where("assumption_question_id = ? OR consequent_question_id = ?", question.id, question.id) }, class_name: 'Rule'
# Notice ↑
end
class Rule < ActiveRecord::Base
belongs_to :option
belongs_to :assumption_question, class_name: "Question", foreign_key: :assumption_question_id, inverse_of: :assumption_rules
belongs_to :consequent_question, class_name: "Question", foreign_key: :consequent_question_id, inverse_of: :consequent_rules
end
class Option < ActiveRecord::Base
belongs_to :question
has_one :rule
end
All done.
Finally
This is my first answer here at stackoverflow,and this method is never found anywhere else.
Thanks for reading.
add_index adds an index to column specified, nothing more.
Rails does not provide native support in migrations for managing foreign keys. Such functionality is included in gems like foreigner. Read the documentation that gem to learn how it's used.
As for the associations, just add the columns you mentioned in your Question to each table (the migration you provided looks fine; maybe it's missing a :rule_id?)
Then specify the associations in your models. To get you started
class Question < ActiveRecord::Base
has_many :options
has_many :assumption_rules, class_name: "Rule"
has_many :consequent_rules, class_name: "Rule"
end
class Rule < ActiveRecord::Base
belongs_to :option
belongs_to :assumption_question, class_name: "Question", foreign_key: :assumption_question_id, inverse_of: :assumption_rules
belongs_to :consequent_question, class_name: "Question", foreign_key: :consequent_question_id, inverse_of: :consequent_rules
end
class Option < ActiveRecord::Base
belongs_to :question
has_one :rule
end
Note This is just a (untested) start; options may be missing.
I strongly recommend you read
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
http://guides.rubyonrails.org/association_basics.html
Edit: To answer the question in your comment
class Option < ActiveRecord::Base
belongs_to :question
# ...
The belongs_to tells rails that the question_id column in your options table stores an id value for a record in your questions table. Rails guesses the name of the column is question_id based on the :question symbol. You could instruct rails to look at a different column in the options table by specifying an option like foreign_key: :question_reference_identifier if that was the name of the column. (Note your Rule class in my code above uses the foreign_key option in this way).
Your migrations are nothing more than instructions which Rails will read and perform commands on your database based from. Your models' associations (has_many, belongs_to, etc...) inform Rails as to how you would like Active Record to work with your data, providing you with a clear and simple way to interact with your data. Models and migrations never interact with one another; they both independently interact with your database.
You can set a foreign key in your model like this:
class Leaf < ActiveRecord::Base
belongs_to :tree, :foreign_key => "leaf_code"
end
You do not need to specify this in a migration, rails will pull the foreign key from the model class definition.

Rails 3 Migration update_all with relation / join

Given Following Models
class User
has_many :conversations
end
class Conversation
belongs_to :user
has_many :messages
end
class Message
belongs_to :conversation
end
I want to remove the Conversation model and migrate the reference to a user to Message.
Normally I would use something like
add_column :messages, :user_id, :integer
Message.reset_column_information
Message.all.each do |message|
message.user_id = message.conversation.user_id
end
remove_column :messages, :conversation_id
But in Production migrations run after the code was updated. Therefore this would throw an error.
Probably I just need a little hint.
Message should still have 'conversation_id' as a field even though you removed the belongs: to relationship right?
So what if you did:
message.user_id = User.find_by_id(message.conversation_id).user_id
A good solution is to define a temporary model inside the migration -> Source: Rails Guides

Resources