Make advanced Search with Inner Join Tables - mysql

I have a question. I have two tables with an NN relationship. They are perfectly well linked in base (verified by the console with a Tips.find(1).categories). The two tables are Tip and Category. My objective is to create a search form that allows you to select a category, and to display all the Tip related to that category. I found a method that does this, it works for base links (i. e. if for example there was a category_id in the Tip table, which is not the case here). So I have to find a way to write the following search condition:
Tip.rb
class Tip < ApplicationRecord
has_many :category_to_tips
has_many :categories, through: :category_to_tips
has_many :comments
belongs_to :creator, class_name: "User"
has_many :likes, dependent: :destroy
def self.search(params)
tips = Tip.where("(tips) LIKE ?", "%#{params[:search]}%") if params[:search].present?
tips
end
end
Tip_controller :
def index
#tip = Tip.all
#tip = Tip.joins(:categories).search(params[:search])
end
form in index.html.erb :
<%= form_tag tips_path, :method => 'get' do %>
<p>
<%= text_field_tag :search, params[:search] %>
<%= submit_tag "Search", :name => nil %>
</p>
<% end %>
And when I run my form, i have this error :
PG::SyntaxError: ERREUR: erreur de syntaxe sur ou près de « . »
LINE 1: ...ry_to_tips"."category_id" WHERE ((Category.find(2).tips) LIK...
^
: SELECT "tips".* FROM "tips" INNER JOIN "category_to_tips" ON "category_to_tips"."tip_id" = "tips"."id" INNER JOIN "categories" ON "categories"."id" = "category_to_tips"."category_id" WHERE ((Category.find(2).tips) LIKE '%2%')
I hope I've been clear and I thank you in advance! :)

If I am reading it correct you are looking for a category so that you can display all the tips related to the category. Why don't you just do this in youre controller.
#tip = Category.where("name LIKE ?", "#{params[:search]}%").includes(:tips).map(&:tips).uniq
name would be the database field from category you are searching on.

Related

Rails - Having multiple 'has_many through 'associations on the same model

