Pull data from multiple tables in Rails - sql

I am new in Ruby on Rails and I am trying to make a book registration website. Everything works fine except category section. When a user assign a category to his book, my database copies book_categories.id and put it in book.book_categories_id. The website also have a profile page, where you can view user's book(s). My problem is to display a category.name, I searched a lot of similar problems but I have not found the right answer.
Here is my book controller:
before_action :set_book, except: [:index, :new, :create]
before_action :authenticate_user!, except: [:show]
def show
#photos = #book.photos
end
def index
#books = current_user.books
end
def new
#book = current_user.books.build
end
def create
#book = current_user.books.build(books_params)
if #book.save
redirect_to listing_book_path(#book), notice: "Saved."
else
flash[:alert] = "Failed."
render :new
end
end
private
def set_book
#book = Book.find(params[:id])
end
def book_params
params.require(:book).permit(:book_categories_id, :book_name, :summary, :address, :price, :company_name)
end
As long as I understand, I have to allow my controller to have access to my category table but I do not know how. Also, all of my categories store in seed.rb.
This is my BookCategory.rb model:
class BookCategory < ActiveRecord::Base
has_many :books, :foreign_key => :book_categories_id
end
Book.rb model:
class Book < ApplicationRecord
belongs_to :user, :foreign_key => 'user_id'
has_many :photos, dependent: :delete_all
validates :book_name, presence: true
validates :book_categories_id, presence: true
def cover_photo
if self.photos.length > 0
self.photos[0].image.url
else
"default/image-default.jpg"
end
end
end
My schema.rb:
create_table "book_categories", force: :cascade do |t|
t.string "name"
t.string "subcategory"
t.text "description"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "book", force: :cascade do |t|
t.string "book_name"
t.text "summary"
t.string "address"
t.decimal "price", precision: 8, scale: 2
t.boolean "active"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
t.float "latitude"
t.float "longitude"
t.string "company_name"
t.integer "book_categories_id"
t.index ["book_categories_id"], name: "index_books_on_book_categories_id"
end
Usually, when I type #book_categories.name, I get an error:
undefined method `name' for nil:NilClass
And at the very end, it says Parameters: {"id"=>"15"} whereas the category.id is different, and it passes book.id instead.
What I am doing wrong?

First fix the naming problem you have going. I don't know how the books table got called "book" instead of "books" in the schema, but you might have to fix that first.
Then category should be singular everywhere except the database table name. You need to make a migration to fix the foreign key and its index in the books table something like this:
class FixCategoryNaming < ActiveRecord::Migration
def change
remove_column :books, :book_categories_id
add_reference :books, :book_category, index: true, foreign_key: true
end
end
Then run rails db:migrate and check the schema looks ok
Then in the BookCategory class change the line to simply
has_many :books
You need to add this line to your Book class
belongs_to :book_category
and change the validation for book_category to be this
validates :book_category_id, presence: true
Then in the view for book index you have a #books variable. You can get the book name and category name for each book like this:
<% #books.each do |book| %>
<p><%= book.book_name %></p>
<p><%= book.book_categories.name %></p>
<% end %>

I think you need to setup as has_many through relationship. This is a good place to start http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association

Related

How to count all the likes that User has done on my specific items (Rails)

My apologies for the newbie question, I'm relatively new to Rails and SQL. I'm trying to count all the likes a User has done on my specific items. With the help of the SO community and from Treehouse Coding I am able to show which Users have liked my specific items. In addition, I was able to research how to show all the likes that I have received (from Lisa, James, Harry, etc.) using .join in my controller. Now I would like to show the count for each specific User. So for instance if James like 4 of my items I would like to show 4 next to James. I have listed all my relevant code below, thank you guys so much!!
Index.html.erb
My Fans - <%= #likersnumero%>
<%- #likers.each do |liker| %>
<%= image_tag liker.avatar, width: 25, class: "css-style" %>
<%= liker.username %>
<% end %>
Items_controller
def index
#items = Item.order("created_at DESC")
if current_user.present?
#likers = current_user.items.map(&:likes).flatten.map(&:user).flatten
#likersnumero = current_user.items.joins(:likes).map(&:user).count
end
end
Item.rb
class Item < ApplicationRecord
has_many :likes, :counter_cache => true
end
Users.rb
class User < ApplicationRecord
has_many :likes
def likes?(post)
post.likes.where(user_id: id).any?
end
#Tried using def total_likes in my index.html.erb using <%= liker.total_likes%>
but got the following
error SQLite3::SQLException: no such column: likes:
SELECT SUM(likes) FROM "likes" WHERE "likes"."user_id" = ?"
def total_likes
likes.sum(:likes)
end
end
Routes.rb
Rails.application.routes.draw do
resources :items do
resource :like, module: :items
end
root to: "items#index"
end
Schema.rb
create_table "items", force: :cascade do |t|
t.string "product"
t.integer "user_id"
t.integer "item_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "avatar_file_name"
t.string "avatar_content_type"
t.integer "avatar_file_size"
t.datetime "avatar_updated_at"
t.integer "likes_count", default: 0, null: false
end
create_table "likes", force: :cascade do |t|
t.integer "user_id"
t.integer "item_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
Given liker is the user who liked post and poster_id is the id of the person who create the post.
liker.likes.joins(:items).where('items.user_id = ?', poster_id).count
Or somethings similar. If you post your annotated model or table schema I can probably come up with a better answer.
After countless reading and experiementing I figured out the answer that worked for me. In case anyone might encounter a similar issue - I have provided the answer that worked for me.
To get total likes by a specific user on all users items - In my controller I put the following
#likersuno = #likers.map(&:likes).flatten.map(&:user).flatten.uniq
And in my index.html.erb
I simply put this and it worked from there
<%= #likersuno.count %>
To get total likes by a specific user on my specific items - In my index.html.erb I put the following and it worked beautifully
<%= liker.likes.select { |like| like.item.user == current_user }.count %>

How to call references to other tables in a model rails

I have a model called cause.rb which has a scope called pesquisa that I want to query in the table causes, fields that are references to other tables. I want to call the table places which have a field called city from the model cause.
Causes table:
class CreateCauses < ActiveRecord::Migration
def change
create_table :causes do |t|
t.string :title, null: false
t.text :description, null: false
t.boolean :anonymous, default: false
t.integer :count_likes, default: 0
t.integer :count_dislikes, default: 0
t.integer :count_visits, default: 0
t.references :user
t.references :category
t.timestamps
end
add_index :causes, :user_id
add_index :causes, :category_id
end
end
class AddPlaceIdToCauses < ActiveRecord::Migration
def change
add_column :causes, :place_id, :integer
end
end
Places table:
class CreatePlaces < ActiveRecord::Migration
def change
create_table :places do |t|
t.string :description
t.decimal :latitude
t.decimal :longitude
t.references :city
t.timestamps
end
add_index :places, :city_id
end
end
Here is the scope:
scope :pesquisa, ->(pesquisa) { where("cause.place.city like :p or category_id like :p ", p: "%#{pesquisa}%") }
default_scope :order => "id desc"
How do I resolve this problem? I try this above cause.place.city to call the city in the table places but nothing happens.

has_many relationship with images in the def create

I am trying to create many images for a single product.
Since the number of images per product is as many as the user wants to enter I have created 2 separate models, product and product_image.
Product has many product_images
and product_images belongs_to product
I'm almost certain that this section of code is the problem (this is the product_image controller)
def create
#product_image = ProductImage.new(params[:product_image])
#product = #product_image.product
if #product_image.save
#product_image.product_id = #product.id
#product_image.save
redirect_to #product_image, notice: 'Product image was successfully created.'
else
render :template => "products/edit"
end
end
At the moment the code allows me to upload an image via paperclip but totally disregards the product_id and just puts the product_image_id in that field instead.
I checked the db through the cmd line to see this.
So how do I get an image to be created with an ID of a particular product? I've searched this site but the questions that exist do seem to offer the solution that I require.
Thanks for any help you can offer.
Here are the migrations that I used for the models relating to products and product_images
I apologize for the mess, I was very indecisive in my initial development which caused lost of little changes to be made as I gained more knowledge about the whole rails system
products
class CreateProducts < ActiveRecord::Migration
def change
create_table :products do |t|
t.string :name
t.text :description
t.string :image_url
t.decimal :price, precision: 8, scale: 2
t.timestamps
end
end
end
and, products
class AddColumnsToProducts < ActiveRecord::Migration
def change
drop_table :products
create_table :products do |t|
t.string :product_title
t.text :product_desc
t.string :product_image_url
t.decimal :price, precision: 8, scale: 2
t.timestamps
end
end
end
and, products
class AddColumnToProducts < ActiveRecord::Migration
def change
add_column :products, :department, :string
end
end
and, products
class AddMoreColumnsToProducts < ActiveRecord::Migration
def change
add_column :products, :display_on_home_page, :boolean, default: false
add_column :products, :is_highight_product, :boolean, default: false
end
end
and, products
class RenameIsHighightProductInProducts < ActiveRecord::Migration
def up
rename_column :products, :is_highight_product, :is_highlight_product
end
def down
end
end
and, products
class RenameProductImageUrlInProducts < ActiveRecord::Migration
def up
rename_column :products, :product_image_url, :image_url
end
def down
end
end
and product images table created
class CreateProductImages < ActiveRecord::Migration
def change
create_table :product_images do |t|
t.integer :product_id
t.string :title
t.text :description
t.string :image_file_name
t.string :image_content_type
t.integer :image_file_size
t.datetime :image_updated_at
t.timestamps
end
end
end
and, products
class AlterTableProducts < ActiveRecord::Migration
def up
end
remove_column :products, :image_url
add_column :products, :product_image_id, :integer
def down
end
end
and, product_images
class AddColumnToProductImages < ActiveRecord::Migration
def change
add_column :product_images, :image_path, :string
end
end
and, product_images
class RenameColumnImagePathInProductImages < ActiveRecord::Migration
def up
rename_column :product_images, :image_path, :image_url
end
def down
end
end
and, product_images
class AddProductTitleColumnToProductImages < ActiveRecord::Migration
def change
add_column :product_images, :product_title, :string
end
end
and finally, products
class DropPriceFromProductsAndAddPriceToProducts < ActiveRecord::Migration
def up
end
remove_column :products, :price
add_column :products, :price, :decimal, :precision => 8, :scale => 2
def down
end
end
I am not sure what is going wrong because there is a little too less info in your question. But let me show quickly, how this should be set up (simplified).
new rails app:
rails new stack_product
creating the models
rails g model product
rails g model image
You get all this (you have to add the attr_accessible attributes by hand here)
app/models/product.rb
class Product < ActiveRecord::Base
attr_accessible :title, :description
has_many :images
end
app/models/image.rb
class Image < ActiveRecord::Base
attr_accessible :name, :path, :product_id
belongs_to :product, foreign_key: "product_id"
end
db/migrations/20131011195035_create_products.rb
class CreateProducts < ActiveRecord::Migration
def change
create_table :products do |t|
t.primary_key :id
t.string :title
t.string :description
t.string :image_url
t.timestamps
end
end
end
20131011195421_create_images.rb
class CreateImages < ActiveRecord::Migration
def change
create_table :images do |t|
t.primary_key :id
t.integer :product_id
t.string :name
t.string :path
t.timestamps
end
end
end
use rails console in the terminal.
rails console
the fire:
Product.create({title: 'Ford Mustang', description: 'The one and only Shelby'})
...
Image.create({product_id: 1, name: 'Image Mustang', path: '/images/mustang.png'})
Image.create({product_id: 1, name: 'Image Mustang from behind', path: '/images/mustang2.png'})
then you can query the objects
p = Product.find(1)
Product Load (0.2ms) SELECT "products".* FROM "products" WHERE "products"."id" = ? LIMIT 1 [["id", 1]]
=> #<Product id: 1, title: "Ford Mustang", description: "The one and only Shelby", image_url: nil, created_at: "2013-10-11 20:14:06", updated_at: "2013-10-11 20:14:06">
Image.where("product_id=?", p.id)
Image Load (0.3ms) SELECT "images".* FROM "images" WHERE (product_id=1)
=> [#<Image id: 1, product_id: 1, name: "Image Mustang", path: "/images/mustang.png", created_at: "2013-10-11 20:14:09", updated_at: "2013-10-11 20:14:09">, #<Image id: 2, product_id: 1, name: "Image Mustang from behind", path: "/images/mustang2.png", created_at: "2013-10-11 20:14:26", updated_at: "2013-10-11 20:14:26">]
So this works fine. If you would create forms for this, you would have one for the products and another one for the images. The images form would have a dropdown with all the products (productname and value with id). The dropdown for the products would be named product_id and then the product's id would be saved in the image table as product_id.
You should maybe scaffold all this to see how it is be done by Rails.

Ruby on Rails Has_Many :Through Association

I'm having some problems with ruby on rails, specifically setting up a many-to-many connection with deal and event through deal_event. I've checked out several similar stackoverflow questions and even http://guides.rubyonrails.org/ but I'm still not getting something..
Here are my models:
deal.rb
class Deal < ActiveRecord::Base
has_many :deal_events
has_many :events, :through => "deal_events"
attr_accessible :approved, :available, :cents_amount, :dollar_amount, :participants, :type
end
event.rb
class Event < ActiveRecord::Base
has_many :deal_events
has_many :deals, :through => "deal_events"
attr_accessible :day, :image, :description, :location, :title, :venue, :remove_image
end
deal_event.rb
class DealEvent < ActiveRecord::Base
belongs_to :deal
belongs_to :event
end
And here are my migration files:
20130102150011_create_events.rb
class CreateEvents < ActiveRecord::Migration
def change
create_table :events do |t|
t.string :title, :null => false
t.string :venue
t.string :location
t.text :description
t.date :day
t.timestamps
end
end
end
20130112182824_create_deals.rb
class CreateDeals < ActiveRecord::Migration
def change
create_table :deals do |t|
t.integer :dollar_amount
t.integer :cents_amount
t.integer :participants
t.string :type, :default => "Deal"
t.integer :available
t.string :approved
t.timestamps
end
end
end
20130114222309_create_deal_events.rb
class CreateDealEvents < ActiveRecord::Migration
def change
create_table :deal_events do |t|
t.integer :deal_id, :null => false
t.integer :event_id, :null => false
t.timestamps
end
end
end
After I seed my database with one deal and one event, I go into the console and type in
deal = Deal.first # ok
event = Event.first # ok
DealEvent.create(:deal => deal, :event => event) # Error: ActiveModel::MassAssignmentSecurity::Error: Can't mass-assign protected attributes: deal, event
deal.events # Error: ActiveRecord::HasManyThroughAssociationNotFoundError: Could not find the association "deal_events" in model Deal
Any idea on what I'm doing wrong for these two errors to be popping up? Thanks.
You'll need this line in your DealEvent model:
attr_accessible :deal, :event
Although if it's just a relationship table (which it looks like) then you don't create the relationship that way. Use nested forms and the like.

Give composite primary key in Rails

How can i give composite primary key in Rails without any gem?
My first table in migration file:
class CreateUsers < ActiveRecord::Migration
def self.up
create_table :users do |t|
t.string :userid
t.string :name
t.string :address
t.timestamps
end
end
def self.down
drop_table :users
end
end
My second table in migration file:
class CreateProjects < ActiveRecord::Migration
def self.up
create_table :projects do |t|
t.string :title
t.string :description
t.timestamps
end
end
def self.down
drop_table :projects
end
end
In my schema file:
ActiveRecord::Schema.define(:version => 20110222044146) do
create_table "projects", :force => true do |t|
t.string "title"
t.string "description"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "users", :force => true do |t|
t.string "userid"
t.string "name"
t.string "address"
t.datetime "created_at"
t.datetime "updated_at"
end
end
Now I want to create a table called User_has_project in which I will refer to User and Project that means will have 2 foreign keys.
So I tried like this:
class CreateUser_has_projects < ActiveRecord::Migration
def self.up
create_table :user_has_projects do |t|
t.references :User
t.references :Project
t.boolean :status
t.timestamps
end
end
def self.down
drop_table :users
end
end
Now how can I set combination of user_id and project_id as a primary key in user_has_projects?
It looks like you're trying to specify a many-many relationship between Users and Projects, with an additional field on the relationship itself.
The way you're currently doing isn't the Rails way of doing things - especially with the concept of a composite primary key.
The Rails/ActiveRecord way of doing this sort of relationship modelling is to have a third model that describes the relationship between User and Project. For the sake of example, I'm going to call it an Assignment. All you need to do is re-name your user_has_projects table to assignments like so:
class CreateAssignments < ActiveRecord::Migration
def self.up
create_table :assignments do |t|
t.references :user
t.references :project
t.boolean :status
t.timestamps
end
end
def self.down
drop_table :assignments
end
end
And then, in your model files:
# app/models/user.rb
class User < ActiveRecord::Base
has_many :assignments
has_many :projects, :through => :assignments
end
# app/models/assignment.rb
class Assignment < ActiveRecord::Base
belongs_to :user
belongs_to :project
end
# app/models/project.rb
class Project < ActiveRecord::Base
has_many :assignments
has_many :users, :through => :assignments
end
You can read more about this here: http://guides.rubyonrails.org/association_basics.html#the-has_many-through-association