Joining two tables in rails - ruby-on-rails

I am trying to link the location and the operating tables so that I can display some data in location table in the operatings views. However, I am stuck and don't know what to do. Any help would be appreciated.
#below are models#
class Location < ApplicationRecord
has_many :operatings
end
class Operating < ApplicationRecord
belongs_to :location
end
##below are my tables##
enable_extension "plpgsql"
create_table "locations", force: :cascade do |t|
t.string "country"
t.string "supra_region"
t.string "region"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "operatings", force: :cascade do |t|
t.string "operating_company_name"
t.string "address"
t.date "year_formed"
t.string "other_operational_countries"
t.string "about_company"
t.string "current_focus"
t.string "incumbent_irm_contractor"
t.string "irm_frame_agreements"
t.text "estimated_irm_budgets"
t.integer "location_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["location_id"], name: "index_operatings_on_location_id", using: :btree
end
add_foreign_key "operatings", "locations"
###below is my operating controller###
def create
#operating = Operating.new(op_company)
if #operating.save
flash[:success] = "A recorded has been successfully Saved"
redirect_to operatings_path
else
render 'new'
end
end
####routes####
resources :offshores, :index, :show, :new, :create, :destroy
resources :locations, :index, :show, :new, :create, :destroy

Since your Location and Operating models are linked together using has_many and belongs_to, if you have an operating object in your template, you can easily access the attributes of its location:
<% #operatings.each do |operating| %>
<div>The name of its location: <%= operating.location.name %></div>
<% end %>
You need to be careful with this though. If you only fetch the operatings from the database, accessing each operating's location attribute in that each loop will trigger a separate database query for every operating item. This is called an N+1 query, and it is very inefficient. To fix the problem, make sure to pre-fetch the associated location as well when loading operatings using includes:
# in the controller
#operatings = Operating.all.includes(:location)
This way the associated locations of every operating will be fetched using just a single query.

Related

Rails many-to-many through relationship returning empty array

I'm a beginner in rails and am making a card app. I have a user, card, and user_card models with a many to many, through relationship set up between the cards and users. My problem is that when I return the card table and try to include: the users I get an empty array. I've tried resetting the database but still nothing.
ActiveRecord::Schema.define(version: 2022_06_15_200100) do
create_table "cards", force: :cascade do |t|
t.string "name"
t.string "image"
t.string "text"
t.integer "level"
t.string "types"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.integer "quantity", default: 0
end
create_table "user_cards", force: :cascade do |t|
t.integer "user_id"
t.integer "card_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["card_id"], name: "index_user_cards_on_card_id"
t.index ["user_id"], name: "index_user_cards_on_user_id"
end
create_table "users", force: :cascade do |t|
t.string "username"
t.string "password_digest"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
class User < ApplicationRecord
has_secure_password
has_many :user_cards
has_many :cards, through: :user_cards
end
class Card < ApplicationRecord
has_many :user_cards
has_many :users, through: :user_cards
end
class UserCard < ApplicationRecord
belongs_to :user
belongs_to :card
end
controller
class CardsController < ApplicationController
wrap_parameters false
def create
card = Card.create(card_params)
if card.valid?
render json: card, status: :created, include: :users
else
render json:{errors: card.errors}
end
def index
card = Card.all
render json: card, include: :users
end
In order for the User.first.cards to work, you need to ensure the application is inserting data in the user_cards table.
You may check if there are any records in there by doing UserCard.all in your rails console.
Coming to the controller, after creating a card record, you have to assign it to a user record in order for the relationship to be established.
def create
card = Card.create(card_params)
if card.valid?
card.users << current_user # Or card.users << (any user object like) User.first
render json: card, status: :created, include: :users
else
render json:{errors: card.errors}
end
end
card.users << user object will create the necessary record in the user_cards table and you'll be able to access them using includes: :user
You may refer the examples given - here in the Rails API guide

How to implement counter with buttons to a post in a polymorphic associations?

