Missing Attribute Error - ruby-on-rails

I'm working with Rails, and when setting up some tests I encountered:
ActiveModel::MissingAttributeError:
can't write unknown attribute `group_id`
I'm guessing the issue is in my relations. I have
class Group < ActiveRecord::Base
has_many :transactions
has_many :users
end
And
class Transaction < ActiveRecord::Base
belongs_to :group
belongs_to :user
end
And lastly,
class User < ActiveRecord::Base
belongs_to :group
has_many :transactions
end
I saw that someone had the same error because they were using has_one rather than belongs_to and needed to add an ID column to their DB. I'm using belongs_to though, so I don't think that's what I need? Any ideas?

Looks like you don't have the group_id column in your db.
You must remember that Rails is built on top of a relational database, which means that you can access "related" data by referencing a foreign_key.
When setting up a belongs_to / has_many association, the belongs_to table needs to have the appropriate foreign key (in your case group_id):
Your error doesn't state which model you're receiving the exception for; I would hazard a guess that it's User or Transaction.
--
To fix it, I would recommend creating a migration to add the group_id attribute to the appropriate model:
$ rails g migration AddGroupId
#db/migrate/add_group_id____________.rb
class AddGroupID < ActiveRecord::Migration
def change
add_column :users, :group_id, :integer
end
end
$ rake db:migrate

Unless you created the model you are referring to with a migration that had references, you will still need a migration in your database. An easy way to check if the database has one is to visit your some_project_root/db/schema.rb. If you don't see a the field you want there then you will have to generate one. The way you would do so is to run a rails g migration AddXidToY x_id:integer . It should set a field up for the id in the table you want.

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.

Rails Converting a has_many relationship into a has and belongs to many

I have a Rails app with the following relationship:
region.rb
class Region < ActiveRecord::Base
has_many :facilities
end
facility.rb
class Facility < ActiveRecord::Base
belongs_to :region
end
I want to expand functionality a bit so that facilities can belong to more than one region at a time. I believe I can do this with a has_many_through relationship but I'm needing some guidance on converting the existing has_many into a has many through. I understand how to create and wire up the join table, but how would I take existing data and translate it?
So for instance. On a facility object there is region_id, since the facilities can belong to more than one region I'd probably need a region_ids field and shovel the collection of regions into that column which should then populate the other side of the association via the join table. I have this part pretty much figured out as far as moving forward and wiring up the association. But I'm unsure as to how to take existing data and translate it over so the app doesn't break when I change the model association.
Any advice would be greatly appreciated.
I suggest you to always use has_many :through instead of HBTM.
To establish this kind of relation you'll need the following set up:
# region.rb
class Region
has_many :facility_regions
has_many :facilities, through: :facility_regions
end
# facility.rb
class Facility
has_many :facility_regions
has_many :regions, through: :facility_regions
end
# facility_region.rb
class FacilityRegion
belongs_to :facility
belongs_to :region
end
Also, of course, you'll need to create a migration:
rails g migration create_facility_regions facility_id:integer region_id:integer
# in this migration create a uniq index:
add_index :facility_regions, %I(facility_id region_id), name: :facility_region
rake db:migrate
UPD
As to migration from one database state to another one.
I think it should not be a problem.
1) Do not delete the relations you had before (leave has_many :facilities and belongs_to :region in models).
2) When new table is created and new associations added to the classes (which I showed) create a new migration:
rails g migration migrate_database_state
3) Write the script, which will create new records in db (to reflect the current state of things):
ActiveRecord::Base.transaction do
Facility.where.not(region_id: nil).find_each do |facility|
next if FacilityRegion.find_by(falicity_id: facility.id, region_id: facility.region_id)
FacilityRegion.create!(facility_id: facility.id, region_id: facility.region_id)
end
end
4) Put this script into last created migration and run it (or in console without migration, effect would be the same).
5) After script is successfully run, create new migration in which you delete region_id from facilities table and remove these associations definitions (has_many :facilities and belongs_to :region) from models.
It must be it. I might have made some typos or so, make sure I did not miss anything and
You need to add another model, a "middle guy" called FacilityRegion.rb, like this:
facility.rb
class Facility < ActiveRecord::Base
has_many :falicity_regions
has_many :regions, through: falicity_regions
end
facility_region.rb
class FacilityRegion < ActiveRecord::Base
belongs_to :region
belongs_to :facility
end
region.rb
class Region < ActiveRecord::Base
has_many :falicity_regions
has_many :facilities, through: falicity_regions
end
If you want to use belongs_and_has_many relationship, you need to:
rails g migration CreateJoinTableRegionsFacilities regions facilities
Then,
rake db:migrate
Now, your relationships should be:
Region.rb:
class Region < ApplicationRecord
has_and_belongs_to_many :facilities
end
Facility.rb
class Facility < ApplicationRecord
has_and_belongs_to_many :regions
end
In order to populate the new join table, you will need to in your console:
Region.all.find_each do |r|
Facility.where(region_id: r.id).find_each do |f|
r.facilities << f
end
end
Now, you can either leave the columns region_id and facility_id in Facility and Region table, respectively, or you can create a migration to delete it.

