change parameter in one direction in update function when making api - ruby-on-rails

i'm trying to make API using Ruby on Rails 5 , i'm new on it so i used scaffold to generate tables so this my main table for trip
class CreateTrips < ActiveRecord::Migration[5.2]
def change
create_table :trips do |t|
t.string :nameOfDriver
t.string :status
t.timestamps
end
end
end
and this is my update function in controller
def update
if #trip.update(trip_params)
render json: #trip
else
render json: #trip.errors, status: :unprocessable_entity
end
end
i'm trying to add constrains first, to status to be "completed " or "ongoing " ... second check if it "completed" i can't change it to "ongoing"
this link for the full project
https://github.com/gnik2036/RubyTask

class Trip < ApplicationRecord
TRIP_STATUSES = %w[completed ongoin].freeze
INVALID_STATUS_CHANGES = [%w[completed ongoin]].freeze
validate :status_validity, on: :update
validates :status, presence: true, inclusion: { in: TRIP_STATUSES }
private
def status_validity
return unless status_changed?
if INVALID_STATUS_CHANGES.include? status_change
errors.add(:status, "Cannot be changed from #{status_was} to #{status}.")
end
end
end

Related

Rails PG::ForeignKeyViolation: ERROR: insert or update on table

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.

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

How to assign a user id to a record in the backend?

I want to make sure that when I create a record in the front end, the user id of the user who created it is automatically assigned to this record. What should I do in the backend if I want the id of the authorized user to be automatically assigned to the record when creating the record? For authorization I use gem 'devise_token_auth'.
notebooks_controller.rb:
class NotebooksController < ApplicationController
before_action :set_notebook, only: [:show, :update, :destroy]
def index
#notebooks = Notebook.all
render json: #notebooks
end
def show
render json: #notebook
end
def create
#notebook = Notebook.new(notebooks_params)
if #notebook.save
render json: #notebook, status: :created, location: #notebook
else
render json: #notebook.errors, status: :unprocessable_entity
end
end
def update
if #notebook.update(notebooks_params)
render json: #notebook
else
render json: #notebook.errors, status: :unprocessable_entity
end
end
def destroy
#spr_contract_solution.destroy
end
private
def set_notebook
#notebook = Notebook.find(params[:id])
end
def notebooks_params
params.require(:notebook).permit!
end
end
notebook.rb:
class Notebook < ApplicationRecord
belongs_to :user
end
...._create_notebooks.rb
class CreateNotebooks < ActiveRecord::Migration[5.1]
def change
create_table :notebooks do |t|
t.string :title
t.text :description
t.boolean :is_active
t.references :user, foreign_key: true
t.timestamps
end
end
end
First, #Subash is right, you need to pass the list of parameters to the permit method in notebook_params (note that maybe you would want to use permit instead of permit!), for example:
params.require(:notebook).permit :name, :text, :any_other_attribute_you_are_saving
Then, to answer your question, you could do something like this in the create action:
#notebook = Notebook.new(notebooks_params)
#notebook.user = current_user #Assuming you have this method available
Assuming you have has_many :notebooks in your user model, this is a popular idiom for doing what you want:
#notebook = current_user.notebooks.build(notebook_params)
add
#notebook.user = current_user
after
#notebook = Notebook.new(notebooks_params)

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

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