I have an app where I can make post and create comments to them. And I need to implement 2 buttons (like and unlike) in my view to every post(tweet) and comment to this tweet. Also I have 2 routes to my likes: post and destroy: (all routes are here)
resource :user_profiles, only: %i[edit update]
resources :tweets
resources :comments, only: %i[create destroy]
resources :likes, only: %i[create destroy]
root to: 'home#index'
But also I have polymorphic associations:
class Like < ApplicationRecord
belongs_to :likable, polymorphic: true
belongs_to :user
end
class Tweet < ApplicationRecord
belongs_to :user
has_many :likes, as: :likable
has_many :comments, dependent: :destroy
end
class Comment < ApplicationRecord
has_many :likes, as: :likable
belongs_to :tweet
belongs_to :user
end
I wrote some code in my tweet.preview
<% if tweet.likes.find_by(user_id: current_user.id)%>
<%= button_to 'Unlike', like_path(likable_id: tweet.id, likable_type: tweet.class.name, user_id: current_user.id), method: :delete %>
<% else %>
<%= button_to 'Like', likes_path(likable_id: tweet.id, likable_type: tweet.class.name, user_id: current_user.id), method: :post%>
<% end %>
But I don't know how to implement like controller to count likes and display 2 buttons with counter to view.
My db.schema is here:
create_table "comments", force: :cascade do |t|
t.string "content"
t.integer "user_id"
t.integer "tweet_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "likes", force: :cascade do |t|
t.integer "user_id"
t.string "likable_type"
t.integer "likable_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "tweets", force: :cascade do |t|
t.string "content"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["user_id"], name: "index_tweets_on_user_id"
end
create_table "user_profiles", force: :cascade do |t|
t.string "username"
t.string "first_name"
t.string "last_name"
t.date "birthday"
t.string "bio"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["user_id"], name: "index_user_profiles_on_user_id"
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
Thanks in advance!
Well you just need to pass the parent of the future like record (whereas it is a post or a comment) to the create action of the like controller.
You need to add a link to your buttons such as : like_path(parent_type: "comment", parent_id: #comment_id), method: "post"
(here #comment_id may not be an instance variable, especially if you loop through all comment of a specific tweet, it may instead look like comment.id if in your loop you did something like #tweet.comments.each do |comment|)
Then in your like controller, you can just assign them to the record.
I assume here that the user id "is already known" and you create the like from the user record.
#like = current_user.likes.new(likable_id: params[:parent_id], likable_type: params[:parent_type])
#like.save
So most of the job is made in your view by creating the correct link for your different buttons...
So i'm not sure about your setup. Are you using a gem?
So the secret to do this is to create two seperated paths:
resources :likes do
member do
get :like
get :unlike
end
end
inside your controller, you can then execute the logic inside each method:
def like
//find the user
//follow the user
//redirect him
end
def unlike
//find the user
//unfollow the user
//redirect him
end
Also, you can then simply use your buttons for each like/unlike path:
<% if tweet.likes.find_by(user_id: current_user.id)%>
<%= button_to 'Unlike', likes_unlike_path(likable_id: tweet.id, likable_type: tweet.class.name, user_id: current_user.id), method: :delete %>
<% else %>
<%= button_to 'Like', likes_like_path(likable_id: tweet.id, likable_type: tweet.class.name, user_id: current_user.id), method: :post%>
<% end %>
Now, to display the sizes of a like, you could retrieve the amount of users that have liked the tweet:
<h3>People who liked this tweet: <%= likes.count %> </h3>
obviously, only those people who have liked something will be displayed. If you also need a negative number, you could do two things: Either create a join table for your likes or add a new integer and call it "number_likes"
Then, inside your like method, simply increase the number_likes integer by 1. Whenvever someone clicks on the like link, the number will increase.
Inside the unlike method, simply substract by 1 whenever unlikes something. This should give you more or less the same result, but it would also allow you to add a negativ like number.
def like
number_likes += 1
//your previous logic here
end
def unlike
number_likes -=1
end
in your view, you could then simply get the tweet.likes.number_likes to display the amount of likes.
If something is still unclear, let me know!
Happy coding!

How to show a specific unit of data from a record queried in rails?

I am really stuck with dealing with querys from multiple tables in rails. If this is my controller:
def show
#user = User.find(params[:id])
#entry = Entry.joins(:event, :user).where(users: { id: '2' })
#event = Event.find(1)
end
and this is my models
class Entry < ApplicationRecord
belongs_to :user
belongs_to :event
class User < ApplicationRecord
has_many :entries, dependent: :destroy
class Event < ApplicationRecord
has_many :entries, dependent: :destroy
The query runs happily in rails console and returns the records but I don't know how to access specific units like entry.course in my embeded ruby.
Update
To clarify On a page I woulk like to output somthing for all the entries assosiated with the user logged in to the page.
eventName eventLocation eventDate course siCard
The schema:
ActiveRecord::Schema.define(version: 20171204183458) do
# These are extensions that must be enabled in order to support this
database
enable_extension "plpgsql"
create_table "entries", force: :cascade do |t|
t.string "course"
t.string "siCard"
t.bigint "user_id"
t.bigint "event_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["event_id"], name: "index_entries_on_event_id"
t.index ["user_id", "created_at"], name:
"index_entries_on_user_id_and_created_at"
t.index ["user_id"], name: "index_entries_on_user_id"
end
create_table "events", force: :cascade do |t|
t.string "eventName"
t.string "location"
t.date "eventDate"
t.text "description"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "eventLink"
t.string "eventCoordinates"
t.index ["eventName"], name: "index_events_on_eventName", unique: true
end
create_table "users", force: :cascade do |t|
t.string "name"
t.string "email"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "password_digest"
t.string "firstName"
t.string "remember_digest"
t.boolean "admin"
t.index ["email"], name: "index_users_on_email", unique: true
end
add_foreign_key "entries", "events"
add_foreign_key "entries", "users"
end
You can find it in 2 ways
1)
#entries = Entry.includes(:user,:event).where(user: { id: (params[:id]) })
2)
#entries = User.includes(entries: :event).find(params[:id]).entries
Than in the loop of entries you can access value course of particular entry
e.g.
#entries.each do |entry|
puts "#{entry.course}"
puts "#{entry.event&.name}"
end
Why are you using such a complicated query with joins? If you are looking for the Entrys of #user just use #user.entries. ActiveRecord knows from the has_many how to deal with that.
If you posted a simplified example, you should try to find your Entry with something like .where(user_id: user.id) as long as id is the foreign key. With that you don't need that join, too.
You could confine with one query using
eager_load
def show
#user = User.eager_load(:entries => [:event]).where(users: {id: params[:id]})
end
According to your models you can access entries though user.
#user.entries
#user.entries.map{|entry| entry.course}
Also you can access event (which belongs to entry) though entry.
#user.entries[1].event
#user.entries.map{|entry| entry.event}