ActiveModel::MissingAttributeError: can't write unknown attribute `ad_id' with FactoryGirl

I have the following models:
class Ad < ActiveRecord::Base
belongs_to :page
has_one :image
has_one :logo
end
class Page < ActiveRecord::Base
has_many :logos
has_many :images
has_many :ads
end
class Image < ActiveRecord::Base
belongs_to :page
has_many :ads
end
And I have defined the following Factories:
factory :page do
url 'test.com'
end
factory :image do
width 200
height 200
page
end
factory :ad do
background 'rgb(255,0,0)'
page
image
end
When I try to do this:
ad = FactoryGirl.create(:ad) I get the following error ActiveModel::MissingAttributeError: can't write unknown attribute ad_id' right in the line where I decide the image association in the ad Factory.
What am I doing wrong here?
When you say:
has_one :image
Rails expects you to define an ad_id field at the images table. Given the way your associations are organised, I assume you have an image_id and a logo_id a the ads table so instead of:
class Ad < ActiveRecord::Base
belongs_to :page
has_one :image
has_one :logo
end
You probably mean:
class Ad < ActiveRecord::Base
belongs_to :page
belongs_to :image
belongs_to :logo
end
If that's not the case then you need to add ad_id columns to both Image and Logo.
If you are getting this error while running the specs, it may be the newly added field that are not migrated in the test environment. So migrate it in the test environment with the below command
rake db:migrate db:test:prepare
I ran into this same error and it took a while to figure out a fix. Just in case this helps someone else in the future, here's my scenario and what worked for me. Class names have been changed as this is for work:
I had 2 namespaced models:
Pantry::Jar
has_many :snacks, class_name: Pantry::Snack
accepts_nested_attributes_for :snacks
Pantry::Snack
belongs_to :pantry_jar, class_name: Pantry::Jar
When I would create a new jar with new snacks, I would get:
ActiveModel::MissingAttributeError: can't write unknown attribute `jar_id'
The fix was to change the has_many to be more explicit about the foreign key:
has_many :snacks, class_name: Pantry::Snack, foreign_key: :pantry_jar_id
Check your test database.
In my case, sometimes I did not modify the columns in the test database after modifying the development database after an incorrect migration.
If you're getting the error while testing and you're using different repositories accessing the same test database, check if the migrations are equal among projects.
If you reset the database each time you're running the tests, only migrations available in that repository will be run, hence triggering the error related to the missing foreign key
ActiveModel::MissingAttributeError: can't write unknown attribute ad_id'
You should have the migration file with something like:
class AddAdReferenceToImage < ActiveRecord::Migration[5.0]
def change
add_reference :images, :ad, foreign_key: true
end
end
In my case this message appeared because I tried setting a reference (with a column name different from the class name) like this:
Entity.create(reference: some_obj).save!
Although I'm not sure what exactly caused the issue, I was able to fix it by using the column name instead of the reference name:
Entity.create(reference_id: some_obj.id).save!

has_many, belongs_to relation in active record migration rails 4

