Rails PG::ForeignKeyViolation: ERROR: insert or update on table - ruby-on-rails

I've got two models with simple belongs_to has_many relation, as follows:
class Property < ApplicationRecord
belongs_to :portfolio, optional: true
end
class Portfolio < ApplicationRecord
has_many :properties, dependent: :nullify
end
Which were created by migrations:
class CreateProperties < ActiveRecord::Migration[6.0]
def change
create_table :properties do |t|
t.string :name, null: false
t.string :status, null: false
t.references :portfolio, foreign_key: true
t.timestamps
end
end
end
class CreatePortfolios < ActiveRecord::Migration[6.0]
def change
create_table :portfolios do |t|
t.string :name, null: false
t.timestamps
end
end
end
When I want to create new Property with portfolio_id: 1 which doesn't exist I'm getting an error:
PG::ForeignKeyViolation: ERROR: insert or update on table "properties" violates foreign key constraint "fk_rails_760fb8258a" DETAIL: Key (portfolio_id)=(1) is not present in table "portfolios".
How to handle such error and display to user information such as "portfolio_id doesn't exist" ?
Create action is pretty standard:
property_controller.rb
def create
#property = Property.new(property_params)
respond_to do |format|
if #property.save
format.html { redirect_to #property, notice: 'Property was successfully created.' }
format.json { render :show, status: :created, location: #property }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: #property.errors, status: :unprocessable_entity }
end
end
end

The error makes sense, since you're trying to create a property with a portfolio_id that doesn't exist. Since you added the optional: true to belongs_to :portfolio in the Property class, you removed the validation that normally comes with the belongs_to.
The best way of avoiding that would be to display a dropwdown in the form for a new portfolio that contains all the possible properties. That would also be better in terms of UX because the user would not have to remember the id of a property.

Related

How can I set up a foriegn key field given an attribute rather than the foriegn key id?

I'm new to Rails and I'm having difficulties setting up my associations with my tables. I have a Team table that I want to have a foreign key to a TeamType table. The team type that I want to give each team is based on input from a form. Right now, my code is setup so that the user inputs the id of the team type. However, I want to make it so that the user inputs the name of the team type rather than the id in the form. With the name rather than the id, can I still populate the foreign id column? How could I do this?
Here are my migrations:
class CreateTeams < ActiveRecord::Migration[5.1]
def change
create_table :teams do |t|
t.references :team_type, foreign_key: true
t.string :team_name
t.integer :num_of_students
t.timestamps
end
end
end
class CreateTeamTypes < ActiveRecord::Migration[5.1]
def change
create_table :team_types do |t|
t.string :type_name
t.integer :max_size
t.timestamps
end
end
end
Here are my models:
class Team < ApplicationRecord
validates :team_name, length: {maximum: 40}, presence: true
belongs_to :team_type
# add_index :team_name
end
class TeamType < ApplicationRecord
validates :type_name, length: {maximum: 35}, presence: true
validates :max_size, numericality: { only_integer: true },
presence: true
end
Here is the create method of the team controller
def create
#team = Team.new(team_params)
respond_to do |format|
if #team.save
format.html { redirect_to #team, notice: 'Team was successfully created.' }
format.json { render :show, status: :created, location: #team }
else
format.html { render :new }
format.json { render json: #team.errors, status: :unprocessable_entity }
end
end
end
Any help would be appreciated. Thanks
Yes, you can set up your form fields on the front end so that the options display the name, but also pass along the ID so objects can be associated. I'd recommend referencing the Rails docs about form helpers, specifically section 3.1 on Select and Option Tags: https://guides.rubyonrails.org/form_helpers.html#the-select-and-option-tags

Create a post with many-to-many relationship in Ruby on Rails

I have a structure very much for between category and yell. What I do is qeuro a call on POST type API with the following parameters:
{
"user_id":"1",
"title":"primeito",
"desciption":"de_novo",
"categories":[{"name":"eletro"},{"name":"domestic"},{"name":"new_category"}],
"yell_type":"novo",
"price":"10,00",
"payment_type":"boleto"
}
My structure is as follows:
My model yell:
#yell.rb
class Yell < ActiveRecord::Base
belongs_to :user, inverse_of: :yells
has_and_belongs_to_many :categories
end
model category:
#category.rb
class Category < ActiveRecord::Base
has_and_belongs_to_many :yells
end
method crete in controller yell:
#yells_controller.rb
def create
#yell = Yell.new(yell_params)
params[:categories].each do |rel|
#category = Category.find_by_name(rel[:name])
if #category
#only creates the relationship
else
#yell.categories.build(name: rel[:name]) #creates the relationship and category
end
end
if #yell.save
render json: #yell, status: :created, location: api_yell_path(#yell)
else
render json: #yell.errors, status: :unprocessable_entity
end
end
...
private:
def yell_params
params.require(:yell).permit(:title, :desciption, :price, :payment_type, :user_id, :yell_type, :categories)
end
So I created the table
class CreateCategoriesYellsJoinTable < ActiveRecord::Migration
def self.up
create_table :categories_yells, :id => false do |t|
t.integer :category_id
t.integer :yell_id
end
add_index :categories_yells, [:category_id, :yell_id]
end
def self.down
drop_table :categories_yells
end
end
I can make him create the categories, but does not know how to create only the relationship. Agluem can help me is the comment #only creates the relationship?
I need to do this check because the category name is unique
Also if someone know something more elegant way to do this, I accept suggestions
I am not quite sure I understood the last paragraph, but I think you need an intermediate table to join the two models first.
You would need to create a table like this:
class CreateCategoriesAndYells < ActiveRecord::Migration
def change
create_table :categories_yells, id: false do |t|
t.belongs_to :category, index: true
t.belongs_to :yell, index: true
end
end
end
Then you would need to update your controller to say something like this:
#yell.categories.build(category_params)
You would need also to pass the category parameters to the controller.
In order to do so I had to create a model to join my table:
model category_yell.rb
class CategoriesYell < ActiveRecord::Base
belongs_to :category
belongs_to :yell
end
and create my method was as follows:
def create
##yell = Yell.new(yell_params.except(:categories))
#yell = Yell.new({title: params[:title], desciption: params[:desciption], price: params[:price], user_id: params[:user_id], yell_type: params[:yell_type]})
if #yell.save
Array(params[:categories]).each do |rel|
#category = Category.find_by_name(rel[:name])
if #category
#categories_yells = CategoriesYell.new(category_id: #category.id, yell_id: #yell.id)
if #categories_yells.save
#yell.categories.build(id: #category.id, name: rel[:name])#only creates the relationship
else
render json: {status: 1, message:"relationship categoy not create", data: #yell.errors}, status: :unprocessable_entity
end
else
#yell.categories.create(name: rel[:name]) #creates the relationship and category
end
end
render json: {status: 0, message:"sucess", data: #yell}, status: :created
else
render json: {status: -1, message:"error", data: #yell.errors}, status: :unprocessable_entity
end
end

#RubyOnRails Many to Many relationship not inserting into join table

I'm creating an app while learning how to code in Ruby. I'm making a simple league app where i can create new teams, players, leagues...
I have a team model and a league model and because a team can enter in more than one league and one league can have more than one team they are a many to many association. However, when i create / edit a team it doesnt insert anything into the join table, only changes the other attributes.
The models:
Team Model
class Team < ActiveRecord::Base
validates_presence_of :name
has_many :teams_join_leagues, :dependent => :destroy
has_many :leagues, through: :teams_join_leagues
end
League Model
class League < ActiveRecord::Base
validates_presence_of :name, :begindate, :enddate, :prizepool, :season_id
validate :start_must_be_before_end_time
has_many :teams_join_leagues, :dependent => :destroy
has_many :teams, through: :teams_join_leagues
Join TeamLeague Model
class TeamsJoinLeague < ActiveRecord::Base
belongs_to :team
belongs_to :league
end
TeamController param function:
def team_params
params.require(:team).permit(:name, leagues: [:id])
end
Create function:
def create
#team = Team.new(team_params)
respond_to do |format|
if #team.save
format.html { redirect_to #team, notice: 'Team was successfully created.' }
format.json { render :show, status: :created, location: #team }
else
format.html { render :new }
format.json { render json: #team.errors, status: :unprocessable_entity }
end
end
end
What i'm doing wrong? it never inserts anything on the join table leaving all the teams without leagues :/
Im assuming your teams_join_leagues table like this:
create_table "teams_join_leagues", force: :cascade do |t|
t.integer "team_id"
t.integer "league_id"
....
end
so in your create team, it should look like this or similar
def create
#league = current_team.teams_join_leagues.build(league_id: params[:league_id])
if #league.save
....
end

Accessing one-to-many association from console

I have a one-to-many association between 2 resources: Discovery and Matter
class Discovery < ActiveRecord::Base
belongs_to :matter
end
class Matter < ActiveRecord::Base
has_many :discoveries
end
My routes file has this:
resources :matters do
resources :discoveries
end
My migration files look like:
class CreateDiscoveries < ActiveRecord::Migration
def change
create_table :discoveries do |t|
t.string :aws_url
t.string :upload_file_path
t.attachment :upload
t.integer :matter_id
t.string :direct_upload_url
t.boolean :processed
t.timestamps
end
end
end
class AddMatterIdToDiscoveries < ActiveRecord::Migration
def change
add_index :discoveries, :matter_id
add_index :discoveries, :processed
end
end
discoveries_controller.rb
def create
#matter = Matter.find(params[:matter_id])
if(params[:url])
#discovery = Discovery.new
render "new" and return
end
if(params[:discovery][:upload_file_path])
#discovery = Discovery.new(discovery_params)
respond_to do |format|
if #discovery.save
#discovery.matter = current_user.matters.find(params[:matter_id])
format.html { render action: :show, notice: 'Discovery was successfully created.' } # matter_url(#discovery.matter_id)
format.json { render action: 'show', status: :created, location: #discovery }
else
format.html { render action: 'new' }
format.json { render json: #discovery.errors, status: :unprocessable_entity }
end
# redirect_to new_document and return
end
else
#discovery = Discovery.new
render action: 'new', notice: "No file"
end
end
When I create a new discovery in the matters model matters/3/discoveries/new the discovery gets created, but in the console, I thought I should be able to access Discovery.last.matter, but instead I get the error NoMethodError: undefined method 'matter' for #<Discovery:0x0000000495dc98>
How would I go about showing the matter that the discovery belongs to? Thanks
Call reload! in the console after changing your models (schema changes, running migrations, adding methods).

Basic database association

I am attempting to associate 2 models - Athletes and Results
They have the following fields:
Athletes - :name :year :gender
Results - :name :event :performance
I have added belongs_to :athlete to results.rb & added has_many :results to athletes.rb
I would like to use the :name attribute to act as the primary key used to associate the two models as the intention is for all athletes to be uploaded initially and then just use the results input for the remainder of the season.
I have edited the results_controller to the following:
def create
#this was the initial code....
##result = Result.new(params[:result])
# This is the new code to try set up an association
#athlete = Athlete.where('name = ?', 'Peter')
#result = #athlete.results.create(params[:result])
respond_to do |format|
if #result.save
format.html { redirect_to #result, notice: 'Result was successfully created.' }
format.json { render json: #result, status: :created, location: #result }
else
format.html { render action: "new" }
format.json { render json: #result.errors, status: :unprocessable_entity }
end
end
end
This however produces the error undefined method 'results' for #<ActiveRecord::Relation:0x36a2b28>. I was also hoping to use the line #athlete = Athlete.where("name = ?", params[:name]) however it keeps yielding a NULL parameter value...
Is anyone able to point me in the correct direction please?
Extra information:
Results migration
class CreateResults < ActiveRecord::Migration def change
create_table :results do |t|
t.string :name
t.string :event
t.decimal :performance
t.timestamps
end
#add in new line here
add_index :results, :name
end end
Athletes migration
class CreateAthletes < ActiveRecord::Migration
def change
create_table :athletes do |t|
t.string :name
t.integer :year
t.string :gender
t.timestamps
end
end
end
Result.rb:
class Result < ActiveRecord::Base
belongs_to :athlete
end
Athlete.rb
class Athlete < ActiveRecord::Base
has_many :results
end
The problem is that Athlete.where('name = ?', 'Peter') returns ActiveRecord::Relation object. Either use
Athlete.where('name = ?', 'Peter').first
or the dynamic find method
Athlete.find_by_name('Peter')
Edit
Also make sure you add t.references :athlete to your results table.
Your results table needs to store the athlete_id.
create_table :results do |t|
t.references :athlete
t.string :name
t.string :event
t.decimal :performance
t.timestamps
end
The references will create the foreign key association using the rails conventions (which in this case is athlete_id)

Resources