create relationship between two tables in rails 5

I am trying to establish relationship between two tables in rails so
that I can share data between the two tables. However,am not able to
enter data into the operating tables. Any help would be highly appreciated.
#below are models#
class Location < ApplicationRecord
has_many :operatings
end
class Operating < ApplicationRecord
belongs_to :location
end
##below are my tables##
enable_extension "plpgsql"
create_table "locations", force: :cascade do |t|
t.string "country"
t.string "supra_region"
t.string "region"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "operatings", force: :cascade do |t|
t.string "operating_company_name"
t.string "address"
t.date "year_formed"
t.string "other_operational_countries"
t.string "about_company"
t.string "current_focus"
t.string "incumbent_irm_contractor"
t.string "irm_frame_agreements"
t.text "estimated_irm_budgets"
t.integer "location_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["location_id"], name: "index_operatings_on_location_id", using: :btree
end
add_foreign_key "operatings", "locations"
###below is my operating controller###
def create
#operating = Operating.new(op_company)
if #operating.save
flash[:success] = "A recorded has been successfully Saved"
redirect_to operatings_path
else
render 'new'
end
end
####routes####
resources :offshores, :index, :show, :new, :create, :destroy
resources :locations, :index, :show, :new, :create, :destroy
Try
class Operating < ApplicationRecord
belongs_to :location, :optional => true
end
belongs_to will check is the association present, you can debug by printing the Operating's errors
operating = Operating.create
operating.errors.messages

