Issue with Rails query joins - ruby-on-rails

I have a really dumb issue, I have two models:
class User < ApplicationRecord
belongs_to :role
end
and
class Role < ApplicationRecord
has_many :user
end
I'm trying to get the user and make a join with the role's table to get the role's name and id, like:
"user" : {
"name": "json",
"role": {"name":"admin", "id":1}
}
however, after using:
User.includes(:role).all
I just get the users with a "role_id" value, I've also tried:
User.joins(:role)
With the same result. I've been looking at the official docs at (https://guides.rubyonrails.org/active_record_querying.html) and it should be pretty straightforward but I don't know what I'm doing wrong. Do I need to add something to my migrations?, At my create_user migration I have:
class CreateUsers < ActiveRecord::Migration[6.0]
def change
create_table :users do |t|
t.string :username
t.string :email
t.integer :role_id
end
add_index :users, :email
add_index :users, :username
end
end
and my create_roles migration
class CreateRoles < ActiveRecord::Migration[6.0]
def change
create_table :roles do |t|
t.string :name
end
end
end

all by itself doesn't return such a custom data. You can use as_json for that:
User.all.as_json(only: :name, include: { role: { only: [:name, :id] } }, root: true)
# User Load (0.5ms) SELECT "users".* FROM "users"
# Role Load (0.6ms) SELECT "roles".* FROM "roles" WHERE "roles"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
# => [{"user"=>{"name"=>"json", "role"=>{"id"=>1, "name"=>"admin"}}}]

Related

How do you set up MTI in Rails with a polymorphic belongs_to association?

In an effort to create a Short, Self Contained, Correct (Compilable), Example, imagine that I want to do the following.
I have a blog website. There are two types of posts, TextPost and LinkPost. There are also two types of users, User and Guest. I would like to implement Multiple Table Inheritance with TextPost and LinkPost, by which I mean (hopefully I'm using the term correctly):
At the model level, I will have Post, TextPost and LinkPost. TextPost and LinkPost will inherit from Post.
At the database level, I will have tables for the "leaf" models of TextPost and LinkPost, but not for Post.
Each type of Post can belong to either a User or a Guest. So we have a polymorphic belongs_to situation.
My question is how to accomplish these goals.
I tried the following, but it doesn't work.
class Post < ApplicationRecord
self.abstract_class = true
belongs_to :author, polymorphic: true # user or guest
validates :title, :author_id, :author_type, presence: true
end
class TextPost < Post
validates :content, presence: :true
end
class LinkPost < Post
validates :url, presence: :true
end
class User < ApplicationRecord
has_many :text_posts, as: :author
has_many :link_posts, as: :author
validates :name, presence: true
end
class Guest < ApplicationRecord
has_many :text_posts, as: :author
has_many :link_posts, as: :author
end
class CreateTextPosts < ActiveRecord::Migration[6.1]
def change
create_table :text_posts do |t|
t.string :title
t.string :content
t.references :author, polymorphic: true
t.timestamps
end
end
end
class CreateLinkPosts < ActiveRecord::Migration[6.1]
def change
create_table :link_posts do |t|
t.string :title
t.string :url
t.references :author, polymorphic: true
t.timestamps
end
end
end
class CreateUsers < ActiveRecord::Migration[6.1]
def change
create_table :users do |t|
t.string :name
t.timestamps
end
end
end
class CreateGuests < ActiveRecord::Migration[6.1]
def change
create_table :guests do |t|
t.timestamps
end
end
end
Console output:
:001 > user = User.create(name: 'alice')
(1.6ms) SELECT sqlite_version(*)
TRANSACTION (0.1ms) begin transaction
TRANSACTION (0.1ms) SAVEPOINT active_record_1
User Create (1.2ms) INSERT INTO "users" ("name", "created_at", "updated_at") VALUES (?, ?, ?) [["name", "alice"], ["created_at", "2021-06-11 23:33:38.445387"], ["updated_at", "2021-06-11 23:33:38.445387"]]
TRANSACTION (0.2ms) RELEASE SAVEPOINT active_record_1
:002'> text_post = TextPost.create(title: 'foo', content: 'lorem ipsum', author_id: 1, author_type:
'user')
Traceback (most recent call last):
1: from (irb):2:in `<main>'
NameError (wrong constant name user)
The names of constants look like the names of local variables, except that they begin with a capital letter.
All the built-in classes, along with the classes you define, have a corresponding global constant with the same name as the class called class name.
So in your case, when you define User class, there's a constant class name: User, but not user, that why the error NameError (wrong constant name user) is raised.
try text_post = TextPost.create(title: 'foo', content: 'lorem ipsum', author_id: 1, author_type: 'User')

Reservation.last.card => NoMethodError: undefined method `card' for #<Reservation:0x0090d440e130> Did you mean? card_id

