Rails Has_Many :Through confusion - ruby-on-rails

I'm working on creating a basic survey app as I'm learning rails. I've setup a has_many through relationship between the surveys as the questions (as questions may be used in multiple surveys). I've been struggling with adding a question to a survey though. Any idea what I need to do to create a new surveytization when creating my new question (and thus adding the question to the survey)? I'm able to do it in the console but am struggling with translating that to the controllers/views/params - if you know of any good documentation about those I'd love to check them out to (but thus far haven't found much).
It seems to error out when I try to assign my #survey variable using the :survey_id in the params I'm sending to the Question controller.
I really appreciate your help!
Question.rb:
class Question < ActiveRecord::Base
has_many :answers, dependent: :delete_all
validates :title, presence: true
has_many :surveytizations
has_many :surveys, :through => :surveytizations
end
Survey.rb
class Survey < ActiveRecord::Base
has_many :surveytizations
has_many :questions, :through => :surveytizations
end
Surveytization.rb:
class Surveytization < ActiveRecord::Base
has_many :surveys
has_many :questions
validates :survey_id, presence: true
validates :question_id, presence:true
end
SurveyController.rb:
class SurveysController < ApplicationController
before_action :set_survey, only: [:show, :edit, :update, :destroy]
before_action :set_question
# GET /surveys
# GET /surveys.json
def index
#surveys = Survey.all
end
# GET /surveys/1
# GET /surveys/1.json
def show
end
# GET /surveys/new
def new
#survey = Survey.new
end
# GET /surveys/1/edit
def edit
end
# POST /surveys
# POST /surveys.json
def create
#survey = Survey.new(survey_params)
respond_to do |format|
if #survey.save
format.html { redirect_to #survey, notice: 'Survey was successfully created.' }
format.json { render action: 'show', status: :created, location: #survey }
else
format.html { render action: 'new' }
format.json { render json: #survey.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /surveys/1
# PATCH/PUT /surveys/1.json
def update
respond_to do |format|
if #survey.update(survey_params)
format.html { redirect_to #survey, notice: 'Survey was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #survey.errors, status: :unprocessable_entity }
end
end
end
# DELETE /surveys/1
# DELETE /surveys/1.json
def destroy
#survey.destroy
respond_to do |format|
format.html { redirect_to surveys_url }
format.json { head :no_content }
end
end
def add_question(question)
surveytizations.create!(question_id: question.id)
end
def remove_question(question)
surveytizations.find_by(question_id: question.id).destroy
end
def find_question(question)
#question = surveytizations.find_by(question_id: question.id)
end
private
# Use callbacks to share common setup or constraints between actions.
def set_survey
#survey = Survey.find(params[:id])
end
def set_question
#question = Question.new
end
# Never trust parameters from the scary internet, only allow the white list through.
def survey_params
params.require(:survey).permit(:title)
end
end
Survey show.html.erb:
<p id="notice"><%= notice %></p>
<p>
<strong>Title:</strong>
<%= #survey.title %>
</p>
<%= link_to 'Edit', edit_survey_path(#survey) %> |
<%= link_to 'Back', surveys_path %>
<%= link_to "Add Question", new_question_path(:survey_id => #survey.id)%>
QuestionController:
class QuestionsController < ApplicationController
before_action :set_question, only: [:show, :edit, :update, :destroy]
# GET /questions
# GET /questions.json
def index
#questions = Question.all
end
# GET /questions/1
# GET /questions/1.json
def show
#answers = #question.answers
end
# GET /questions/new
def new
#question = Question.new
#survey = Survey.find(:survey_id)
end
# GET /questions/1/edit
def edit
end
# POST /questions
# POST /questions.json
def create
#question = Question.new(question_params)
respond_to do |format|
if #question.save
format.html { redirect_to #question, notice: 'Question was successfully created.' }
format.json { render action: 'show', status: :created, location: #question }
else
format.html { render action: 'new' }
format.json { render json: #question.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /questions/1
# PATCH/PUT /questions/1.json
def update
respond_to do |format|
if #question.update(question_params)
format.html { redirect_to #question, notice: 'Question was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #question.errors, status: :unprocessable_entity }
end
end
end
# DELETE /questions/1
# DELETE /questions/1.json
def destroy
#question.destroy
respond_to do |format|
format.html { redirect_to questions_url }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_question
#question = Question.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def question_params
params.require(:question).permit(:title, :single_response, :surveytization)
end
end

One problem is your join relation should have belongs_to instead of has_many, to get the has_many through working:
class Surveytization < ActiveRecord::Base
belongs_to :survey
belongs_to :question
validates :survey_id, presence: true
validates :question_id, presence:true
end
Notice the :survey and :question are singular name in the belongs_to
To Add a question to a survey you can
# create new question or find existing question and store it in #question
#question
# get the survey into #survey
#survey
#survey.questions << #question
This will magically create the surveytization as well. Now that question will belong to that survey.
You Don't even have to call #survey.save! after.

Related

SQLite3::SQLException: no such column

I'm trying to build an app where people can specify what posts need to be read before this one and here I'm trying to show them.
<%= #post.before.each do |before| %>
<li><%= before.title %></li>
<% end %>
This gets me this error:
SQLite3::SQLException: no such column: connections.post_id: SELECT "posts".* FROM "posts" INNER JOIN "connections" ON "posts"."id" = "connections"."after_id" WHERE "connections"."post_id" = ?
There isn't supposed to be a post_id column in connections, there is supposed to be an before_id column in it.
My models are:
post.rb:
class Post < ApplicationRecord
has_many :connections
has_many :before, through: :connections, source: :after, foreign_key: "before_id"
has_many :after, through: :connections, source: :before, foreign_key: "after_id"
end
connection.rb
class Connection < ApplicationRecord
belongs_to :before, class_name: "Post", foreign_key: "before_id"
belongs_to :after, class_name: "Post", foreign_key: "after_id"
end
The controller code for posts - automatically generated with scaffolding:
class PostsController < ApplicationController
before_action :set_post, only: [:show, :edit, :update, :destroy]
# GET /posts
# GET /posts.json
def index
#posts = Post.all
end
# GET /posts/1
# GET /posts/1.json
def show
end
# GET /posts/new
def new
#post = Post.new
end
# GET /posts/1/edit
def edit
end
# POST /posts
# POST /posts.json
def create
#post = Post.new(post_params)
respond_to do |format|
if #post.save
format.html { redirect_to #post, notice: 'Post was successfully created.' }
format.json { render :show, status: :created, location: #post }
else
format.html { render :new }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /posts/1
# PATCH/PUT /posts/1.json
def update
respond_to do |format|
if #post.update(post_params)
format.html { redirect_to #post, notice: 'Post was successfully updated.' }
format.json { render :show, status: :ok, location: #post }
else
format.html { render :edit }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
# DELETE /posts/1
# DELETE /posts/1.json
def destroy
#post.destroy
respond_to do |format|
format.html { redirect_to posts_url, notice: 'Post was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_post
#post = Post.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def post_params
params.require(:post).permit(:title, :text)
end
end
has_many :connections in your Post model is telling Rails that you have a post_id on the connections table. If this is not the case, I recommend deleting the association or defining a foreign_key for the association.
I would recode your model as follows:
class Post < ApplicationRecord
has_many :before, class_name: 'Connection', foreign_key: "before_id"
has_many :after, class_name: 'Connection', foreign_key: "after_id"
end
NOTE: You have source: :before and source: :after set opposite their respective association names and foreign_key names. I suspect that was not intentional. However, if it was intentional, you may want to swap foreign_keys between your two associations (i.e put the before_id on the after association and vis versa).

Ruby on Rails ActiveRecord::AssociationTypeMismatch with associations

I'm trying to create a very basic test application to allow users to add recipes to their 'favorites'.
I've set up user, recipe and favorite models and I am basically following the guide as posted in the top comment below:
Implement "Add to favorites" in Rails 3 & 4
However, I get a "ActiveRecord::AssociationTypeMismatch in RecipesController#favorite" error.
Here are my models:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :recipes
# Favorite recipes of user
has_many :favorite_recipes # just the 'relationships'
has_many :favorites, through: :favorite_recipes, source: :recipe # the actual recipes a user favorites
end
class Recipe < ActiveRecord::Base
belongs_to :user
# Favorited by users
has_many :favorite_recipes # just the 'relationships'
has_many :favorited_by, through: :favorite_recipes, source: :user # the actual users favoriting a recipe
end
class FavoriteRecipe < ActiveRecord::Base
belongs_to :recipe
belongs_to :user
end
And here is the controller:
class RecipesController < ApplicationController
before_action :set_recipe, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!
# GET /recipes
# GET /recipes.json
def favorite
type = params[:type]
if type == "favorite"
current_user.favorites << #recipe
redirect_to :back, notice: 'You favorited #{#recipe.name}'
elsif type == "unfavorite"
current_user.favorites.delete(#recipe)
redirect_to :back, notice: 'Unfavorited #{#recipe.name}'
else
# Type missing, nothing happens
redirect_to :back, notice: 'Nothing happened.'
end
end
def index
#recipes = Recipe.all
end
# GET /recipes/1
# GET /recipes/1.json
def show
end
# GET /recipes/new
def new
#recipe = Recipe.new
end
# GET /recipes/1/edit
def edit
end
# POST /recipes
# POST /recipes.json
def create
#recipe = Recipe.new(recipe_params)
respond_to do |format|
if #recipe.save
format.html { redirect_to #recipe, notice: 'Recipe was successfully created.' }
format.json { render :show, status: :created, location: #recipe }
else
format.html { render :new }
format.json { render json: #recipe.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /recipes/1
# PATCH/PUT /recipes/1.json
def update
respond_to do |format|
if #recipe.update(recipe_params)
format.html { redirect_to #recipe, notice: 'Recipe was successfully updated.' }
format.json { render :show, status: :ok, location: #recipe }
else
format.html { render :edit }
format.json { render json: #recipe.errors, status: :unprocessable_entity }
end
end
end
# DELETE /recipes/1
# DELETE /recipes/1.json
def destroy
#recipe.destroy
respond_to do |format|
format.html { redirect_to recipes_url, notice: 'Recipe was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_recipe
#recipe = Recipe.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def recipe_params
params.require(:recipe).permit(:user_id, :title)
end
end
The code i am using in the view to enable favourite adding:
<% if current_user %>
<%= link_to "favorite", favorite_recipe_path(#recipe, type: "favorite"), method: :put %>
<%= link_to "unfavorite", favorite_recipe_path(#recipe, type: "unfavorite"), method: :put %>
<% end %>
The full error i receive :
ActiveRecord::AssociationTypeMismatch in RecipesController#favorite
Recipe(#70327608659260) expected, got NilClass(#17178580)
Extracted source (around line #11):
09 type = params[:type]
10 if type == "favorite"
11 current_user.favorites << #recipe
redirect_to :back, notice: 'You favorited #{#recipe.name}'
elsif type == "unfavorite"
Any help would be greatly appreciated.
Thank you very much
Your #recipe is nil... you need to set the #recipe for the favorite action too:
class RecipesController < ApplicationController
before_action :set_recipe, only: [:show, :edit, :update, :destroy, :favorite]
...

ActiveRecord::RecordNotFound - before_action set_article

I'm attempting to create a Rails blog application and am having some issues implementing friendly urls. (Not the via the gem, I'm manually implementing them.) I am able to access the article's show page, however when I attempt to like/unlike the article or post a comment to the article, I'm getting this error: ActiveRecord::RecordNotFound. Here is the code snippet that it flags:
Extracted source (around line #19)
def set_article
#article = Article.find_by!(slug: params[:id])
end
end
Here are my three controllers where this is implemented:
class ArticlesController < ApplicationController
before_action :require_signin, except: [:index, :show]
before_action :require_admin, except: [:index, :show]
before_action :set_article, only: [:show, :edit, :update, :destroy]
# GET /articles
# GET /articles.json
def index
#articles = Article.all
end
# GET /articles/1
# GET /articles/1.json
def show
#likers = #article.likers
if current_user
#current_like = current_user.likes.find_by(article_id: #article.id)
end
#tags = #article.tags
end
# GET /articles/new
def new
#article = Article.new
end
# GET /articles/1/edit
def edit
end
# POST /articles
# POST /articles.json
def create
#article = Article.new(article_params)
respond_to do |format|
if #article.save
format.html { redirect_to #article, notice: 'Article was successfully created.' }
format.json { render :show, status: :created, location: #article }
else
format.html { render :new }
format.json { render json: #article.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /articles/1
# PATCH/PUT /articles/1.json
def update
respond_to do |format|
if #article.update(article_params)
format.html { redirect_to #article, notice: 'Article was successfully updated.' }
format.json { render :show, status: :ok, location: #article }
else
format.html { render :edit }
format.json { render json: #article.errors, status: :unprocessable_entity }
end
end
end
# DELETE /articles/1
# DELETE /articles/1.json
def destroy
#article.destroy
respond_to do |format|
format.html { redirect_to articles_url, notice: 'Article was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_article
#article = Article.find_by!(slug: params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def article_params
params.require(:article).permit(:slug, :title, :content, tag_ids: [])
end
end
class LikesController < ApplicationController
before_action :require_signin
before_action :set_article
def create
#article.likers << current_user
redirect_to #article, notice: "Thanks for liking!"
end
def destroy
like = current_user.likes.find(params[:id])
like.destroy
redirect_to #article, notice: "Sorry you unliked it!"
end
private
def set_article
#article = Article.find_by!(slug: params[:id])
end
end
class CommentsController < ApplicationController
before_action :set_article
before_action :set_comment, only: [:edit, :update, :destroy]
# GET /comments
# GET /comments.json
def index
#comments = #article.comments
end
# GET /comments/new
def new
#comment = #article.comments.new
end
# GET /comments/1/edit
def edit
end
# POST /comments
# POST /comments.json
def create
#comment = #article.comments.new(comment_params)
respond_to do |format|
if #comment.save
format.html { redirect_to #article, notice: 'Comment was successfully created.' }
format.json { render :show, status: :created, location: #article }
else
format.html { render :new }
format.json { render json: #comment.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /comments/1
# PATCH/PUT /comments/1.json
def update
respond_to do |format|
if #comment.update(comment_params)
format.html { redirect_to article_path(#article), notice: 'Comment was successfully updated.' }
format.json { render :show, status: :ok, location: article_comments_path(#article) }
else
format.html { render :edit }
format.json { render json: #article.comments.errors, status: :unprocessable_entity }
end
end
end
# DELETE /comments/1
# DELETE /comments/1.json
def destroy
#article.comments.destroy
respond_to do |format|
format.html { redirect_to comments_url, notice: 'Comment was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_article
#article = Article.find_by!(slug: params[:id])
end
def set_comment
#comment = Comment.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def comment_params
params.require(:comment).permit(:post)
end
end
Here is the Article Model
class Article < ActiveRecord::Base
before_validation :generate_slug
has_many :comments, dependent: :destroy
has_many :likes, dependent: :destroy
has_many :likers, through: :likes, source: :user
has_many :characterizations, dependent: :destroy
has_many :tags, through: :characterizations
belongs_to :user
validates :title, presence: true, uniqueness: true
validates :content, presence: true
validates :slug, uniqueness: true
def to_param
slug
end
def generate_slug
self.slug ||= title.parameterize if title
end
end
I'm not sure why it's having issues. Any help would be appreciated!
It seems you don't have article with slug value params[:id] in you database, that's why it throws error. You are using find_by! and it throws error if no record found.
In your LikesController
...
private
def set_article
#article = Article.find_by!(slug: params[:article_id])
end
...
# You need to use params[:article_id] not params[:id], because in your route for like it is set as article_id
# If same set up for comments then just change params[:id] -> params[:article_id]

posts_controller.rb:8: syntax error, unexpected tIDENTIFIER, expecting keyword_end has_many :comments dependant: :destroy

Currently working on the Blog app in Ruby on Rails and encountered this error. This occurs whenever I attempt to view localhost:3000/posts/ via my browser.
Here's my posts_controller.rb file:
class PostsController < ApplicationController
before_action :set_post, only: [:show, :edit, :update, :destroy]
# GET /posts
# GET /posts.json
def index
#posts = Post.all
has_many :comments dependant: :destroy
end
# GET /posts/1
# GET /posts/1.json
def show
end
# GET /posts/new
def new
#post = Post.new
end
# GET /posts/1/edit
def edit
end
# POST /posts
# POST /posts.json
def create
#post = Post.new(post_params)
respond_to do |format|
if #post.save
format.html { redirect_to #post, notice: 'Post was successfully created.' }
format.json { render action: 'show', status: :created, location: #post }
else
format.html { render action: 'new' }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /posts/1
# PATCH/PUT /posts/1.json
def update
respond_to do |format|
if #post.update(post_params)
format.html { redirect_to #post, notice: 'Post was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
# DELETE /posts/1
# DELETE /posts/1.json
def destroy
#post.destroy
respond_to do |format|
format.html { redirect_to posts_url }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_post
#post = Post.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def post_params
params.require(:post).permit(:title, :body)
end
end
Any ideas?
It's only since I entered line 8 that I've had this problem.
has_many :comments dependant: :destroy
You missed , below :
has_many :comments, dependent: :destroy
Read has_many also to see the correct examples.
has_many :comments, dependent: :destroy
Goes in the Post model, not the controller

Rails form params not being saved

I'm very new to Ruby on Rails and I'm struggling with my scaffolded controller.
I made a nested resource where my comments are placed in posts.
class Post < ActiveRecord::Base
validates :name, :presence => true
validates :title, :presence => true, :length => { :minimum => 5 }
has_many :comments
end
class Comment < ActiveRecord::Base
validates :commenter, :presence => true
validates :body, :presence => true
belongs_to :post
end
A simplified version of the controller is
class CommentsController < ApplicationController
before_action :set_comment, only: [:show, :edit, :update, :destroy]
# omited new, index, show...
# POST /comments
# POST /comments.json
def create
post = Post.find(params[:post_id])
#comment = post.comments.create(params[:comment].permit(:name, :title, :context))
respond_to do |format|
if #comment.save
format.html { redirect_to([#comment.post, #comment], notice: 'Comment was successfully created.') }
format.json { render action: 'show', status: :created, location: #comment }
else
format.html { render action: 'new' }
format.json { render json: #comment.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /comments/1
# PATCH/PUT /comments/1.json
def update
post = Post.find(params[:post_id])
#comment = post.comments.find(params[:comment])
respond_to do |format|
if #comment.update(comment_params)
format.html { redirect_to #comment, notice: 'Comment was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #comment.errors, status: :unprocessable_entity }
end
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_comment
#comment = Comment.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def comment_params
params.require(:comment).permit(:commenter, :body, :post)
end
end
When i filled in the form, i get this exception:
2 errors prohibited this comment from being saved:
Commenter can't be blank
Body can't be blank
I tried this guide but i think it isn't 100% compatible with Rails 4.
You are reading from params attributes for a Post (:name, :title, :context), but you need to read Comments attributes (:commenter, :body)
replace this:
#comment = post.comments.create(params[:comment].permit(:name, :title, :context))
with
#comment = post.comments.create(params[:comment].permit(:commenter, :body))
or, much better, with:
#comment = post.comments.create(comment_params)
It appears that you are explicitly permitting different set of attributes in your create action.
You should update your create action to use comment_params like you've done in update action. The reason being your Comment is definitely expecting Commenter and Body and not :name, :title, :context which you've permitted in your create action.
Update controller's create action as:
# POST /comments
# POST /comments.json
def create
...
#comment = post.comments.create(comment_params)
...
end

Resources