Retrieving data from third model in database using primary keys - ruby-on-rails

I would like to access the current user's albums by using the primary keys in the Schema below. I have User, Band, And Album models as follows...
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :bands
end
class Band < ActiveRecord::Base
has_many :albums
has_many :users
end
class Album < ActiveRecord::Base
belongs_to :band
end
Schema as follows...
create_table "albums", force: true do |t|
t.string "name"
t.string "releaseDate"
t.string "artWorkUrl"
t.integer "band_id"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "bands", force: true do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "user_id"
end
create_table "users", force: true 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.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at"
t.datetime "updated_at"
end
and am trying to use the following method in the Albums controller...
def albums
#albums = current_user.bands.albums
end
Sorry, i'm sure this is a noob question. I know this should be a simple primary key access through the user --> bands --> albums, using user_id and band_id but have been unable to populate the current users albums through bands. Any insight in much appreciated.

def albums
band_ids = current_user.bands.map(&:id)
#albums = Album.where(band_id: band_ids)
end
By separating the queries you do not have the n+1 problem: What is SELECT N+1?

by using the primary keys
You should be looking at the foreign_keys (primary_keys are when you reference the same table). So what you're really asking should be how do I set up the Rails associations correctly for my models?
Here's how:
#app/models/user.rb
Class User < ActiveRecord::Base
has_many :bands
end
#app/models/band.rb
Class Band < ActiveRecord::Base
has_many :albums
belongs_to :user
end
#app/models/album.rb
Class Album < ActiveRecord::Base
belongs_to :band
end
This will allow you to call current_user.bands.first.albums
--
Notes
You have to remember the bands association will be a collection. This means you can't just call ....bands.albums. Instead, you'll need to loop through the array like this:
<% for band in current_user.bands do %>
<%= band.albums.inspect %>
<% end %>

In user class define one more association
class User < ActiveRecord::Base
has_many :albums, :through => bands
end
In your controller
#albums = current_user.albums

Related

Unitialized constant User:Bookings when trying to add data into a join table

I have a User table and a Booking Table that is linked by a create_join_table what holds the user id and booking ids. When a user books a room, i need the id of both the user and new booking to go into that. I am getting the error above and im not sure why.
I have looked online and saw something similar, their class names were plural however I don't think I have that.
booking.rb
class Booking < ApplicationRecord
enum room_type: ["Basic Room", "Deluxe Room", "Super-Deluxe Room", "Piton Suite"]
has_many :join_tables
has_many :users, through: :join_tables
end
user.rb
class User < ApplicationRecord
has_secure_password
validates :email, format: {with: URI::MailTo::EMAIL_REGEXP}, presence: true, uniqueness: true
has_many :join_tables
has_many :bookings, through: :join_tables
end
join_table.rb
class JoinTable < ApplicationRecord
belongs_to :users
belongs_to :bookings
end
bookings_controller.rb
def create
#booking = Booking.create(booking_params)
current_user.bookings << #booking ##Where the error happens
db/schema
ActiveRecord::Schema.define(version: 2019_12_13_181019) do
create_table "bookings", force: :cascade do |t|
t.integer "room_type"
t.date "check_in"
t.date "check_out"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "join_tables", force: :cascade do |t|
t.integer "users_id"
t.integer "bookings_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["bookings_id"], name: "index_join_tables_on_bookings_id"
t.index ["users_id"], name: "index_join_tables_on_users_id"
end
create_table "users", force: :cascade do |t|
t.string "name"
t.string "email"
t.string "password_digest"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
I have just tried to reproduce your problem and I have a similar exception
irb(main):003:0> User.first.bookings
NameError (uninitialized constant User::Bookings)
but, when I change
belongs_to :users
belongs_to :bookings
to
belongs_to :user
belongs_to :booking
in app/models/join_table.rb everything works as expected.
This is how I created the JoinTable model
$ rails generate model JoinTable
class CreateJoinTables < ActiveRecord::Migration[6.0]
def change
create_table :join_tables do |t|
t.references :user
t.references :booking
t.timestamps
end
end
end
As you can see in the belongs_to docs, it is used in the singular form most of the time.

Create records with associated tables in Rails

