I am currently working on a blog app. The blog has users, stories, and favourites. 1 user can have many stories and many favourites, and one story can have many favourites (1 from each user) too.
When I try to save a new favourite on the front end (the create route), I receive the following error:
undefined method `favourites' for #<Class:0x00007f850e24e290>
Extracted source (around line #11):
9 def create
10 #story = Story.find(params[:story_id])
11 #favourite = Story.favourites.new
12 #favourite.user = #current_user
13 #favourite.save
14 redirect_to story_path(#story)
I feel like this should be defined, and is almost the same way that I am using create with other controllers. Based on what I've found so far, I suspect that it is because the favourites in Story.favourites.new is nested within the Story, but I haven't found a way to sort it out yet. I'm quite new to Rails, so apologies if the answer is obvious. Any guidance is appreciated! Thanks for your help!
Below are some of the pertinent files.
favourites_controller.rb
class FavouritesController < ApplicationController
before_action :logged_in, except: [:index, :show]
def show
end
def create
#story = Story.find(params[:story_id])
#favourite = Story.favourites.new
#favourite.user = #current_user
#favourite.save
redirect_to story_path(#story)
end
end
stories_controller.rb
class StoriesController < ApplicationController
before_action :logged_in, except: [:index, :show]
# Stories list page (also homepage)
def index
# Topic filtering
#topic = params[:topic]
#stories = if #topic.present?
Story.where(topic: #topic)
else
Story.all
end
end
def new
#story = Story.new
end
def create
#story = Story.new(form_params)
#story.user = #current_user
if #story.save
redirect_to root_path
else
render 'new'
end
end
def show
#story = Story.find(params[:id])
end
def destroy
#story = Story.find(params[:id])
#story.destroy if #story.user == #current_user
redirect_to root_path
end
def edit
#story = Story.find(params[:id])
redirect_to root_path if #story.user != #current_user
end
def update
#story = Story.find(params[:id])
if #story.user == #current_user
if #story.update(form_params)
redirect_to story_path(#story)
else
render 'edit'
end
else
redirect_to root_path
end
end
def form_params
params.require(:story).permit(:title, :topic, :body)
end
end
models/favourite.rb
class Favourite < ApplicationRecord
belongs_to :story
belongs_to :user
validates :story, uniqueness: { scope: :user }
end
models/story.rb
class Story < ApplicationRecord
has_many :comments
has_many :favourites
belongs_to :user
validates :title, presence: true
validates :body, presence: true, length: { minimum: 10 }
def to_param
id.to_s + '-' + title.parameterize
end
end
config/routes.rb
Rails.application.routes.draw do
resources :stories do
resources :comments
resource :favourite
end
resources :users
resource :session
root 'stories#index'
end
the issue is in this line:
11 #favourite = Story.favourites.new
it should be:
11 #favourite = #story.favourites.new
because the class Story itself doesn't have the favourites method but its instances do.
Related
I have 2 models that are linked through a joint table:
class Dailyreport < ApplicationRecord
max_paginates_per 9
belongs_to :owner
has_many :dailyreport_issues
has_many :issues, through: :dailyreport_issues
accepts_nested_attributes_for :issues, allow_destroy: true
end
class Issue < ApplicationRecord
belongs_to :project
belongs_to :owner
has_many :dailyreport_issues
has_many :dailyreports, through: :dailyreport_issues
max_paginates_per 10
before_create { |issue| issue.jiraid = issue.jiraid.upcase }
validates :jiraid, uniqueness: true
validates :jiraid, :project, :owner, :time_forecast, :time_real, presence: true
validates :jiraid, format: { with: /\b[a-zA-Z]{2,6}-[1-9]\d{0,3}\b/, message: 'must follow this format ABCXYZ-9999' }
validates :time_real, numericality: { only_float: true }
validates :time_forecast, numericality: { only_float: true }
end
class DailyreportIssue < ApplicationRecord
belongs_to :dailyreport
belongs_to :issue
end
I use nested forms 'cocoon gem' to generate issues inside the create form of the dailyreport.
I successfully implemented that with these 2 controllers:
class DailyreportsController < ApplicationController
helper DailyreportsHelper
before_action :define_dailyreport, only: [:edit, :show, :update, :destroy]
def index
#dailyreports = Dailyreport.all.order(created_at: :desc).page params[:page]
end
def new
#dailyreport = Dailyreport.new
#dailyreport.issues.build
#issues = Issue.all.order(created_at: :desc)
end
def edit
end
def show
end
def owner_dailyreport
#owner_dailyreport = current_user.owner.dailyreports
end
def create
#dailyreport = Dailyreport.new(dailyreport_params)
#dailyreport.issues.each do |cr_issue|
call_jira_api("https://agenceinspire.atlassian.net/rest/api/3/issue/#{cr_issue.jiraid}")
if #response_output_issues.key?('errors')
flash.alert = "Please check if #{cr_issue.jiraid} exists and is available on JIRA"
no_api_reponse
else
issue_details_from_jira(cr_issue)
issue_time_real_from_jira(cr_issue)
end
if #dailyreport.save!
redirect_to #dailyreport, notice: 'Dailyreport was successfully created.'
else
render :new
end
end
end
def update
if #dailyreport.update(dailyreport_params)
redirect_to #dailyreport, notice: 'Dailyreport was successfully updated.'
else
render :edit
end
end
def destroy
if current_user.admin? || current_user.email == #dailyreport.owner.email
#dailyreport.destroy
else
admin_only_access
end
previous_page
end
private
def dailyreport_params
params.require(:dailyreport).permit(
:comment,
:owner_id,
issues_attributes: [
:jiraid,
:project_id,
:owner_id,
:time_forecast,
:time_real,
:departement,
:retour_test,
:status,
:_destroy
]
)
end
def define_dailyreport
#dailyreport = Dailyreport.find(params[:id])
end
end
class IssuesController < ApplicationController
require 'net/http'
require 'uri'
before_action :define_issue, only: [:show, :edit, :update, :destroy]
before_action :admin_only_access, only: [:destroy, :edit, :update]
def index
#issues = Issue.all.order(created_at: :desc).page params[:page]
end
def search
if params[:search].blank?
redirect_to issues_path and return
else
#parameter = params[:search].downcase
#results = Issue.all.where('lower(jiraid) LIKE :search', search: "%#{#parameter}%").page params[:page]
end
end
def new
#issue = Issue.new
end
def show
call_jira_api("https://agenceinspire.atlassian.net/rest/api/3/issue/#{#issue.jiraid}")
if #response_output_issues.key?('errors')
flash.alert = "Please check if #{#issue.jiraid} exists and is available on JIRA"
no_api_reponse
else
issue_details_from_jira(#issue)
yes_api_response
end
end
def create
#issue = Issue.new(issue_params)
# Check if issue exists on JIRA
unless call_jira_api("https://agenceinspire.atlassian.net/rest/api/3/issue/#{#issue.jiraid}")
flash.alert = "Please check if #{#issue.jiraid} exists and is available on JIRA"
end
# Get issue details from JIRA
issue_details_from_jira(#issue)
issue_time_real_from_jira(#issue)
# Save the issue
if #issue.save
flash.notice = "Issue #{#issue.jiraid} created"
redirect_to issues_path and return
else
flash.alert = "There was a problem saving #{#issue.jiraid}, check if all the fields are filled on the JIRA issue"
end
end
def edit
end
def update
if #issue.update(issue_params)
redirect_to issues_path
else
render :edit, status: :unprocessable_entity
end
end
def destroy
if current_user.admin?
#issue.destroy
else
admin_only_access
end
previous_page
end
private
def issue_params
params.require(:issue).permit(
:jiraid,
:project_id,
:owner_id,
:time_forecast,
:time_real,
:departement,
:retour_test,
:status
)
end
def define_issue
#issue = Issue.find(params[:id])
#issue_owner = Owner.find_by(params[:current_user])
end
end
My routesRails.application.routes.draw do
get '/search', to: 'issues#search'
get '/home/jira', to: 'home#jira'
get '/dailyreports/owner_dailyreport/:id', to: 'dailyreports#owner_dailyreport', :as => 'my_crs'
resources :projects
resources :issues
resources :departements
resources :owners
resources :dailyreports
# Devise routes
devise_scope :user do
get 'users', to: 'devise/sessions#new'
end
devise_for :users
authenticated :user do
root to: 'home#index', as: :authenticated_root
end
root to: redirect('/users/sign_in')
end
I am trying to implement an update or create process:
Check if the JIRAID exists in my DB
If it doesn't just get the data and save the dailyreport.
If it does, I call the API and get its updated details then update it and save the dailyreport.
And here I found some issues with the code I tried.
First when I update the issue then try to save the dailyreport, it throws the validation error (Jiraid exists) because the dailyreport.save is trying to update the issue again.
I also tried this:
def create
#dailyreport = Dailyreport.new(dailyreport_params)
issues_attributes = params[:dailyreport][:issues_attributes]
p("///////////////////////////////////ISSUES_ATTRIBUTES#{issues_attributes}")
issues_attributes.each do |_, issue_attributes|
p("~~~~~~~~~~~~~~~~~~~~~~ISSUE_ATTRIBUTE#{issue_attributes}")
# Call the JIRA API and check for errors
call_jira_api("https://agenceinspire.atlassian.net/rest/api/3/issue/#{issue_attributes["jiraid"]}")
if #response_output_issues.key?('errors')
flash.alert = "Please check if #{issue_attributes["jiraid"]} exists and is available on JIRA"
return
end
# Update the issue attributes with details from the JIRA API
issue_details_from_jira(issue_attributes)
issue_time_real_from_jira(issue_attributes)
p("~~~~~~~~~~~~~~~~~~~~~~JIRA ID IN THE DB: #{issue.jiraid}")
# Check if the issue already exists in the database
issue = Issue.find_by(jiraid: issue_attributes["jiraid"])
if issue
issue_details_from_jira(issue)
issue_time_real_from_jira(issue)
# Update the existing issue
issue.update(
time_forecast: issue.time_forecast,
time_real: issue.time_real,
status: issue.status
)
else
# Build and save a new issue if it doesn't exist
#dailyreport.issues.build(issue_attributes)
end
end
I know I have an issue here:
issue_details_from_jira(issue_attributes)
issue_time_real_from_jira(issue_attributes)
I am going to have to create an object to pass to my methods. But i don't know how.
I couldn't update the issue from the dailyreport controller too, so I tried passing the update method (+ the id) inside the strong params of the dailyreport. That resulted in a ForbiddenAttributes error.
I actually need a lead of how to approach this, not a specific solution. I think that my approach is wrong.
thank you in advance
I have two models tea and a review. I have nested a route to create reviews for a specific tea but when I submit the form I am getting a UrlGeneration Eror based on missing a required key. Below is the controller action and route and picture of the error. I am trying to have a user create a new review from /teas/1/reviews/new it is a nested form the issue being it does not save the create.
Review Model
class Review < ApplicationRecord
belongs_to :user
belongs_to :tea
validates :title, presence: true
validates :rating, numericality: {only_integer: true, greater_than_or_equal_to: 0, less_than: 11}
validates :tea, uniqueness: {scope: :user, message: "has already been reviewed by you" }
scope :order_by_rating, ->{left_joins(:reviews).group(:id).order('avg(rating) desc')}
end
class ReviewsController < ApplicationController
before_action :set_review, only:[:create, :show, :edit, :update, :destroy]
def new
if #tea = Tea.find_by_id(params[:tea_id])
#review = #tea.reviews.build
else
#review = Review.new
end
end
def create
#review = current_user.reviews.build(review_params)
if #review.valid?
#review.save
redirect_to new_review_path(#review)
else
render :new
end
end
def show
#review = Review.find_by_id(params[:id])
end
def index
if #tea = Tea.find_by_id(params[:tea_id])
#reviews = #tea.reviews
else
#reviews = Review.all
end
end
def edit
end
def update
#review.update(review_params)
redirect_to tea_reviews_path(current_user.id)
end
def destroy
#review.destroy
flash[:delete_review] = "Review Deleted!"
redirect_to reviews_path(#review)
end
private
def review_params
params.require(:review).permit(:tea_id, :content, :rating,:title)
end
def set_review
#review = Review.find_by_id(params[:id])
redirect_to reviews_path if !#review
end
end
Route
resources :reviews
resources :teas do
resources :reviews, only: [:new, :index]
end
In line 62 of Reviews_controller
redirect_to review_path if !#review
In this line you are trying to redirect to a review show page if review doesn't exist,
hence when review doesn't exist it redirects to show path without an id, that's why you are getting an error.
Think carefully where do you want to redirect, if a review does not exist.
Per your updated question,
Remove :create from the before_action in the first line of the controller,
also, in your create action change the redirect to
redirect_to reviews_path
after the #review.save.
I have some polymorphic relationships set up and are working well for the primary purpose. That is for a User to be able to Comment on both Articles and Coffeeshops.
However I'm struggling with being able to display the users list of comments on their profile page. In the future I also want the user to be able to 'favourite' and 'want to go to' different coffeeshops which I would also want to show up on their profile page. I'm hoping once I get the logic for display current comments, the rest will be a breeze ;)
So what I have:
Models
class User < ApplicationRecord
has_many :comments
end
class Comment < ApplicationRecord
belongs_to :user
belongs_to :commentable, polymorphic: true
end
class Coffeeshop < ApplicationRecord
has_many :comments, as: :commentable
end
class Article < ApplicationRecord
has_many :comments, as: :commentable
end
Comment Controller
class CommentsController < ApplicationController
before_action :load_commentable
before_action :authenticate_user!
before_action :comment_auth, only: [:edit, :update, :destroy]
def index
#comments = #commentable.comments
end
def new
#comment = #commentable.comments.new
end
def create
#comment = #commentable.comments.new(allowed_params)
#comment.user_id=current_user.id if current_user
if #comment.save
redirect_to #commentable, notice: "Comment created."
else
render :new
end
end
def update
#comment = Comment.find(params[:id])
if #comment.update(comment_params)
redirect_to #commentable
else
render 'edit'
end
end
def destroy
#comment = Comment.find(params[:id])
#commentable = #comment.commentable
if #comment.destroy
flash[:success] = "Comment Destroyed!"
redirect_to :back
end
end
private
def allowed_params
params.require(:comment).permit(:name, :body)
end
def load_commentable
resource, id = request.path.split('/')[1,2]
#commentable = resource.singularize.classify.constantize.find(id)
end
def comment_params
params.require(:comment).permit(:body).merge(user_id: current_user.id)
end
Profile Controller
class ProfileController < ApplicationController
before_action :authenticate_user!
def index
end
def show
#user = User.find.current_user(params[:id])
#comments = #commentable.comments
end
In views/profile/show.html.erb. I was trying to do:
<h3>Your Latest Comment</h3>
<%=#comment.user.body%>
But this clearly isn't right as I get Couldn't find User without an ID. From ProfileController#show
update
If I change ProfileController to
before_action :authenticate_user!
def index
#user = User.find.current_user(params[:user_id])
end
def show
#comments = #commentable.comments
end
I get an error for undefined comments.
ok first return this to show moving it to index is not solving a problem the index is not called so write show like this.
def show
#user = current_user #you get instance of a user that is logged in
#comments = #user.comments
end
I do not know if you have user_id in your comment migration but if you do not have you must write
class User < ApplicationRecord
has_many :comments, as: :commentable
end
view
<h3>Your Latest Comment</h3>
<%=#comments.try(&:last).try(&:body)%>
I want to the category I create in relation with the post I want to create. I don't want to add a gem or anything else I think we can do it with has_manyand belongs_to
I create two tables Posts and Category and I want to choose a category in a collection and this is written in the post new#view I want to create and on the post show#view and post index#view.
Models for posts is :
class Post < ActiveRecord::Base
belongs_to :user
belongs_to :category
TAGS = ["Design", "Mode", "Tendance", "Life-Style", "Tradition", "Gastronomie", "Insolite", "Technologie"]
validates :tag, inclusion: { in: Post::TAGS, allow_nil: false }
mount_uploader :cover, ImageUploader
end
and categories is foreign keys for posts here is the model
class Category < ActiveRecord::Base
has_many :posts
NAMES = ["JAPON", "CHINE", "INDE"]
validates :name, inclusion: { in: Category::NAMES, allow_nil: false }
end
Posts Controllers are here
class PostsController < ApplicationController
before_filter :authenticate_user!, except: [:index, :show]
before_action :find_post, only: [:show, :edit, :update, :destroy]
def index
#posts = Post.all
end
def show
# #alert_message = "Vous lisez #{#post.title}"
end
def new
# if current_user and current_user.admin?
#post = Post.new
# else
# redirect_to posts_path
# end
end
def create
# if current_user and current_user.admin?
#post = current_user.posts.new(post_params)
##post = current_user.posts.new(post_params)
if #post.save
redirect_to #post
else
render :new
end
# else
# render 'shared/404.html.erb'
# end
end
def edit
end
def update
if #post.update(post_params)
redirect_to #post
else
render :edit
end
end
def destroy
#post.destroy
redirect_to :back
end
private
def find_post
#post = Post.find(params[:id])
end
# def set_category
# #post_category = Category.find(params[:category_id])
# end
def post_params
params.require(:post).permit(:title, :subtitle, :introduction, :body, :cover, :tag, :category_id)
end
end
and categories_controller are here
class CategoriesController < ApplicationController
before_action :set_category, only: [:show, :new, :create, :destroy]
def show
#category = Category.find(params[:id])
end
def index
#categories = Category.all
end
def create
#category = Category.new(category_params)
if #category.save
redirect_to #post
else
render :new
end
end
def new
#category = Category.new
end
def edit
end
def update
end
def destroy
#category = Category.find(params[:id])
#category.destroy
redirect_to post_path(#post)
end
private
# def set_post
# #post = Post.find(params[:post_id])
# end
# def set_category
# #category = Category.find(params[:category_id])
# end
def set_category
if params[:id].present?
#category = Category.find(params[:id])
else
#category = Category.new
end
end
# def find_category
# #category = Category.find(params[:id])
# end
def category_params
params.require(:category).permit(:name, :description)
end
end
Please could you show the right way to add a category I choose in collection and I show in post new#view show#view index#view.
Thank you for your help.
If a category can belong to more than one post, I would recommend using a has_may_through relationship for your data model.
class Post
has_many :post_categories
has_many :categories, through: post_categories
accepts_nested_attributes_for :categories
end
class Category
has_many :post_categories
has_many :posts, through: post_categories
end
class PostCategory
belongs_to :posts
belongs_to :categories
end
You will need to create a migration to add the 'through' table, PostCategory, which will consist of a post_id and a category_id.
In the controller
def new
#post = Post.new
#post.categories.build
end
def post_params
params.require(:post).permit(:title, :subtitle, :introduction, :body, :cover, :tag, category_ids: [])
end
In your form, you can use fields_for to build the form for categories.
If you set this all up, rails will handle the creation of the category when the post is created. Then you will be able to call category.posts to get all the posts with that category and you can call post.categories to get all the categories assigned to the post.
Assigning a Post to a Category can be accomplished in the create method of your PostsController. You are already passing the category_id to the controller via the params.
def create
#post = current_user.posts.new(post_params)
#category = Category.find(params[:category_id])
if #post.save && (#category.posts << #post)
redirect_to #post
else
render :new
end
end
When you are trying to show a post in a view, you should be able to access that category directly.
<%= post.category.name %>
If you allow some posts to be created without a category, you can simply not show anything, or show a "No Category" message.
# Don't show anything if the post doesn't belong to a category
<%= post.category.name if post.category.present? %>
# Show a "No Categories" message (this uses the ternary operator)
<%= post.category.present? ? post.category.name : "No Category" %>
I am trying to follow this video railscast #364 but I am having a lot of trouble with my nested routes. When I use this code:
<%= link_to "up", vote_movie_review_path(#movie, #reviews, type: "up"), method: "post" %>
I get this error when I select up vote:
ActiveRecord::RecordNotFound in ReviewsController#vote
Couldn't find Review with 'id'=# <Review::ActiveRecord_Relation:0x007f0358c1e550>
This is my route:
vote_movie_review POST /movies/:movie_id/reviews/:id/vote(.:format) genre_linkers#vote
I created another model that was not nested using this code:
<%= link_to "up", vote_movie_path(movie, type: "up"), method: "post" %>
and that one worked. So I am thinking it has to be something wrong with my path or how I am calling the objects. I have spent almost all day working on this, I really need help.
review_controller.rb
class ReviewsController < ApplicationController
before_action :set_review, only: [:show, :edit, :update, :destroy]
before_action :set_movie
before_action :authenticate_user!
respond_to :html
def index
#reviews = Review.all
respond_with(#reviews)
end
def show
end
def vote
value = params[:type] == "up" ? 1 : -1
#review = Review.find(params[:id])
#review.add_evaluation(:vote, value, current_user)
redirect_to :back, notice: "thanks for the vote"
end
def new
#review = Review.new
respond_with(#review)
end
def edit
end
def create
#review = Review.new(review_params)
#review.user_id = current_user.id
#review.movie_id = #movie.id
if #review.save
redirect_to #movie
else
render 'new'
end
end
def update
#review.update(review_params)
respond_with(#review)
end
def destroy
#review.destroy
respond_with(#review)
end
private
def set_review
#review = Review.find(params[:id])
end
def set_movie
#movie = Movie.find(params[:movie_id])
end
def review_params
params.require(:review).permit(:genre, :description, :vote)
end
end
routes.rb
Rails.application.routes.draw do
devise_for :users
resources :movies do
resources :reviews do
member { post :vote }
end
end
root 'movies#index'
end
and the model
review.rb
class Review < ActiveRecord::Base
belongs_to :user
belongs_to :movie
has_reputation :votes, source: :user, aggregated_by: :sum
end
The culprit is this part of your link_to: vote_movie_review_path(#movie, #reviews, type: "up"). #reviews is an ActiveRecord::Relation and not a Review record, hence no record with an ID can be found.