I have a User model and a Task model. I have not mentioned any relation between them while creating them.
I need to establish that User has_many Tasks and a Task belongs_to User through a migration
What would be the migration generation command for establishing that relationship?
You could call:
rails g model task user:references
which will generates an user_id column in the tasks table and will modify the task.rb model to add a belongs_to :user relatonship. Please note, you must to put manually the has_many :tasks or has_one :task relationship to the user.rb model.
If you already have the model generated, you could create a migration with the following:
rails g migration AddUserToTask user:belongs_to
which will generate:
class AddUserToTask < ActiveRecord::Migration
def change
add_reference :tasks, :user, index: true
end
end
the only difference with this approach is, the belongs_to :user relationship in the task.rb model won't be created automatically, so you must create it for your own.
To answer the question, "What would be the migration generation command for establishing that relation?"( Meaning, how do you add a migration for existing models with a relationship like User has_many Tasks & Task belongs_to User)
The the easiest way for me to remember is like this:
>rails g migration AddUserToTask user:belongs_to
or
>rails g migration AddUserToTask user:references
:belongs_to is just an alias of :references, so either will do the same thing.
Doing it this way, the command will infer the name of the table from the migration name, set up a change method that will add the column for relationship, and configure it to be indexed:
class AddUserToTask < ActiveRecord::Migration
def change
add_reference :tasks, :user, index: true
end
end
After generating that you:
>rake db:migrate
Finally, you still have to add the usual relations to your models, as is stated in the other answers, but I think this is the right answer to your question.
This is how the migration should be created normally:
rails g scaffold child parent:references
I forgot to add parent:references when I created the table, what should I do?
Option 1: Destroy the table and start over
If you don't have a lot defined in the model/db about the child table. Your best bet might just be to run rails destroy scaffold child, and then run
rails g scaffold child parent:references over it. Be sure to add the line drop_table :children if table_exists? :children before create table in the file that creates the new table. (That way if anyone pulls your code they can just run the migrations and be done.) However, it seems more probable that you will have data you don't want to lose in the child model already.
In that case:
Option 2: Write a migration to add the references
rails g migration add_parent_refs_to_child
## XXXXXXXXXXXXXX_add_parent_refs_to_child.rb
class AddParentRefsToChild < ActiveRecord::Migration
def change
add_reference :child, :parent, index: true
end
end
See add_reference
Additionally, don't forget to make sure the parent model has_[one | many] :children, and that the child model belongs_to :parent.
How not to do it:
You may be tempted to add the parent_id manually. Don't. Conventionally this sort of operation is handled through a migration, or within the initial table creation. Manual addition will detract from the maintainability of the project.
The Ruby on Rails guide to association has more information on the subject.
There is no special migration command that would be used.
In your User model you will put
class User < ActiveRecord::Base
has_many :tasks
end
class Task < ActiveRecord::Base
belongs_to :user
end
In the corresponding migration file for the tasks you have the following field added user_id
Take a look at this guide
The migration will add the user's id to the task table so they know about each other
rails g migration AddUserIdToTask user_id:integer
then
rake db:migrate
And after update your controllers and views so that tasks can't be created on their own but must correspond to a user
The Relationship in Rails is taken care by model not by Rails.
So you just need to define this relationship in your model:
class User < ActiveRecord::Base
has_many :tasks
end
class Task < ActiveRecord::Base
belongs_to :user
end
And just make sure that a user_id field is present in the migration for creating the "tasks" table.

Rails: Changing relationship after initial migration

I am having some trouble finding a good answer to my question on rails relationship creation.
If I already ran the initial migration for my user model and my comment model Without declaring a relationship (ie: a user has_many comments, and comments belong_to user) how do I define that relationship later on?
Can I simply:
1-add the user_id column to Comments,
2-declare the relationship and
3-run the new add_user_id_to_comment migration file?
Will this work? If not, how would I go about changing the relationship after already having ran the initial migration for the models? Thank you so much for your help.\
Rails 3.1, Ruby 1.8.7
You can just add the reference in another migration, using the change_table migration (documentation):
change_table :comments do |t|
t.references :user
end
Then just add the associations to your models.
class User < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :user
end

Resources