I am new to ruby on rails and don't understand how to create and save records using associated tables. I want the controller to take the data create a product record and then create as many property and product properties associated with that product. The property and product property have a one to one relationship. The product can have many properties and product properties.
Properties and product properties are coming in like this:
{"name"=>"color", "value"=>"red"}
{"name"=>"material", "value"=>"cotton"}
My controller works for the creation of the product but I am unsure how to create a loop that will build as may associated product and product properties that come in the array sent from the client.
My controller now:
class SendDataController < ApplicationController
protect_from_forgery with: :null_session
def hi
product = Product.new
product.name = params[:name]
product.upc = params[:upc].to_i
product.available_on = params[:availableon]
product.save
end
end
Below are my models:
class Product < ApplicationRecord
has_many :propertys, dependent: :destroy
has_many :product_propertys, dependent: :destroy
end
class Property < ApplicationRecord
belongs_to :product
has_one :product_property, dependent: :destroy
end
class ProductProperty < ApplicationRecord
belongs_to :property
belongs_to :product
end
Migration:
class CreateProducts < ActiveRecord::Migration[5.2]
def change
create_table :products do |t|
t.string :name
t.string :upc
t.datetime :available_on
t.timestamps
end
end
end
class CreateProductProperties < ActiveRecord::Migration[5.2]
def change
create_table :product_properties do |t|
t.string :value
t.belongs_to :property
t.belongs_to :product
t.timestamps
end
end
end
class CreateProperties < ActiveRecord::Migration[5.2]
def change
create_table :properties do |t|
t.string :name
t.belongs_to :product
t.timestamps
end
end
end
schema:
ActiveRecord::Schema.define(version: 2018_09_22_140824) do
create_table "product_properties", force: :cascade do |t|
t.string "value"
t.integer "property_id"
t.integer "product_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["product_id"], name: "index_product_properties_on_product_id"
t.index ["property_id"], name: "index_product_properties_on_property_id"
end
create_table "products", force: :cascade do |t|
t.string "name"
t.string "upc"
t.datetime "available_on"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "properties", force: :cascade do |t|
t.string "name"
t.integer "product_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["product_id"], name: "index_properties_on_product_id"
end
end
Thanks for any help you can give a new guy!
Your Product Model plurality required, has_many properties & equally has_many product_properties.
Your Property schema will need product_id as an integer. i would avoid using has_one it can get messy, just use has_many or you may require a has_many through
Your ProductProperty Model You'll also need product_id integer & property_id integer adding them as separate migration.
rails db:create add_product_id_to product_properties, product_id:integer
check the migration file product_id that the attribute is in the file
rails db:migrate
Restart server & test in the console.
Once the Models speak, instantiate a Product object, bring it across into Properties & ProductProperties through the respective controllers by setting & in turn making the SendDataController obsolete unless your logic requires this.

Rails InvalidForeignKey

I am developing a portfolio for my website, I decided to add skills to each portfolio item.
class PortfolioSkill < ApplicationRecord
belongs_to :portfolio
belongs_to :skill
end
class Portfolio < ApplicationRecord
has_many :portfolio_skills
has_many :skills, through: :portfolio_skills
def all_tags=(names)
self.skills = names.split(",").map do |name|
Skill.where(name: name.strip).first_or_create!
end
end
def all_tags
self.skills.map(&:name).join(", ")
end
def remove_skill_tags
PortfolioSkill.where(portfolio_id: id).destroy_all
end
end
create_table "portfolio_skills", force: :cascade do |t|
t.integer "portfolio_id"
t.integer "skill_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["portfolio_id"], name: "index_portfolio_skills_on_portfolio_id"
t.index ["skill_id"], name: "index_portfolio_skills_on_skill_id"
end
create_table "portfolios", force: :cascade do |t|
t.string "name"
t.string "client"
t.date "completed"
t.text "about"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "long_landscape"
t.string "cover"
t.integer "category_id"
t.index ["category_id"], name: "index_portfolios_on_category_id"
end
When I click destroy on the index page I get the
SQLite3::ConstraintException: FOREIGN KEY constraint failed: DELETE FROM "portfolios" WHERE "portfolios"."id" = ?
error. All the associations look right. I used this same pattern for my tags on other models and it worked with no issues. Any help would be great.
You are deleting from portfolios table, but table portfolio_skills has a column referencing it as foreign key. Hence the error.
Trying to delete a parent without checking and deleting its associated children can lead to data inconsistency. This exception is in place to prevent that.
Rails dependent destroy will take care of removing associated children rows while removing a parent.
Try using a dependent destroy:-
class Portfolio < ApplicationRecord
has_many :portfolio_skills, :dependent => :destroy
...
end