I'm sure it something really stupid, but I cannot seem to find the issue.
I'm trying to call Reservation.last.card, but get the error
Reservation Load (0.3ms) SELECT "reservations".* FROM "reservations" ORDER BY "reservations"."id" DESC LIMIT $1 [["LIMIT", 1]]
NoMethodError: undefined method `card' for #<Reservation:0x090d440e130>
Did you mean? card_id
migration + schema
class AddCardToReservations < ActiveRecord::Migration[5.2]
def change
add_reference :reservations, :card, foreign_key: true
end
end
create_table "reservations", force: :cascade do |t|
t.bigint "park_id"
t.bigint "card_id"
t.index ["card_id"], name: "index_reservations_on_card_id"
t.index ["park_id"], name: "index_reservations_on_park_id"
end
models
class Reservation < ApplicationRecord
has_one :card
belongs_to :park
end
class Card < ApplicationRecord
belongs_to :park
has_many :reservations
end
the line in the Reservation class...
has_one :card
Implies that the card object has a reservation_id which isn't the case, the foreign key is card_id in the reservation object, so what you want is...
belongs_to :card

Rails 6: Can't delete nested model. Random Insert statement

I using Rails 6 with Postgres and having issues deleting a nested model.
A random insert statement gets generated after the association has been deleted.
Let me explain my set up.
Migrations
class CreateEntries < ActiveRecord::Migration[6.0]
def change
create_table :entries do |t|
t.string :name
t.timestamps
end
end
end
class Cards < ActiveRecord::Migration[6.0]
def change
create_table :cards do |t|
t.string :card_number
t.belongs_to :entry, null: true, foreign_key: true
t.timestamps
end
end
end
Models
class Entry < ApplicationRecord
has_one :card, dependent: :destroy
accepts_nested_attributes_for :card, allow_destroy: true
end
class Card < ApplicationRecord
belongs_to :entry
end
Controller
class EntriesController < ApplicationController
before_action :set_entry
def update
#entry.update(entry_params)
end
def set_entry
#entry = Entry.find(params[:id])
end
def entry_params
params.require(:entry).permit(:name,
card_attributes: [:id, :card_number, :_destroy]
)
end
end
Request Params
Parameters: {"authenticity_token"=>"CQ...Ucw==", "entry"=>{"card_attributes"=>{"_destroy"=>"true"}}, "id"=>"1"}
These are the logs
(0.2ms) BEGIN
ConcessionCard Load (0.2ms) SELECT "cards".* FROM "cards" WHERE "cards"."entry_id" = $1 LIMIT $2 [["entry_id", 1], ["LIMIT", 1]]
Card Destroy (0.4ms) DELETE FROM "cards" WHERE "cards"."id" = $1 [["id", 2]]
Card Create (0.6ms) INSERT INTO "cards" ("entry_id", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["entry_id", 1], ["created_at", "2019-09-06 13:50:41.100718"], ["updated_at", "2019-09-06 13:50:41.100718"]]
(0.3ms) COMMIT
Why is insert being generated after the delete call? It's not even a rollback.
Note: I have tried both null:true and null:false in the Cards belongs_to migration. I also tried setting optional:true in the belongs_to :entry statement in the Card model
Unless you include an id in card_attributes then Rails sees this as a new record, so it just replaces the has_one with a newly created Card for you (which because of your dependent: :destroy option deletes the existing associated Card).
Best to use a form.fields_for :card block in your form partial/view, which will automatically add the hidden id tag for an existing Card.

belongs_to with foreign_key not working in one direction

class Position < ActiveRecord::Base
belongs_to :product, foreign_key: :symbol
end
class Product < ActiveRecord::Base
has_many :positions, primary_key: :symbol, foreign_key: :symbol
end
When I do
Product.first.positions.first I am getting a Product back.
But, when I do Position.first.product I am getting nothing.
When I look at the SQL generated by the query, it is:
SELECT "products.*" FROM "products" WHERE "products.id" = ? LIMIT 1 [["id", 0]]
Why?
The SQL generated is using products.id instead of products.symbol because you didn't tell it that the association should use symbol as the primary key instead of the default of id. So, in your Position class just add primary_key: :symbol to the belongs_to and I think that'll do it.
Try this:
class Product < ActiveRecord::Base
self.primary_key = "symbol"
end
First of all, you need to revise the creation of your model Product.
You need to create it the following way:
class CreateProducts < ActiveRecord::Migration
def change
create_table :products, id: false do |t|
t.string :symbol, null: false
t.timestamps
end
add_index :products, :symbol, unique: true
end
end
And then let your model know about the primary_key, that is not id:
class Product < ActiveRecord::Base
self.primary_key = "symbol"
end
And after that, when you do Product.last, it will generate the following query:
Product.last
# Product Load (0.3ms) SELECT "products".* FROM "products" ORDER BY "products"."symbol" DESC LIMIT 1

How do I change has_many and belongs_to to has_many and has_many?

There is a user-model and a schedule-model.
First I wanted to have many schedules which belongs_to one user.
Now I want to have many schedules which has/belongs_to many users.
Here are my code-snippets:
user-model:
# encoding: UTF-8
require 'bcrypt'
class User < ActiveRecord::Base
[...]
has_many :schedules
[...]
end
schedule-model:
# encoding: UTF-8
class Schedule < ActiveRecord::Base
belongs_to :user
[...]
end
The Database attributes are:
user-model:
there is no attribute for schedule.
schedule-model:
user_id as integer
How can I change it correctly?
EDIT:
generated-output:
class CreateUsersSchedulesJoinTable < ActiveRecord::Migration
def change
create_join_table :users, :schedules do |t|
# t.index [:user_id, :schedule_id]
# t.index [:schedule_id, :user_id]
end
end
end
class RemoveUserIdFromSchedule2 < ActiveRecord::Migration
def change
remove_column :schedules, :user_id, :integer
end
end
SQL:
Started GET "/" for 127.0.0.1 at 2014-07-29 16:05:25 +0200
Processing by WelcomeController#index as HTML
User Load (1.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = 12 LIMIT 1
Schedule Load (1.0ms) SELECT "schedules".* FROM "schedules" INNER JOIN "schedules_users" ON "schedules"."id" = "schedules_users"."schedule_id" WHERE "schedules_users"."user_id" = $1 ORDER BY "schedules"."date_time" ASC [["user_id", 12]]
Rendered welcome/index.html.erb within layouts/application (0.0ms)
Completed 200 OK in 48ms (Views: 44.0ms | ActiveRecord: 2.0ms)
The migration works fine. There is a new table with the ids, now, but I have to change the controller as well, because my order-method didnt work atm. Can anyone/you help me ?
class WelcomeController < ApplicationController
def index
if(current_user)
#user_schedules = current_user.schedules
#user_schedules_date = #user_schedules.order(:date_time).group_by { |sched| sched.date_time.beginning_of_day }
end
end
end
You should use a join table, you probably want to name the new table : 'users_schedules', this new table should contains two columns : user_id and schedule_id (use a migration):
class CreateUsersSchedulesJoinTable < ActiveRecord::Migration
def change
create_table :users_schedules, id: false do |t|
t.integer :user_id
t.integer :schedule_id
end
end
end
And another to remove the old columns in the previous table :
class RemoveUserIdColumns < ActiveRecord::Migration
def change
remove_column :schedule, :user_id
end
end
Then update your model to be like
class User < ActiveRecord::Base
has_and_belongs_to_many :schedules
.....
end
and
class Schedule < ActiveRecord::Base
has_and_belongs_to_many :users
.....
end
This should work !

Resources