Why doesn't my user follow/unfollow button work? - ruby-on-rails

I am working on building an application (following Michael Hartl's chapter 11) where users can follow projects that are created by other users.
I created a ProjectRelationship model to hold two components: follower_id for the users and projectuser_id for the projects. The foreign keys have been set up as such.
Right now, my _follow_form.html.erb page renders "follow" or "unfollow" depending on whether the current_user is following the project. Please see my code below and see what I am missing.
Right now, the follow button is generated on each project show page. But when I click the button follow button that is generated by _follow.html.erb, it does not seem to follow the project or update the count when I call #project.followers.count as the POST is not happening.
And thus, when I click follow button, the URL becomes all jumbled. See example:
#Goes from
domain.com/projects/21
#to
domain.com/projects/21?utf8=%E2%9C%93&authenticity_token=5EQmU0EkHB5yKDYakqL78piMWzZl0CfdpHFEqBeQiN4%3D&project_relationship%5Bprojectuser_id%5D=21&commit=Follow%22
**Update:
It seems to work now, but I'm not sure if I really changed anything but got rid of the follower_id index :unique => true through a migration change.
schema.rb
create_table "project_relationships", :force => true do |t|
t.integer "follower_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.integer "projectuser_id"
end
add_index "project_relationships", ["follower_id"], :name => "index_project_relationships_on_follower_id", :unique => true
add_index "project_relationships", ["projectuser_id"], :name => "index_project_relationships_on_projectuser_id"
routes.rb
resources :projects do
resources :comments
member do
get :following
end
end
resources :project_relationships, only: [:create, :destroy]
project_relationship.rb
class ProjectRelationship < ActiveRecord::Base
attr_accessible :projectuser_id
belongs_to :user, foreign_key: "follower_id"
belongs_to :project, foreign_key: "projectuser_id"
end
project.rb
has_many :project_relationships, foreign_key: "projectuser_id"
has_many :favorited_by, through: :project_relationships, source: :user
user.rb
has_many :project_relationships, foreign_key: "follower_id"
has_many :followed_projects, through: :project_relationships, source: :project
def following_project?(project)
project_relationships.find_by_follower_id(project.id)
end
def follow_project!(project)
project_relationships.create!(projectuser_id: project.id)
end
def project_unfollow!(project)
project_relationships.find_by_projectuser_id(project.id).destroy
end
project_relationships_controller.rb
class ProjectRelationshipsController < ApplicationController
def create
#project = Project.find(params[:project_relationship][:projectuser_id])
current_user.follow_project!(#project)
redirect_to #project
end
def destroy
#project = ProjectRelationship.find(params[:id]).followed_project
current_user.project_unfollow!(#project)
redirect_to #project
end
end
projects/show.html.erb
<%= render 'follow_form' if signed_in? %>
projects/_follow_form.html.erb
<% if current_user.following_project?(#project) %>
<%= render 'unfollow' %>
<% else %>
<%= render 'follow' %>
<% end %>
projects/_follow.html.erb
<%= form_for(current_user.project_relationships.build(projectuser_id: #project.id)) do |f| %>
<div><%= f.hidden_field :projectuser_id %></div>
<%= f.submit "Follow", class: "btn btn-large btn-primary" %>
<% end %>
projects/_unfollow.html.erb
<%= form_for(current_user.project_relationships.find_by_projectuser_id(#project),
html: { method: :delete }) do |f| %>
<%= f.submit "Unfollow", class: "btn btn-large" %>
<% end %>

First of all - if you run projectfollow!(project) and projectunfollow!(project) in your console (with a user, project etc) do they work properly?
For your forms try the following instead and see if it works:
<%= form_for(current_user.project_relationships.build, url: project_relationships_path(project_id: #project.id)) do |f| %>
Then in your project relationships controller:
class ProjectRelationshipsController < ApplicationController
def create
#project = Project.find(params[:project_id])
current_user.projectfollow!(#project)
redirect_to #project
end
end
So if your create URL is /project_relationships (via POST), the above should post to /project_relationships?project_id=5 and then the controller can find that project.
Also, try to rename your methods so they make sense:
def following_project?(project)
end
def follow_project!(project)
end
def unfollow_project!(project)
end
Now current_user.following_project?(project) makes a lot of sense!
Update
Ok, I think the following is the problem, in your create action you're getting the id from the params:
#project = Project.find(params[:project_relationship][:projectuser_id])
However in your form you're not setting the value of the hidden field:
<%= f.hidden_field :projectuser_id %>
Change it to the following and see if it works:
<%= f.hidden_field :projectuser_id, value: #project.id %> # or wherever the id is from

The problem was that my follow/unfollow form was embedded in another form which caused the error. Once taken out, worked!

Related

Issues with creating a "many-to-many" relation

I'm trying to do a simple task : I created a "magazine" scaffold, but I want it to have a specific relation : a user can have participate for the creation / redaction of a magazine, and it can take several users to create a magazine.
I checked the APIDock and did the following:
Specified the relation between the magazines and the users
model/magazine.rb
class Magazine < ApplicationRecord
mount_uploader :thumbnail, ThumbnailUploader
has_and_belongs_to_many :users
end
model/user.rb
class User < ApplicationRecord
has_and_belongs_to_many :magazines
# More code...
end
Created a migration to add a table to link both the models
class ManyToMany < ActiveRecord::Migration[5.0]
def change
create_table :magaziness_users, :id => false do |t|
t.integer :user_id
t.integer :magazine_id
end
add_index :magazines_users, [:magazine_id, :user_id]
end
end
Then I ran the migration
Added the list of all users ever recorded to the database to create a dropdown
<div class="field">
<%= f.label :users %>
<%= f.select :users, User.all_except(current_user).collect {|u| [u.username, u]}, {prompt: 'Add a creator?'}, { :multiple => true, :size => 3 } %>
</div>
But, when I'm saving a new magazine, the user doesn't get saved, and the "magazines_user remains empty.
edit 1
This is an auto-generated controller, since I use the scaffold command to create it. I didn't touch anything excepted the set_magazine function, where I added the Friendly_Id
class MagazinesController < ApplicationController
before_action :set_magazine, only: [:show, :edit, :update, :destroy]
def index
#magazines = magazine.all
end
def show
end
def new
#magazine = magazine.new
end
def edit
end
def create
#magazine = magazine.new(magazine_params)
if #magazine.save
redirect_to #magazine, notice: 'magazine was successfully created.'
else
render :new
end
end
def update
if #magazine.update(magazine_params)
redirect_to #magazine, notice: 'magazine was successfully updated.'
else
render :edit
end
end
def destroy
#magazine.destroy
redirect_to magazines_url, notice: 'magazine was successfully destroyed.'
end
private
def set_magazine
#magazine = magazine.friendly.find(params[:id])
end
def magazine_params
params.require(:magazine).permit(:titre, :description, :apercu, :users)
end
end
Did I forget any step?
so here is the answer with my code working:
I did two scaffolds:
rails generate scaffold user username:string email:uniq password:digest
rails generate scaffold magazine title:string description:text preview:string
Then added this to magazine migration:
create_table :magazines_users, id: false do |t|
t.belongs_to :magazine, index: true
t.belongs_to :user, index: true
end
In my form, I added:
<div class="field">
<%= f.label :users %>
<%= f.select :user_ids, User.all.collect { |u| [u.username, u.id] }, {include_blank: true}, {multiple: true} %>
</div>
And in my magazines controller I only modified magazine_params:
def magazine_params
params.require(:magazine).permit(:title, :description, :preview, :user_ids => [])
end
To see that it works, I added this in magazin show view:
<p>
<strong>Users:</strong>
<%= #magazine.users.map(&:username).join(" - ") %>
</p>
Of course I added "has_and_belongs_to_many" as you did in User and Magazine models.
And that's it :)
Tested with Rails 5 and it works just fine. :)
Also I strongly advice you to take a look at the simple_form gem. It has some great methods to handle associations (like has_and_belongs_to_many) easily, like this : <%= f.association :users, collection: User.all_except(current_user).order(:username) %>

No route matches {:action=>"show", :controller=>"items"} missing required keys: [:id]

Update: I've been trying to debug my files, so most of the files have changed recently
I am getting a strange error when trying to use a "new" action to my items_controller. Essentially, a wishlist has_many items and an item belongs_to wishlist. The error message is as follows:
Code
Here is my items_controller:
class ItemsController < ApplicationController
def show
#item = Item.find(params[:id])
end
def new
#item = Item.new
end
def create
#item = Item.new(item_params)
if #item.save
redirect_to "/wishlist", :notice => "Success!"
else
redirect_to "/wishlist", :notice => "Failure, try again later!"
end
end
def edit
#item = Item.find(params[:id])
end
def update
#item = Item.find(params[:id])
if #item.update_attributes(item_params)
redirect_to(:action => 'show', :id => #item.id)
else
render 'edit'
end
end
private
def item_params
params.require(:item).permit(:name,:size,:qty,:priority)
end
private
def create_params
params.require(:item).permit(:name,:size,:qty,:priority,:wishlist_id)
end
end
And my routes.rb:
Rails.application.routes.draw do
get '/wishlist' => 'wishlists#index', as: :wishlists
get '/wishlist/:id' => 'wishlists#show', as: :wishlist
get '/wishlist_items/new' => 'items#new'
get '/wishlist_items/:id' => 'items#show', as: :items
get '/wishlist_items/:id/edit' => 'items#edit', as: :edit_items
patch '/wishlist_items/:id' => 'items#update'
resources :items
And finally, my new.html.erb for the items model:
<h1>New Item</h1>
<div class="wishlist-new">
<% if true %>
<%= form_for(#item) do |f| %>
<%= f.text_field :name, :placeholder => "Name" %>
<%= f.text_field :size, :placeholder => "Specifications" %>
<%= f.text_field :qty, :placeholder => "Quantity" %>
<%= f.text_field :priority, :placeholder => "Priority" %>
<%= f.text_field :id, :placeholder => "Wishlist ID" %> # changing :id to :wishlist_id doesn't seem to do anything
<%= f.submit "Create Item", class: "btn-submit" %>
<% end %>
<% end %>
</div>
My migration files (so you know how my databases are structured:
# Migration file for items
class CreateItems < ActiveRecord::Migration
def change
drop_table :items
create_table :items do |t|
t.string :name
t.string :size
t.string :qty
t.integer :priority
t.references :wishlist
t.timestamps
end
end
end
# Migration File for Wishlists
class CreateWishlists < ActiveRecord::Migration
def change
drop_table :wishlists
create_table :wishlists do |t|
t.string :title
t.timestamps
end
end
end
Attempts to Debug
It seems like the routes.rb is sending requests to different methods in the items_controller because the error seems to say that /wishlist_items/new is accessing a show method even though its new method takes priority. To support this, the page loads properly when I comment out get '/wishlist_items/:id' => 'items#show', as: :items in the routes file. What happens is the page loads properly, and the Item is created properly (when I fill out the form) except that when I go into the console, it says that the Item created has a property of wishlist_id: nil even though I specified for it to be 1 in the form.
The method mentioned above has two problems: (1) it doesn't work entirely correctly, and (2) it becomes impossible to show a specific Item in the wishlist.
The error occurs before the inner section of the form_for is loaded, so the problem either is (a) a weird routing thing (as mentioned above) or (b) something weird happening to the #item variable.
Thanks in advance!

Dealing With Multiple Objects

I am new to Rails and currently trying to add a patient to an existing dentist appointment. I am having difficulty setting up my views and controllers properly. How can I properly accomplish this?
Note: With the way I have set things up, I can create an appointment and tie it to a dentist. Of course, the patient_id is missing.
Models:
class Dentist < ActiveRecord::Base
has_many :appointments
has_many :patients, :through => :appointments
end
class Appointment < ActiveRecord::Base
belongs_to :dentists
belongs_to :patients
end
class Patient < ActiveRecord::Base
has_many :appointments
has_many :dentists, :through => :appointments
end
Schema:
ActiveRecord::Schema.define(version: 20151107052115) do
create_table "appointments", force: :cascade do |t|
t.integer "dentist_id"
t.integer "patient_id"
t.datetime "appt_date"
end
create_table "dentists", force: :cascade do |t|
t.string "name"
end
create_table "patients", force: :cascade do |t|
t.string "name"
end
end
Routes:
Rails.application.routes.draw do
concern :commentable do
resources :appointments
end
resources :dentists, concerns: :commentable
resources :patients, concerns: :commentable
end
Dentists Controller:
class DentistsController < ApplicationController
def new
#dentist = Dentist.new
end
def create
#dentist = Dentist.new(dentist_params)
if #dentist.save
redirect_to dentists_path
else
render :new
end
end
...
end
Appointments Controller:
class AppointmentsController < ApplicationController
def new
#dentist = Dentist.find(params[:dentist_id])
#appointment = #dentist.appointments.new
end
def create
#dentist = Dentist.find(params[:dentist_id])
#appointment = #dentist.appointments.new(appt_params)
if Appointment.exists?(:appt_date => #appointment.appt_date)
render :new
else
#appointment.save
redirect_to dentist_path(#dentist)
end
end
...
end
Patients Controller:
TBD
Dentists View (Show):
<p><%= #dentist.name %> DDS</p>
<% if #dentist.appointments.any? %>
<% #dentist.appointments.each do |appt| %>
<ul>
<li><%= appt.appt_date %></li>
<p><%= link_to "Edit", edit_dentist_appointment_path(#dentist, appt) %> |
<%= link_to 'Delete', dentist_appointment_path(#dentist, appt), :method => :delete,
data: {:confirm => 'Are you sure you want to delete this record?'} %> |
<%= link_to 'Add Patient', new_patient_path %></p>
</ul>
<% end %>
<% else %>
<p>There are currently no appointments scheduled</p>
<% end %>
<p><%= link_to 'Delete Dentist', dentist_path(#dentist), :method => :delete,
data: {:confirm => 'Are you sure you want to delete this record?'} %></p>
<p><%= link_to 'Create an appointment', new_dentist_appointment_path(#dentist) %></p>
<p><%= link_to 'Return to list', root_path %></p>
I am new to Rails
Welcome!
You need to change your belongs_to references to be singular:
class Appointment < ActiveRecord::Base
belongs_to :dentist
belongs_to :patient
end
--
Because I can't see where you're trying to achieve this functionality, I'll show you what I'd do (using the appointment#edit action):
#app/controllers/appointments_controller.rb
class AppointmentsController < ApplicationController
def edit
#appointment = Appointment.find params[:id]
end
def update
#appointment = Appointment.find params[:id]
#appointment.save appointment_params
end
private
def appointment_params
params.require(:appointment).permit(:dentist_id, :patient_id, :appt_date)
end
end
#app/views/appointments/edit.html.erb
<%= form_for #appointment do |f| %>
<%= f.collection_select :patient_id, Patient.all, :id, :name %>
<%= f.submit %>
<% end %>
--
If you're trying to set the patient from your appointments#create method, you'll be best doing this:
#app/controllers/appointments_controller.rb
class AppointmentsController < ApplicationController
def new
#dentist = Dentist.find params[:id]
#appointment = #dentist.appointments.new
end
def create
#dentist = Dentist.find params[:id]
#appointment = #dentist.appointments.new appointment_params
end
private
def appointment_params
params.require(:appointment).permit(:dentist_id, :patient_id, :appt_date)
end
end
#app/views/appointments/new.html.erb
<%= form_for #appointment do |f| %>
<%= f.collection_select :patient_id, Patient.all, :id, :name %>
<%= f.submit %>
<% end %>
I think what you are asking, is can you create an appointment through both the Dentist model and the Patient model at the same time eg. #dentist.#patient.apointment.new
you cannot do that. Based on the relationships you have set up, you will either want to create the appt from the Dentist, like you have now, and pass in the patient ID as an attribute, or vice-versa. OR, create through your Appointment model eg. Appointment.new(dentist: #dentist, patient: #patient, ...)

Comments on multiple models

Within my rails app, I currently have comments setup to work with my posts model, which is functioning properly. How do I add comments to my books model?
Here is what I have so far:
Here is what I have in my schema for the comments:
create_table "comments", force: true do |t|
t.text "body"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "user_id"
t.integer "post_id"
t.integer "book_id"
end
In my user model:
class User < ActiveRecord::Base
has_many :comments
acts_as_voter
end
In my post model:
class Post < ActiveRecord::Base
has_many :comments
end
In my book model:
class Book < ActiveRecord::Base
has_many :comments
end
In my comment model:
class Comment < ActiveRecord::Base
belongs_to :post
belongs_to :book
belongs_to :user
acts_as_votable
end
In my comments controller:
class CommentsController < ApplicationController
def create
post.comments.create(new_comment_params) do |comment|
comment.user = current_user
end
respond_to do |format|
format.html {redirect_to post_path(post)}
end
end
def upvote
#post = Post.find(params[:post_id])
#comment = #post.comments.find(params[:id])
#comment.liked_by current_user
respond_to do |format|
format.html {redirect_to #post}
end
end
private
def new_comment_params
params.require(:comment).permit(:body)
end
def post
#post = Post.find(params[:post_id])
end
end
In my routes file:
resources :posts do
resources :comments do
member do
put "like", to: "comments#upvote"
end
end
end
In my view:
<% #post.comments.each do |comment| %>
<%= comment.body %>
<% if user_signed_in? && (current_user != comment.user) && !(current_user.voted_for? comment) %>
<%= link_to “up vote”, like_post_comment_path(#post, comment), method: :put %>
<%= comment.votes.size %>
<% else %>
<%= comment.votes.size %></a>
<% end %>
<% end %>
<br />
<%= form_for([#post, #post.comments.build]) do |f| %>
<p><%= f.text_area :body, :cols => "80", :rows => "10" %></p>
<p><%= f.submit “comment” %></p>
<% end %>
What do I add to my comments controller to get comments working on both posts and books? What do I add to my routes file?
Thanks in advance for any help.
You don't want to specify each type of object that can hold Comment objects. That creates a headache of if-elsif-else blocks all over the place. Instead, you want things to be Commentable, and they all will have .comments on them.
This is called a polymorphic association in Active Record. So you would have your models something like:
class Comment < ActiveRecord::Base
belongs_to :commentable, polymorphic: true
end
class Post < ActiveRecord::Base
has_many :comments, as: :commentable
end
class Book < ActiveRecord::Base
has_many :comments, as: :commentable
end
And modify your database accordingly, it's all in the linked article. Now when you build a Comment object for a form, it will have pre-populated a commentable_id and commentable_type, which you can toss in hidden fields. Now it doesn't matter what the Comment is associated with, you always treat it the same.
I'd leave User as a separate association, since it's not really the same idea.

Rails - Liking Comments Made on Posts - NoMethodError

For my application, I have Users, who can Create Project Postings. On each Project Posting, they can make comments that I have made a Blogupdate model. I want users to be able to like Blogupdates made on each Project page.
So, I created a Bloglike model. But when I try to render a LIKE/UNLIKE button, I get the following error:
NoMethodError in Projects#blogs
undefined method `bloglikes_path'
Extracted source (around line #11):
11: <%= form_for(current_user.bloglikes.build(blogupdate_id: blogupdate.id)) do |f| %>
Question: As a note, I have not built up the controller for the actual create/destroy function in my bloglikes controller; but looking at my attached code below, does somebody know how I can resolve this error so the like/unfollow button renders?
schema.rb
create_table "bloglikes", :force => true do |t|
t.integer "user_id"
t.integer "blogupdate_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
add_index "bloglikes", ["blogupdate_id"], :name => "index_bloglikes_on_blogupdate_id"
add_index "bloglikes", ["user_id", "blogupdate_id"], :name => "index_bloglikes_on_user_id_and_blogupdate_id", :unique => true
add_index "bloglikes", ["user_id"], :name => "index_bloglikes_on_user_id"
user.rb
has_many :bloglikes, foreign_key: "user_id"
has_many :liked_blogupdates, through: :bloglikes, source: :blogupdate
blogupdate.rb
has_many :bloglikes, foreign_key: "blogupdate_id"
has_many :liked_by, through: :bloglikes, source: :user
def liking_blogupdate?(blogupdate)
bloglikes.find_by_blogupdate_id(blogupdate.id)
end
def like_blogupdate!(blogupdate)
bloglikes.create!(blogupdate_id: blogupdate.id)
end
def blogupdate_unlike!(blogupdate)
bloglikes.find_by_blogupdate_id(blogupdate.id).destroy
end
bloglike.rb
class Bloglike < ActiveRecord::Base
attr_accessible :blogupdate_id
belongs_to :user, foreign_key: "user_id"
belongs_to :blogupdate, foreign_key: "blogupdate_id"
end
projects_controller.rb
def blogs
#project = Project.find(params[:id])
#blogupdates = #project.blogupdates.newest.page(params[:blogupdates_page]).per_page(5)
end
views/projects/blogs.html.erb
<%= render 'blogs' %>
views/projects/_blogs.html.erb
<%= render #blogupdates %>
views/blogupdates/_blogupdates.html.erb
<%= blogupdate.liked_by.count %>
<% if current_user.liking_blogupdate?(blogupdate) %>
<%= form_for(current_user.bloglikes.find_by_blogupdate_id(blogupdate),
html: { method: :delete }) do |f| %>
<%= f.submit "UNLIKE", class: "btn btn-medium" %>
<% end %>
<% else %>
<%= form_for(current_user.bloglikes.build(blogupdate_id: blogupdate.id)) do |f| %>
<div><%= f.hidden_field :blogupdate_id %></div>
<%= f.submit "LIKE", class: "btn btn-medium btn-primary" %>
<% end %>
<% end %>
<p><%= raw blogupdate.content %></p>
UPDATE: As noted below by #Dan, I forgot to update the routes.rb file. I added "resources :bloglikes" and it worked now.
You didn't post your routes.rb file but I'd wager that is where the problem is at. An undefined method related to routes (e.g. bloglikes_path) typically indicates you've not defined the routes.
Add resources :bloglikes to your project's routes.rb file and see if that resolves the issue.

Resources