has_many :through broke some code - ruby-on-rails

So i'm relatively new to RoR, and am having some issues in trying to get my code back up and working. So previously I had users, and wikis that users could create. I've set up so that users can subscribe and get premium status to make wikis private. Now I'm in the process of making it so that Premium users can add standard users as collaborators to the wiki. I've decided to got about associating them through has_many :through relationships.
The issue I'm running into so that some of my buttons have started making errors that I don't understand. The one I'm stuck on right now is when showing the page that has a create new wiki button on it.
This is the error I am getting when I added the has_many through: relationship
No route matches {:action=>"new", :controller=>"wikis", :format=>nil, :user_id=>nil} missing required keys: [:user_id]
Here are the models:
collaborator.rb
class Collaborator < ActiveRecord::Base
belongs_to :wiki
belongs_to :user
end
user.rb
class User < ActiveRecord::Base
...
has_many :collaborators
has_many :wikis, :through => :collaborators
end
wiki.rb
class Wiki < ActiveRecord::Base
belongs_to :user
has_many :collaborators
has_many :users, :through => :collaborators
end
The important bits of the wiki_controller.rb
def new
#user = User.find(params[:user_id])
#wiki = Wiki.new
authorize #wiki
end
def create
#user = current_user
#wiki = #user.wikis.create(wiki_params)
authorize #wiki
if #wiki.save
flash[:notice] = "Wiki was saved"
redirect_to #wiki
else
flash[:error] = "There was an error saving the Wiki. Please try again"
render :new
end
end
And finally the show.html.erb file the button is located in.
<div class="center-align">
<%= link_to "New Wiki", new_user_wiki_path(#user, #wiki), class: 'btn grey darken-1' %>
</div>
If I'm missing any files or relevant info please let me know. This may be a simple stupid answer but I'm stuck for the life of me.
Thanks in advance.
Edit:
Here is the requested added info, first up the show info in the users_controllers.rb
def show
#wikis = policy_scope(Wiki)
end
the corresponding policy scope I'm using in the user_policy.rb
class UserPolicy < ApplicationPolicy
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
wikis = []
all_wikis = scope.all
all_wikis.each do |wiki|
if wiki.user == user || wiki.users.include?(user)
wikis << wiki
end
end
end
wikis
end
end
and the route.rb file
Rails.application.routes.draw do
devise_for :users
resources :users, only: [:update, :show] do
resources :wikis, shallow: true
end
resources :wikis, only: [:index]
resources :charges, only: [:new, :create]
delete '/downgrade', to: 'charges#downgrade'
authenticated do
root to: "users#show", as: :authenticated
end
root to: 'welcome#index'
end
Hope it helps

I found out the problem. I set up the migrate file wrong when originally creating the collaboration model.
Thanks for all of your help.

Related

Couldn't find Course without an ID

When I try to access app/views/companies/courses/show.html.erb(which is redirected after app/views/companies/courses/new.html.erb), rails server says that it cannot find a course without an ID.
Also, when I run Course.all on rails console, the courses have the correct id, the correct description and the correct company id but no name attribute.
App/controllers/companies/courses_controller:
class Companies::CoursesController < ApplicationController
before_action :authenticate_company!
def new
#course = Course.new
end
def create
#course = current_company.courses.create(course_params)
if #course.save
redirect_to companycourse_path(:course => #course.id)
else
render 'new'
end
end
def show
#course = current_company.courses.find(params[:id])
end
def index
#courses = current_company.courses.all
end
private
def course_params
params.require(:course).permit(:title, :description)
end
end
App/models/course.rb:
class Course < ApplicationRecord
belongs_to :company
end
App/models/company.rb:
class Company < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :courses
end
Config/routes.rb:
Rails.application.routes.draw do
devise_for :companies
devise_for :users
root 'static_pages#home'
get 'users/:id' => 'users#show'
resources :users, only: [:show]
get 'companies/:id' => 'companies#show'
resources :companies, only: [:show] do
resources :courses, only: [:show,:new,:create,:index]
end
devise_scope :user do
get 'signup', to: 'users/registrations#new'
get 'login', to: 'users/sessions#new'
end
devise_scope :company do
get 'companysignup', to: 'companies/registrations#new'
get 'companylogin', to: 'companies/sessions#new'
get 'newcourse', to:'companies/courses#new'
post 'newcourse', to:'companies/courses#create'
get 'companycourse', to:'companies/courses#show'
get 'companycoursesindex', to:'companies/courses#index'
end
end
I had a similar problem building my app not long ago and my problem was coming from controllers.
In your routes.rb I would add resources :companies, :has_many => :courses.I also would add a validation to your course model in models/course.rb to make sure that when you save it to the database your object has a title: class Course < ApplicationRecord
belongs_to :company validates :title, presence: :true
end
Also try using this or something along lines in your courses_controller.rb instead of your current code:
def create
#company = find_company
#course = Course.new(course_params)
#course.company_id = current_company.id
if #course.save
your conditions here
else
your conditiona here
end
end
private
def find_company
params.each do |name, value|
if name == 'company_id'
return #company = Company.find(value.to_i)
end
end
nil
end
I hope it helps.

Rails 5 adding Likes to Articles and Comments

Rails 5, I am trying to add Likes from scratch without a gem dependency. I am so close to having it down but am being completely stumped by what's happening when Comments get involved.
Storing and saving article.likes worked perfectly. Then I got the comment.likes to work. Now, when I like a Comment, they individually store a new Like (great!), except now Article is not properly saving any likes, and even weirder: article.likes.count gives a TOTAL sum of all it's comments' likes.. I am sure this is an easy fix but I am just totally missing it and I've tried everything. I've gone down some deep rabbit holes for this.
I think the problem lies in routing, or how the models relate.
Articles have many Comments, and both of them have many Likes.
Here are the models starting with like.rb:
class Like < ApplicationRecord
belongs_to :user
belongs_to :article
belongs_to :comment
# Make sure that one user can only have one like per post or comment
validates :user_id, uniqueness: { scope: [:article_id, :comment_id] }
end
article.rb
class Article < ApplicationRecord
belongs_to :user
...
# destroy associated comments on article deletion
has_many :comments, dependent: :destroy
has_many :likes, dependent: :destroy
end
comment.rb
class Comment < ApplicationRecord
belongs_to :article
belongs_to :user
...
has_many :likes, dependent: :destroy
end
routes.rb
...
resources :articles, :path => 'blog' do
resources :likes, only: [:create, :destroy]
resources :comments do
resources :likes, only: [:create, :destroy]
end
end
the meaty likes_controller.rb. Mind the #{}, EVERYTHING checks out the way it should, so why does the comment.likes.create save correctly, but the article.likes.create does not?? Help, please.
class LikesController < ApplicationController
before_action :get_signed_in_user
before_action :comment_or_article
def create
if #comment_like
like_resource(comment)
else
like_resource(article)
end
end
def destroy
if #comment_like
comment.likes.where(user: current_user).destroy_all
else
article.likes.where(user: current_user).destroy_all
end
flash[:success] = "Unliked! :("
redirect_to article_redirect(article)
end
private
def like_resource(obj)
if obj.likes.where(user: current_user.id).present?
flash[:error] = "You've already upvoted this! + #{like_params} + #{#comment_like} + #{obj}"
if #comment_like
if obj.likes.create(user: current_user, article: #article)
flash[:success] = "Upvoted Comment! + #{like_params} + #{#comment_like} + #{obj}"
else
flash[:error] = "An error prevented you from upvoting."
end
elsif obj.likes.create(user: current_user)
flash[:success] = "Upvoted Blog! + #{like_params} + #{#comment_like} + #{obj}"
else
flash[:error] = "An error prevented you from upvoting."
end
redirect_to article_path(article)
end
def get_signed_in_user
unless user_signed_in?
flash[:error] = "Sign in to upvote!"
redirect_back(fallback_location: articles_path)
end
end
# decide what we are liking
def comment_or_article
if comment
#comment_like = true
else
#comment_like = false
end
end
def article
#article = Article.find(params[:article_id])
end
def comment
unless params[:comment_id].nil?
#comment = article.comments.find(params[:comment_id])
end
end
def like_params
params.permit( :article_id, :comment_id).merge(user_id: current_user.id)
end
end
Finally the like buttons in my articles/show.html.erb:
<%= link_to "Like", article_likes_path(#article), method: :post %>
<%= "#{#article.likes.count}" %>
... # inside loop to show comments:
<%= link_to "Like", article_comment_likes_path(#article, comment), method: :post %>
<%= "#{comment.likes.count}" %>
TLDR:
Comment likes work fine, save fine, count individually fine.
Article likes do not save, but article.likes.count === article.comments.likes.count. Why?
I want article.likes to be completely unique, like it's own comments are.
Thank you in advance.
EDIT: took out belongs_to :comments in like.rb and refactored like_controller.rb main function to
private
def like_resource(obj)
if obj.likes.where(user: current_user.id).present?
flash[:error] = "You've already upvoted this!"
elsif obj.likes.create(user: current_user, article: #article)
flash[:success] = "Upvoted!"
else
flash[:error] = "An error prevented you from upvoting."
end
redirect_to article_path(article)
end
Always supplying the #article helps when liking a comment. An Article like would not need a comment_id to save.
Sorry for the long post, hopes this helps someone.
I just figured it out, actually.
class Like < ApplicationRecord
belongs_to :user
belongs_to :article
# belongs_to :comment
end
Commenting the above out, it allows the #article "like" to save without a comment being referenced. Article likes are saved properly. However, article.likes.count still increments whenever an article.comment is liked. This means article.likes is always >= article.comments.likes; which is completely fine.
I just changed the #article.likes to:
<%= "#{#article.likes.where(comment_id: nil).count}" %>
Filtering out all the strictly article.likes. The comment.likes still work perfectly.

How to set a like button in rails

Hello I have an exercise app where a user should be able to Like some products.
I could find a way to display the product he liked, but I really can't figure how to create and make work the like button.
I am not using any gem, I wan't to understand how to do it from Scratch.
Here are my models:
class User < ApplicationRecord
has_many :likes
has_many :liked_products, through: :likes, source: :product
end
class Product < ApplicationRecord
has_many :likes
end
class Like < ApplicationRecord
belongs_to :user
belongs_to :product
end
In my view product show where I want the like button:
<h1><%= #product.name %></h1>
<%= link_to "Like", product_likes_path(#product), method: :put, remote: true %>
my routes:
Rails.application.routes.draw do
root to: 'visitors#index'
devise_for :users
resources :users
resources :products do
resource :likes
end
end
That's my products controller, I think things must come in here but I don't know HOW!
class ProductsController < ApplicationController
before_action :find_product, only: :show
def index
#products = Product.all
end
def show
##product.like => gives an error 404
end
private
def find_product
#product = Product.find(params[:id])
end
end
I had created a likes controller but it seems it is not useful.... So... I gave up there...
class LikesController < ApplicationController
def new
#like = Like.new(like_params)
end
def create
#like = Like.new(like_params)
end
private
def like_params
params.require(:likes).permit(:user_id, :product_id)
end
end
I would really enjoy some light on this please :)
Finally found out how to set the controller
class LikesController < ApplicationController
def create
#user = current_user.id
#product = params[:product_id]
likes = {user_id: #user, product_id: #product}
#like = Like.new(likes)
#like.save!
if #like.save
redirect_to user_path(#user)
else
redirect_to product_path
end
end
end
the buttton
<%= link_to "Like", product_likes_path(#product), method: :post %>
routes
Rails.application.routes.draw do
root to: 'products#index'
devise_for :users
resources :users
resources :users do
resources :products do
resources :likes
end
end
end
You could try something along these lines:
Routes:
Rails.application.routes.draw do
root to: 'visitors#index'
devise_for :users
resources :users do
resources :products do
resources :likes
end
end
resources :products do
resource :likes
end
end
Which will give you something like:
... other routes ...
user_product_likes GET /users/:user_id/products/:product_id/likes(.:format) likes#index
POST /users/:user_id/products/:product_id/likes(.:format) likes#create
new_user_product_like GET /users/:user_id/products/:product_id/likes/new(.:format) likes#new
edit_user_product_like GET /users/:user_id/products/:product_id/likes/:id/edit(.:format) likes#edit
user_product_like GET /users/:user_id/products/:product_id/likes/:id(.:format) likes#show
PATCH /users/:user_id/products/:product_id/likes/:id(.:format) likes#update
PUT /users/:user_id/products/:product_id/likes/:id(.:format) likes#update
DELETE /users/:user_id/products/:product_id/likes/:id(.:format) likes#destroy
... other routes ...
Then:
<%= link_to "Like", user_product_likes_path(#user, #product), method: :post, remote: true %>
And in your LikesController:
class LikesController < ApplicationController
def new
#like = Like.new(like_params)
end
def create
#like = Like.new(like_params)
if #like.save
... do something happy
else
... do something sad
end
end
private
def like_params
params.require(:likes).permit(:user_id, :product_id)
end
end
Untested, so buyer beware. You might need to fiddle with your like_params and other stuff.

Destroy a has_many :through is not working with link_to helper

I'm working on a simple project management tool in Rails 4, and the part which gives me headaches has three main models: Projects, Users and Memberships.
Users can have many projects and projects can have many users. I implemented a has_many through membership relationship between Projects and Users in the following manner:
Project:
class Project < ActiveRecord::Base
has_many :memberships
has_many :users, through: :memberships
end
User:
class User < ActiveRecord::Base
has_many :memberships
has_many :projects, through: :memberships
end
Membership:
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :project
end
So far I haven’t seen it on Stack Overflow, but I created a seperate controller for the Memberships, with a :create and :destroy only.
So far, :create seems to work just fine.
The main problem lies in the destroy function of the Memberships.
The destroy function I implemented is:
def destroy
Membership.find(:id).destroy
redirect_to current_project || request.referer
end
rake routes says that the membership path exists, but the following tries give me:
I tried to use a link_to helper to delete the memberships:
<%= link_to "delete", membership, method: :delete %>
EDIT: error: undefined local variable or method `membership'
<%= link_to "delete", #membership, method: :delete %>
EDIT: error: Sorry something went wrong --> goes to /memberships
<%= link_to "delete, membership_path(#membership), method: :delete %>
EDIT: error No route matches {:action=>"destroy", :controller=>"memberships", :id=>nil} missing required keys: [:id]
which all give errors.
EDIT: on request also the projects_controller #show function
def show
#user = current_user
#project = current_user.projects.find(params[:id])
#members = #project.users
#projects = #user.projects
#membership = #project.memberships.build if logged_in?
#memberships = #project.memberships
end
How can I make sure a membership is removed with the associated id in #project.membership_ids? Should I include certain extra parameters?
resources :memberships, only: [:create, :destroy]
Update
Don't know how I missed this earlier, in your destroy action you have Membership.find(:id).destroy. It should utilize params and be more along the lines of this:
# MembershipsController
def destroy
#membership = Membership.find(params[:id])
if #membership.destroy
redirect_to current_project || request.referer
else
#
end
end
Your ProjectsController's Show action isn't defining #membership as a Membership object.
def show
#user = current_user
#project = #user.projects.find(params[:id])
#membership = Membership.find_by user: #user, project: #project
# build is used for nested attributes, not sure why you'd have this in a show action...
# #project.memberships.build if logged_in?
# the following are redundant.
#projects = #user.projects
#members = #project.users
#memberships = #project.memberships
end
In the view: <%= link_to "delete", #membership, method: :delete %>
If you want all memberships of a project or user to be destroyed upon parent deletion, make the following changes to your User & Project models:
class Project < ActiveRecord::Base
has_many :memberships, dependent: :destroy
has_many :users, through: :memberships
end
class User < ActiveRecord::Base
has_many :memberships, dependent: :destroy
has_many :projects, through: :memberships
end
The reason the your code did not work is because you did not specify the path to link_to. I do not believe you need to have #membership so I edited that out.
<%= link_to "delete", membership_path(#membership), method: :delete %>

Redirecting to edit_profile after devise sign up

I'm trying to get my rails app to redirect to the edit_profile_path after sign up through devise, but am having serious problems trying to work out how. I've spent the past four hours on here trying to find a solution, but can't seem to find the problem.
My current routes.rb:
resources :users, :only => [:index] do
member do
get :favourite_users, :favourited_users
end
resources :posts
resources :profiles, only: [:show, :edit]
end
My RegistrationsController.rb
class RegistrationsController < Devise::RegistrationsController
protected
def after_sign_up_path_for(resource)
edit_profile_path(resource)
end
end
My profile/edit.html.erb:
class User < ActiveRecord::Base
before_create :build_profile #creates profile at user registration
has_one :profile, dependent: :destroy
accepts_nested_attributes_for :profile
end
My profile.rb
class Profile < ActiveRecord::Base
belongs_to :user
def name
first_name + " " + last_name[0]
end
end
My ProfilesController.rb
class ProfilesController < ApplicationController
def new
#profile = Profile.new
end
def show
end
def profile
end
def edit
#profile = current_user.profile
end
end
I've tried as many solutions as I can but all I seem to get is:
undefined method `profile_path' for #<#<Class:0xb50b8618>:0xb50abe54>
or if I remove the (resources) from the edit_profile_path(resource) in the RegistrationsController:
No route matches {:action=>"edit", :controller=>"profiles"} missing required keys: [:id]
Any help would be greatly appreciated!
Have you tried running the following from console:
rake routes
This will output all of the routes your app knows about. I'm hoping I read your routes file right (hard to look at without the formatting), but with it being a nested route, I think you actually want to use
edit_user_profile_path(resource)

Resources