crossing borders of tables: language role

I'm new to development, and have spent the last 12 hours (literally) trying to figure out this error message - I'm giving up for the night, but not before a quick cry for help to stackoverflow.
I have this form:
<h2>Select from the language options below (or, <%= button_to "Login", 'users/login', method: :get %></h2>
<%= form_for #language_role do |f| %>
<div id="input">
<h3>I want to learn:</h3><%= select_tag(:language_id, options_from_collection_for_select(Language.all, :id, :lang_name)) %>
</div>
<div>
<p><%= f.submit "Start learning" %></p>
</div>
<% end %>
which is giving me this error message, highlighting the line #language_role = current_user.language_roles.build : "undefined method `language_roles' for nil:NilClass"
I have three tables:
create_table "language_roles", force: :cascade do |t|
t.integer "language_id"
t.boolean "is_active"
t.boolean "is_teacher"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
t.index ["user_id"], name: "index_language_roles_on_user_id"
end
create_table "languages", force: :cascade do |t|
t.string "lang_name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
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.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
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
The language_roles table is meant to allow a user to have many languages, as well as many roles within that language. Here are my class definitions:
class LanguageRole < ApplicationRecord
belongs_to :languages
belongs_to :users
end
class Language < ApplicationRecord
has_many :language_roles
has_many :users, :through => :language_roles
end
class User < ApplicationRecord
has_many :language_roles
has_many :languages, :through => :language_roles
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
My root path goes to 'home#index', where the user is supposed to pick a language if current_user.language_roles is empty. As such, I put this code in my home controller and language_roles controller:
class HomeController < ApplicationController
def index
#language_role = current_user.language_roles.build
end
end
class LanguageRolesController < ApplicationController
def create
#language_role = current_user.language_roles.build(language_role_params)
if #language_role.save
redirect_to root_path
else
redirect_to :back
end
end
private
def language_role_params
params.permit(:language_id)
end
end
What in the hell is the problem?? I assume I need to instantiate the variable somehow, but I'm not sure how.
Thanks,
Michael
There is a typo in your LanguageRole Model:
LanguageRole < ApplicationRecord
belongs_to :languages
belongs_to :users
end
should be
LanguageRole < ApplicationRecord
belongs_to :language
belongs_to :user
end
belongs_to associations must use the singular term.
The name of the other model is pluralized when declaring a has_many association.
Ref: http://guides.rubyonrails.org/association_basics.html
Your current_user is not defines it seems. You can install a gem called 'pry-rails' and debug your way out of this situation and any other in future. Here's a tutorial how to use it Railscasts #280
In your LanguageRole model you defined like belongs_to :users. But it should be belongs_to :user.
Your model look like ...
LanguageRole < ApplicationRecord
belongs_to :languages
belongs_to :users
end
Which should be something like
LanguageRole < ApplicationRecord
belongs_to :language
belongs_to :user
end

Error in rails saving attribute to object

I have 2 models: User and UserLvl.
class User < ActiveRecord::Base
has_one :user_lvl, primary_key: 'user_lvl_id', foreign_key: 'id'
end
class UserLvl < ActiveRecord::Base
belongs_to :user
end
Controller action
def change_lvl
#user.user_lvl = UserLvl.first
#user.save
end
UserLvl.first is returned fine,with id and all but it failes at the first line with : "Column 'id' cannot be null"
why is this happening?
EDIT:
schema.rb
create_table "users", force: true do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.integer "user_lvl_id"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "user_lvls", force: true do |t|
[omited some information]
t.datetime "created_at"
t.datetime "updated_at"
end
You probably want to use a belongs_to association rather than a has_one, like this:
class User < ActiveRecord::Base
belongs_to :user_lvl
end
Your foreign key is placed in the users table, which makes User the associated model, that belongs to the UserLvl.

Resources