I am working on a rails app that registers campers for a camp. First you sign up using your name, email, etc. Once your profile is made, you can register for a camp. If the camp is not already full, then you are immediately enrolled. If it is full, then you are put on a waitlist. Right now I have 4 models: Campers (name, email, etc.), Camps (name, location, etc.), Enrollments, and Waitlists.
The idea is to have a camper be able to register to many camps, and obviously a camp having many campers enrolled or waitlisted in it. Here are my classes:
# camper.rb
has_many :enrolled_in, :class_name => 'Camps', through: :enrollments, dependent: :destroy
has_many :waitlisted_in, :class_name => 'Camps', through: :waitlists, dependent: :destroy
# camp.rb
has_many :enrolled_campers, :class_name => 'Camper', through: :enrollments
has_many :waitlisted_campers, :class_name => 'Camper', through: :waitlists
I'm having trouble with accessing these models through the views. Here is what show.html.erb looks like:
<!-- Listing camps -->
<h2>Camps</h2>
<p>
<strong>Name:</strong>
<%= #camper.enrolled_in.name %> <!-- This is where I get the error -->
</p>
<!-- Adding camps -->
<h2>Add a camp:</h2>
<%= form_with(model: [#camper, #camper.enrolled_in.build ]) do |form| %>
<p>
<%= form.label :name %><br>
<%= form.text_field :name %>
</p>
<p>
<%= form.submit %>
</p>
<% end %>
But I'm getting the following error:
ActiveRecord::HasManyThroughSourceAssociationNotFoundError in Campers#show
Could not find the source association(s) "enrolled_in" or :enrolled_in in model Enrollment. Try 'has_many :enrolled_in, :through => :enrollments, :source => '. Is it one of camp or camper?
And I honestly can't tell what's going wrong. I'm fairly new to databases and rails, so go easy on me.
Look to direction of HABTM associations.
This will make your code cleaner.
You will have :after_add and :after_destroy actions that will always track model changes (even direct foreign key inserting, unlike in same named has_many callbacks.)
So.
class Camper
has_and_belongs_to_many :waitlists, after_add: :check_camp_ready_to_start_as_example
has_and_belongs_to_many :enrolllists
end
class Waitlist
belongs_to :camp
has_and_belongs_to_many :campers
end
class Enrolllist
belongs_to :camp
has_and_belongs_to_many :campers
end
class Campl
has_many :enrolllists
has_many :waitlists
end
How to create such migrations (need to create join tables), you can read here

Ruby on Rails - hidden tag not passing into the database

Really new to RoR, and ran into an issue I can't find the answer to anywhere.
I have two tables, user and books, and I'm trying to use a join table to match up the user and the books and add a rating and review column in the join table.
The problem is everything passes into the join table EXCEPT the book_id
View
<%= form_for(current_user.user_book_collections.build) do |f| %>
<div>
<%= hidden_field_tag :book_id, current_book.id %>
<%= f.label :rating %>
<%= f.text_field :rating, class:"form-control" %>
<%= f.label :review %> (optional):
<%= f.text_area :review, size: "24x8", placeholder: "Please enter a brief review... ", class:"form-control" %>
</div>
<p></p>
<%= f.submit "Add to your collection", class:"btn btn-primary" %>
<% end %>
current_user and current_book are methods where the users and books are assigned respectively.
Controller
def create
#user_book_collection = current_user.user_book_collections.build(user_book_collection_params)
if #user_book_collection.save
flash[:success] = "Added to your collection"
redirect_to mybooks_path
else
flash[:danger] = "Add was unsuccessful"
redirect_to bookcollection_path
end
end
private
def user_book_collection_params
params.require(:user_book_collection).permit( :book_id, :user_id, :review, :rating )
end
end
This is what shows up in the console:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"85gmzUO7ldQrevh/qnKwYO9mkd9lX77sG3xxJQV8Y46xZrkl5ifk665abPr79nOT91rO3oLcMSDgYL7BtR+/XQ==", "book_id"=>"6", "user_book_collection"=>{"rating"=>"s", "review"=>"asd"}, "commit"=>"Add to your collection"}
but then checking the record also in the console i can see the book_id did not pass through even though its been assigned in the parameters:
=> #<ActiveRecord::Associations::CollectionProxy [#<UserBookCollection id: 10, user_id: 7, book_id: nil, review: "asd", rating: "s", created_at: "2016-02-07 06:36:33", updated_at: "2016-02-07 06:36:33">
Any help would be appreciated, thanks.
It is because your form is constructed in a way the book_id is passed in params[:book_id], not in params[:user_book_collection][:book_id] thus not present in user_book_collection_params
Use:
f.hidden_field :book_id, value: current_book.id
<%= f.hidden_field :book_id, current_book.id %>
When using form_for, you need to bind the input method to the form object (as above).
In addition to Rich Peck and Vasfeds excellent answers I would suggest you consider what you are doing from a REST point of view.
You want users to be able to add reviews to a book. Note that here UserBookCollections is just an ugly piece of plumbing - not really a resource which you would expose with REST. And calling it UserBookCollection is a bit misleading since each UserBookCollection is really just a link between one user and one book.
Also you want to avoid calling anything Collection in Rails since ActiveRecord uses the concept to denote a collection of linked records which may or may not be loaded - you're going to confuse others.
This is one way to solve the same problem
class User
has_many :reviews
has_many :books, through: :reviews
end
class Review
belongs_to :user
belongs_to :book
end
class Book
has_many :reviews, through: :reviews
end
Nice, now we have an object that we don't even have to explain what it does. So lets turn it into a RESTful resource:
Rails.application.routes.draw do
resources :reviews, only: [:show, :edit, :update, :destroy]
resources :books do
resources :reviews, only: [:new, :create, :index], module: :books
end
resources :users do
resources :reviews, only: [:index], module: :users
end
end
This gives us routes with context:
Prefix Verb URI Pattern Controller#Action
edit_review GET /reviews/:id/edit(.:format) reviews#edit
review GET /reviews/:id(.:format) reviews#show
PATCH /reviews/:id(.:format) reviews#update
PUT /reviews/:id(.:format) reviews#update
DELETE /reviews/:id(.:format) reviews#destroy
book_reviews GET /books/:book_id/reviews(.:format) books/reviews#index
POST /books/:book_id/reviews(.:format) books/reviews#create
new_book_review GET /books/:book_id/reviews/new(.:format) books/reviews#new
user_reviews GET /users/:user_id/reviews(.:format) users/reviews#index
Cool - now we have an API for Review with a built in context. The RESTful routes themselves tell us when we are creating a review for a certain book or looking at reviews by a certain user.
module: :books tells rails to link the nested routes to a "namespaced" controller instead of shoving everything into ReviewsController.
We setup our controllers as follows:
class ReviewsController < ApplicationController
before_action :set_review, only: [:edit, :show, :update, :destroy]
# GET /reviews/:id
def show
end
# GET /reviews
def index
#reviews = Review.all
end
# GET /reviews/:id/edit
def edit
end
# PATCH /reviews/:id
def update
#review.update(review_params)
respond_with(#review)
end
# DELETE /reviews/:id
def destroy
#review.destroy
respond_with(#review)
end
private
def set_review
#review = Review.find(params[:id])
end
def review_params
params.require(:review).permit(:rating, :body) # note that you don't need book_id in here.
end
end
# app/controllers/books/reviews_controller.rb
class Books::ReviewsController < ReviewsController
before_action :set_book
# GET /books/:book_id/reviews
def index
#reviews = Review.eager_load(:user, :book).where(book_id: params[:book_id])
end
# GET /books/:book_id/reviews/new
def new
#review = #book.find(params[:book_id]).reviews.new
end
# POST /books/:book_id/reviews
def create
#review = #book.reviews.new(review_params) do |review|
review.user = current_user
end
respond_with(#review)
end
private
def set_book
#book = Book).find(params[:book_id])
end
end
# app/controllers/users/reviews_controller.rb
class Users::ReviewsController < ApplicationController
# GET /users/:user_id/reviews
def index
#reviews = Review.joins(:user, :book).where(user_id: params[:user_id])
end
end
You might question why you would want to use 3 controllers? It allows for a very clean mechanism of both code sharing and customizations. You can have different logic and views without creating a bunch of if or switch code paths. Having many branches in a single action makes testing really messy and violates the skinny controllers paradigm.
The forms would look something like this:
# app/views/reviews/_fields.html.erb
<%= f.label :rating %>
<%= f.text_field :rating, class:"form-control" %>
<%= f.label :body %> (optional):
<%= f.text_area :body, size: "24x8", placeholder: "Please enter a brief review... ", class:"form-control" %>
# app/views/books/reviews/new.erb
<%= form_for([#book, #review]) do |f| %>
<%= render partial: 'reviews/fields', f: f %>
<% end %>
# app/views/books/edit.erb
<%= form_for(#review) do |f| %>
<%= render partial: 'reviews/fields', f: f %>
<% end %>

only one select box shows up due to duplicate id

I have a form where there can be three select boxes depending upon a checkbox - I'm using jQuery to show/hide them depending on which checkbox is active. Below, f is player
<% Game.all.each_with_index do |game, i| %>
<div class="team_<%= i %> hide">
<%= f.collection_select(:team_division_id, TeamDivision.where("game_id = ?", game.id),
:id, :name, include_blank: true) %>
</div>
<% end %>
Each collection select is generating an id of player_team_division_id - and since there can be only one id a page, this is really screwing with my results, namely the first select box shows up fine, but the others show up as simple links to javascript:void(0) - if I check out the generated divs with firebug/chrome, the information is there, it just isn't showing up as a select box (presumably) because of the id. I'm using chosen.js, if that makes a difference, although I'm don't think this has any affect.
Edit (Models):
class Player < ActiveRecord::Base
belongs_to :game
belongs_to :team_division, touch: true
end
class Game < ActiveRecord::Base
has_many :players
has_many :team_divisions
end
Class TeamDivision < ActiveRecord::Base
has_many :players
belongs_to :game
end

using last after model included doesnt work

Im stuck (still very new to Rails), and cant figure out why its not working:
I have:
class Message < ActiveRecord::Base
attr_accessible :updated_at
has_many :categories_messages
has_many :categories, through: :categories_messages
end
class CategoriesMessage < ActiveRecord::Base
attr_accessible :category_id, :message_id
belongs_to :category
belongs_to :message
end
class Category < ActiveRecord::Base
attr_accessible :name
has_many :categories_messages
has_many :message, through: :categories_messages
end
#messagesPer = Category.all.includes(:messages).group('categories.id').order("COUNT(messages.id) DESC")
<% #messagesPer.each_with_index do |message, i| %>
<tr>
<td><%= i+1 %></td>
<td><%= message.name %></td>
<% if message.categories_messages.exists? %>
<td><%= message.messages.last.updated_at.to_s.to_date %></td>
<td><%= message.messages.first.updated_at.to_s.to_date %></td>
<td><%= message.messages.count %></td>
<% else %>
<td>0</td>
<td>0</td>
<% end %>
</tr>
<% end %>
So i want it to show :
the name of Category, date the last message was created , date first message was created, and all messages in that category.
ALl works fine, apart from the fact that it only shows the date when the first message was created, but never the last (still shows first date on the last).
What am i doing wrong?
UPDATE:
if i put
#messagesPer = Category.all.includes(:messages).group('categories.id')
it does show the right date for last and first messages but as soon as i add order it breaks...
I might be helpful to include the error information you get after adding an order clause to the query.
However, I can spot some odd things in the code. The CategoriesMessage model seems to be there simply to satisfy the condition that a category can have many messages and vice versa. You don't need a model for this many-to-many relationship though, Rails will handle this automatically for you.
Your models should be looking like this:
class Message < ActiveRecord::Base
attr_accessible :updated_at
has_and_belongs_to_many :categories
end
class Category < ActiveRecord::Base
attr_accessible :name
has_and_belongs_to_many :messages
end
And in your database you have these tables: messages, categories,categories_messages, where the last one is the join table which only contains columns for amessage_idand acategory_id`.
Then you can simply do something like this in your code:
category.messages.each { |message| puts message.updated_at }
Also see this Ruby on Rails tutorial article for more information. If this doesn't work for you, please post the exact error you get.

has_many , through: relationship count

I was hoping someone could help me with this, been trying to figure it out for a week now, I found a lot of examples, but as I'm new to rails I guess I keep making a mistake somewhere and I just cant find a right solution for my case.
So I have:
class Blog < ActiveRecord::Base
attr_accessible :name, :subject_id, :created_at
has_many :blogs_messages
has_many :messages, through: :blogs_messages
end
class Message < ActiveRecord::Base
attr_accessible :title, :body, :created_at
has_many :blogs_messages
has_many :blogs, through: :blogs_messages
end
class BlogsMessages < ActiveRecord::Base
attr_accessible :message_id, :blog_id
belongs_to :blog
belongs_to :message
end
Messages live in different Blogs(like Pink Blog, Green Blog, Maroon Blog etc), and Blogs live in Subjects (Dark Colors, Bright Colors etc)
Subjects have many Blogs, but Blogs can belong only to one Subject.
BlogsMessages is the connection between Messages and Blogs
what im trying to do is to show:
top 3 Blogs (by amount of messages in them) within one Subject
so e.g. when I want to choose Subject Dark Colors it will show me:
1.Maroon Blog: 46 messages
2.Grey Blog: 13 messages
3.Purple Blog: 12 messages
(There are 8 Blogs altogether in Subject Dark Colors.)
Could someone please help me with this, or at least point me in the right direction how to make it all work?
Update:
in my Blogs_controller now i have:
#blogs = Blog.joins(:blogs_messages => :message).select('blogs.*, COUNT(messages.id) AS message_count').group('blog_id').order('COUNT(messages.id) DESC').limit(3)
in my blogs view:
<% #blogs.each do |blog| %>
<li><%= blog.name %>: messages</li>
<% end %>
I'm not sure this can work because I can't test it but it may help you:
Blog.where(subject_id: subject.id)
.joins(:blogs_messages => :message)
.select('blogs.*, COUNT(messages.id) AS message_count')
.group(:blog_id)
.order('message_count DESC')
.limit(3)
Also, in the view you could access to the new virtual attribute message_count:
<% #blogs.each do |blog| %>
<li><%= blog.name %>: <%= blog.message_count %> messages</li>
<% end %>