I have students table that is related to schools table. The problem is, when I try to fetch all schools' data, it does not include the students that are associated in the api response.
But it more confuses me, student name was displayed in terminal when I try to loop and print. Although I have tried the below line but still won't work.
has_many :student, foreign_key: :school_id, primary_key: :id
Do you have idea why?
The students table has columns school_id that referenced to schools table.
schools_controller.rb
class SchoolsController < ApplicationController
def index
schools = School.includes(:student)
schools.each do |school|
puts school.student.collect(&:name) // student displayed in terminal
end
render json: { message: "sample", data: schools }, status: :ok
end
end
school.rb
class School < ApplicationRecord
has_many :student
end
student.rb
class Student < ApplicationRecord
end
12345_create_students.rb
class CreateStudents < ActiveRecord::Migration[7.0]
def change
create_table :students do |t|
t.references :school, foreign_key: true
t.string :name
...
end
end
end
Model relations is actually fine. Your problem probably is in your controller.
Use preload instead of include, and in your return statement you should include the student.
learn the differences here
Your code should look like these:
schools = School.preload(:student)
render json: schools, include: :student
Read https://guides.rubyonrails.org/association_basics.html for
and try below code
school.rb
class School < ApplicationRecord
has_many :students
end
student.rb
class Student < ApplicationRecord
belongs_to :school
end
Related
I have the following models:
class Model < ApplicationRecord
has_many :states
has_one :state, -> { order(created_at: :desc) }, class_name: 'State'
def update_state!(state)
states.create!(state: state)
end
end
class State < ApplicationRecord
belongs_to :model
end
If code is kept like this, then this is the behaviour:
m = Model.new(states: State.new(state: 'initial'))
m.save!
m.state.state # => "initial"
m.update_state!("final")
m.state.state # => "initial"
One workaround would be changing the Model#update_state! to:
def update_state!(state)
states.create!(state: state)
reload
end
Which kind of sucks.
Is there anyway to handle this without having to refresh the record? Something tells me I am missing something...
In this scenario, I might understand that Rails might not know how to relate :state with :states... However, I even tried making #state a plain method instead of an association, but I am still facing the same issue.
class Model < ApplicationRecord
has_many :states
def state
states.max_by(&:created_at)
end
def update_state!(state)
states.create!(state: state)
end
end
class State < ApplicationRecord
belongs_to :model
end
I would add a foreign key column to models as a "shortcut" to the latest record:
class AddStateToModels < ActiveRecord::Migration[6.0]
change_table :models do |t|
t.references :state
end
end
class Model < ApplicationRecord
has_many :states,
after_add: ->(state){ update_columns(state_id: state.id) }
belongs_to :state, class_name: 'State'
def update_state!(state)
states.create!(state: state)
end
end
This is a good read optimization if you are displaying a list of Model instances and there current state as it lets you do a very effective query:
#models = Model.eager_load(:current_state)
.all
I have the following associations set up:
class Book < ActiveRecord::Base
belongs_to :author
belongs_to :category
has_many :users_books
has_many :users, through: :user_books
end
and
class User < ActiveRecord::Base
has_many :users_books
has_many :books, through: :users_books
end
I created a join table migration as I ought to
class CreateUsersBooks < ActiveRecord::Migration[4.2]
def change
create_table :users_books do |t|
t.integer :user_id
t.integer :book_id
end
end
end
Now I need to create a method called check_out_book, that takes in a book and a due_date as arguments. When a user checks out a book, it should create a new UserBook record (or Checkout record or whatever you want to call you join table/model). That new UserBook record should have a attribute (and therefore table column) of returned? which should default to false. How would I go about creating this method and the migrations?
Your tablenames and your associations in Rails should always be singular_plural with the exception of the odd duckling "headless" join tables used by the (pretty useless) has_and_belongs_to_many association.
class CreateUserBooks < ActiveRecord::Migration[4.2]
def change
create_table :user_books do |t|
t.references :user
t.references :book
t.boolean :returned, default: false
end
end
end
class UserBook < ActiveRecord::Base
belongs_to :user
belongs_to :book
end
class Book < ActiveRecord::Base
belongs_to :author
belongs_to :category
has_many :user_books
has_many :users, through: :user_books
end
class User < ActiveRecord::Base
has_many :user_books
has_many :books, through: :user_books
end
But you should really use a better more descriptive name that tells other programmers what this represents in the domain and not just a amalgamation of the two models it joins such as Loan or Checkout.
I would also use t.datetime :returned_at to create a datetime column that can record when the book is actually returned instead of just a boolean.
If you want to create a join record with any additional data except the foreign keys you need to create it explicitly instead of implicitly (such as by user.books.create()).
#book_user = Book.find(params[:id]).book_users.create(user: user, returned: true)
# or
#book_user = current_user.book_users.create(book: user, returned: true)
# or
#book_user = BookUser.new(user: current_user, book: book, returned: true)
I have Forms created by Users. Every form is only visible to the creator. I would like to grant permission to other users to see a specific form. One could say I want to whitelist other users for a specific form.
Here's what I tried by creating a third model called "SharedForm".
app/models/form.rb
Class Form < ApplicationRecord
belongs_to :user
...
end
app/models/user.rb
Class User < ApplicationRecord
has_many :forms
has_many :forms, through: :sharedforms
...
end
app/models/shared_form.rb
Class SharedForm < ApplicationRecord
belongs_to :user
belongs_to :form
...
end
migration
class CreateSharedForms < ActiveRecord::Migration[5.0]
def change
create_table :shared_forms do |t|
t.integer :form_id, index: true
t.integer :user_id, index: true
t.timestamps
end
add_foreign_key :shared_forms, :users, column: :user_id
add_foreign_key :shared_forms, :forms, column: :form_id
end
end
In order to present both user forms and forms shared with the user I defined the index as:
app/controllers/forms_controller.rb
Class FormsController < ApplicationController
def index
#forms = Form.where(user_id: current_user.id)
shared = SharedForm.where(user_id: current_user.id)
#sharedforms = Form.where(id: shared)
end
end
This doesn't work.
Is there a way to access the records I need by user.forms and user.sharedforms respectively?
You can't use the same name for two associations as the latter will overwrite the former:
class User < ApplicationRecord
has_many :forms
# this overwrites the previous line!
has_many :forms, through: :sharedforms
...
end
Instead you need to give each association a unique name:
class User < ApplicationRecord
has_many :forms
has_many :shared_forms
has_many :forms_shared_with_me, through: :shared_forms
end
Note that the through option for has_many should point to an association on the model!
This would let you use:
class FormsController < ApplicationController
def index
#forms = current_user.forms
#shared = current_user.forms_shared_with_me
end
end
I'm trying to create a web application to organize a user's TV interests, to do this, I need to store data of three types: Shows, Seasons, and Episodes.
I would like to query my data like this: Show.find(1).season(2).episode.each. This should return each episode of the second season of the show with the id 1. How can I set my model up to a achieve this?
I've tried having values of season_id and show_id on the episodes, but its unable to find the episodes belonging to each season.
Define relationship in mode,
Show
has_many :seasons
Season
has_many :episodes
belongs_to :show
Episode
belongs_to :season
Then you can call like this,
Show.find(1).seasons.first.episodes.each {}
Maybe it's a good idea to read through the guides. Assuming that your entity relationships looking like this:
You can implement this with activerecord easily. The models would look like this:
require 'active_record'
class Show < ActiveRecord::Base
has_many :seasons
end
class Season < ActiveRecord::Base
belongs_to :show
has_many :episodes
end
class Episode < ActiveRecord::Base
belongs_to :season
end
Your migrations could look like:
require 'active_record'
class CreateShows < ActiveRecord::Migration
def change
create_table :shows do |t|
t.timestamps
end
end
end
class CreateSeasons < ActiveRecord::Migration
def change
create_table :seasons do |t|
t.references :show, :null => false
t.timestamps
end
end
end
class CreateEpisodes < ActiveRecord::Migration
def change
create_table :episodes do |t|
t.references :season, :null => false
t.timestamps
end
end
end
Put some data into your database and query them with:
Show.find(1).seasons.first.episodes.each{ |e| puts e.title }
The answers above are great; I'd take it a step further and use has_many's :through option in the Show model and has_one :through on the Episode model:
# Show
has_many :seasons
has_many :episodes, through: :seasons
# Season
belongs_to :show
has_many :episodes
# Episode
belongs_to :season
has_one :show, through: :season
This lets you make calls like this:
Show.first.episodes
Episode.first.show
... and will also allow you to write some query-minimizing scopes, and write delegate methods that simplifying finding related information.
# Episode
delegate :name, to: :show, prefix: :show
Episode.first.show_name # => Episode.first.show.name
I have two models, items and categories which have a many-to-many relationship using the has_and_belongs_to_many association.
In my models I have
class Item < ActiveRecord::Base
has_and_belongs_to_many :categories
end
and
class Category < ActiveRecord::Base
has_and_belongs_to_many :items
end
I created a join table "categories_items":
create_table "categories_items", :id => false, :force => true do |t|
t.integer "category_id"
t.integer "item_id"
end
I'm not getting any errors, but I'm just a bit confused about exactly what the association allows. Right now, if I have some category #category, I can find all the Items in it by doing
#category.items
I assumed that I could find the categories associated with a given Item #item by doing
#item.categories
However I get an error that says
ActiveModel::MissingAttributeError: missing attribute: category
Am I misunderstanding how a has_and_belongs_to_many association functions, or am I missing something in my code? Thank you!
Edit - Additional Information:
I think the confusion lies in how I'm supposed to assign items/categories. Currently, I'm creating them independently:
#item = Item.new
... add attributes ...
#item.save
and
#category = Category.new
... add attributes ...
#category.save
and then associating them with
#category.items << #item
#item.categories << #category
I think I've experienced what you're going through once before. I believe the confusion is in how to connect through other tables. In the following, one user can have many skills. A skill is also connected to many users. Something similar to this may work for you ^_^
class User < ActiveRecord::Base
has_many :skills_users
has_many :skills, through: :skills_users
class SkillsUser < ActiveRecord::Base
belongs_to :user
belongs_to :skill
class Skill < ActiveRecord::Base
has_many :skills_users
has_many :users, through: :skills_users