Rails why is my parent object not saving?

I'm quite new to rails/programming/web development, and this is the error that I'm getting:
ActiveModel::MissingAttributeError (can't write unknown attribute 'shopping_list_id')
I'm trying to save a shopping list object which is populated with Item objects after selecting specific Meal objects (with collection_check_box) and the associated Ingredient objects.
Looking through the localhost server logs, all the items are created correctly using the correct ingredients retrieved (and assigned to the current shopping list id) from the correct meal_ids - but when it comes to saving the actual shopping list I get the above error. Please see below for image of server log.
I've been stuck on this for a few days now and I've seen many posts with the same error but all seem to have quite different contexts to this issue.
Is it something to do with strong_parameters? Or are my associations wrong?
In my meals index.html.erb:
<%= form_for :shopping_list, url: shopping_lists_path do |f| %>
<%= collection_check_boxes :shopping_list, :meal_ids, Meal.all, :id, :name do |b|%>
<%= b.label class:"label-checkbox" do%>
<%=b.check_box + b.text%>
<%end%>
<% end %>
<%= f.submit "Create Shopping List" %>
<% end %>
My shopping_lists_controller:
def create
#shopping_list = ShoppingList.new(shopping_list_params)
#meals = Meal.where(id:[#shopping_list.meal_ids])
#meals.map do |meal|
meal.ingredients.map do |ingredient|
#shopping_list.items.build(name: ingredient.name, quantity: ingredient.quantity, unit: ingredient.unit, shopping_list_id: #shopping_list.id)
end
end
respond_to do |format|
if #shopping_list.save
format.html { redirect_to #shopping_list, notice: 'Shopping List was successfully created.' }
format.json { render :show, status: :created, location: #shopping_list }
else
format.html { render :new }
format.json { render json: #shopping_list.errors, status: :unprocessable_entity }
end
end
end
private
def set_shopping_list
#shopping_list = ShoppingList.find(params[:id])
end
def shopping_list_params
params.require(:shopping_list).permit(:name, {meal_ids: []})
end
My ShoppingList Model:
class ShoppingList < ApplicationRecord
has_many :items
has_many :meals
accepts_nested_attributes_for :meals
accepts_nested_attributes_for :items
end
My Item Model:
class Item < ApplicationRecord
belongs_to :shopping_list
end
My schema.rb:
create_table "ingredients", force: :cascade do |t|
t.integer "meal_id"
t.string "name"
t.float "quantity"
t.integer "unit"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["meal_id"], name: "index_ingredients_on_meal_id"
end
create_table "items", force: :cascade do |t|
t.integer "shopping_list_id"
t.string "name"
t.float "quantity"
t.integer "unit"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["shopping_list_id"], name: "index_items_on_shopping_list_id"
end
create_table "meals", force: :cascade do |t|
t.string "name"
t.string "description"
t.string "method"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "meal_image_file_name"
t.string "meal_image_content_type"
t.integer "meal_image_file_size"
t.datetime "meal_image_updated_at"
t.integer "diet"
end
create_table "shopping_lists", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
Image of server log
The issue is that Meal model has belongs_to :shopping_list but it doesn't have column shopping_list_id.
So when you run
ShoppingList.create(meal_ids: ['1'])
Rails tries to create Meal model and link it back to the ShoppingList, but it can't since there is no such attribute and error tells you that ActiveModel::MissingAttributeError. To fix create a migration e.g.:
add_column :meals, :shopping_list_id, :integer
In the create method of shopping_lists_controller, you are using a new instance of shopping_list and using the id of that instance to create items. Since the new instance is not saved to the db, it will not have id. So you cannot use #shopping_list.id while creating items.
So either change
#shopping_list = ShoppingList.new(shopping_list_params)
to
#shopping_list = ShoppingList.create(shopping_list_params)
or do
#shopping_list = ShoppingList.new(shopping_list_params)
#shopping_list.save
You may also want to destroy this object if any exception is raised.

Resources