I have a many_to_many association between Categories and Articles, using has_and_belongs_to_many in a Rails 4 app:
Here are the corresponding migration and classes:
class CategoriesArticles < ActiveRecord::Migration
def change
create_table :categories_articles, id: false do |t|
t.belongs_to :category, index: true
t.belongs_to :article, index: true
end
add_index :categories_articles, [:category_id, :article_id]
end
end
class Category < ActiveRecord::Base
has_and_belongs_to_many :articles
end
class Article < ActiveRecord::Base
has_and_belongs_to_many :categories
end
When a user creates a new article, I simply want to give him or her the option to select categories that he/she wants to associate with the new article. I want the user to be able to select these categories with checkboxes.
Here's the ArticlesController:
class ArticlesController < ApplicationController
before_action :set_article, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!, only: [:new, :create, :edit, :destroy, :update]
before_action :verify_own_article, only: [:destroy]
respond_to :html
def index
if user_signed_in?
#articles = current_user.article_feed
# TODO: if there are too few articles on a user's feed, we want to display more articles
else
#articles = Article.all
end
#articles = #articles.page(params[:page] || 1).per(12)
respond_with(#articles)
end
def show
respond_with(#article)
end
def new
#categories = Category.all
#article = Article.new
respond_with(#article)
end
def edit
if current_user.articles.find_by_id(params[:id]).nil?
flash[:notice] = "You do not have permission to edit this article."
redirect_to #article
end
end
def create
# Creates article object with current_user_id, initial_comment, and URL
#article = current_user.articles.build(article_params)
# Uses Pismo (gem) to grab title, content, photo of URL
#article.populate_url_fields
if #article.save
flash[:success] = "Article created!"
# Might need to change the location of this redirect
redirect_to root_url
else
flash[:notice] = "Invalid article."
redirect_to new_article_path
end
end
def update
#article.update(article_params)
flash[:notice] = "Article successfully updated."
respond_with(#article)
end
def destroy
if #article
#article.destroy
flash[:notice] = "Article successfully destroyed."
else
flash[:notice] = "You do not have permission to delete this article."
end
# TODO: change this to another redirect location
redirect_to root_path
end
private
def set_article
#article = Article.find(params[:id])
end
def article_params
params.require(:article).permit(:url, :title, :datetime, :content, :photo, :initial_comment)
end
# Ensure that a signed in user can only delete articles that they have posted
def verify_own_article
#article = current_user.articles.find_by_id(params[:id])
end
end
Here's the article new.html.erb view:
<h1>New article</h1>
<%= render 'form' %>
<%= link_to 'Back', articles_path %>
... and the form partial:
<%= form_for(#article) do |f| %>
<% if #article.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#article.errors.count, "error") %> prohibited this article from being saved:</h2>
<ul>
<% #article.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :url %><br>
<%= f.text_field :url %>
</div>
<div class="field">
<%= f.label :initial_comment %><br>
<%= f.text_field :initial_comment %>
</div>
<% #categories.each do |t| %>
<div class="field">
<%= f.label t.name %>
<%= f.check_box "categories[#{t.id}]" %>
<br />
</div>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
However, this is erroring for me, specifically the lines:
<% #categories.each do |t| %>
<div class="field">
<%= f.label t.name %>
<%= f.check_box "categories[#{t.id}]" %>
<br />
</div>
<% end %>
Specifically, it's telling me:
undefined method 'categories[1]' for #<Article:0x007f401193d520>. How do I fix this? Thanks.
I note your join table is named
create_table :categories_articles, id: false do |t|
The name needs to be in alphabetical order.
create_table :articles_categories, id: false do |t|
My pick is that Rails cannot find your join table. This means that the form when it calls #article.categories cannot find what it needs. The same will happen if you call #category.articles (ie: being able to find the join table is not related to the arbitrary order of the objects).
See my answer to this question
Related
I have tried multiple methods to show error message from views, but it is not appearing.
<%= form_for #article do |f| %>
<% if #article.errors.any? %>
<div id="error_explanation">
<h2>
<%= pluralize(#article.errors.count, "error") %> prohibited
this article from being saved:
</h2>
<ul>
<% #article.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<p>
<%= f.label :title %><br>
<%= f.text_field :title %>
</p>
<p>
<%= f.label :text %><br>
<%= f.text_area :text %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
_form.html.erb
<center>
<h1>New article</h1>
<%= render 'form' %>
<%= link_to 'Back', articles_path %>
</center>
new.html.erb
class Article < ActiveRecord::Base
#attr_accessible :title, :text
has_many :comments, dependent: :destroy
validates :title, presence: true
end
article.rb
Rails.application.routes.draw do
root "articles#index"
resources :articles do
resources :comments
end
end
routes.rb
class ArticlesController < ApplicationController
http_basic_authenticate_with name: "deba", password: "12345", except: [:index, :show]
def index
#articles = Article.all
end
def new
#article = Article.new
end
def show
#article = Article.find(params[:id])
end
def create
#article = Article.new(article_params)
if #article.save
redirect_to #article
else
render 'new' ##article.errors, status: :unprocessable_entity
end
end
def edit
#article = Article.find(params[:id])
end
def update
#article = Article.find(params[:id])
if #article.update(article_params)
redirect_to #article
else
render 'edit'
end
end
def destroy
#article = Article.find(params[:id])
#article.destroy
redirect_to articles_path
end
private
def article_params
params.require(:article).permit(:title, :text)
end
end
articles_controller.rb
Kindly help me. I tried it, validation is working as I can check that if there is no title it redirect me to new page without saving it but not appearing the error messages.
The problem is with your redirecting - it causes the browser to initiate a new request and then you lose all the data (including the instance variables where the error is stored). Instead of redirecting you should simply render the "new" partial when article is invalid.
You haven't posted your controller code but this should give you a direction
if #article.save
# whatever you wanna do if its valid
else
render "new" # render the view without redirecting
end
try to edit your controller:
class ArticlesController < ApplicationController
before_action :initialize_article, only: [:new, :create]
...
def new
end
def create
if #article.update(article_params)
redirect_to #article
else
render :new
end
end
....
private
def initialize_article
#article ||= Article.new
end
end
I am building a blog application in which I am trying to have two links in that two links I am sending the post_id to my update controller but it is giving me error .And I want to collect that post_id in my update controller and want to check the "status"(Status is basically the column name in my posts table and by default my status column has a status of pending ) .So when admin click on approve the status of column should change to approve and when admin click on decline the status of column should change to decline also user post should be deleted .Admin can access all the users posts whereas user can access his only post
posts_controller
class PostsController < ApplicationController
before_action :authenticate_user!
def index
#posts = Post.user_post(current_user).order('created_at DESC').paginate(:page => params[:page], :per_page => 5)
end
def new
#post = Post.new
end
def show
#post=find_params
end
def create
#post = Post.new(post_params)
#post.user = current_user
if #post.save
Post.upload(params[:post][:files],#post.id)
redirect_to #post
else
render 'new'
end
end
def edit
#post = find_params
puts "cccccccccc#{params[:commit]}"
Post.up(#post.id,params[:commit])
end
def update
#post = find_params
if #post.update(post_params)
redirect_to #post
else
render 'edit'
end
end
def destroy
#post = find_params
#post.destroy
redirect_to posts_path
end
private
def post_params
params.require(:post).permit(:title, :body)
end
def find_params
Post.find(params[:id])
end
end
posts/_form.html.erb
<%= form_for #post,html: { multipart: true } do |f| %>
<% if #post.errors.any? %>
<div id="errors">
<h2><%= pluralize(#post.errors.count, "error") %> prevented this post from saving:</h2>
<ul>
<% #post.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<%= f.label :title %><br>
<%= f.text_field :title %><br>
<br>
<%= f.label :body %><br>
<%= f.text_field :body %><br>
<br>
<!-- if you want to upload multiple files at a time -->
<%= f.label :files %><br>
<%= f.file_field :files,:multiple => true %><br>
<br>
<%= f.submit %>
<br>
<% end %>
posts/edit.html.erb
<div id="page_wrapper">
<h1>Edit Post</h1>
<%= render 'form' %>
<br>
</div>
migration
class CreatePosts < ActiveRecord::Migration
def change
create_table :posts do |t|
t.string :title
t.text :body
t.integer :user_id
t.string :status
t.timestamps
end
end
end
posts/show.html.erb
<div id="post_content">
<h1 class="title"><%= #post.title %></h1>
<p class="date">
Submitted <%= time_ago_in_words(#post.created_at) %> Ago
<% if user_signed_in? %>
<%= link_to 'Edit', edit_post_path(#post) %> |
<%= link_to 'Delete', post_path(#post), method: :delete, data: { confirm: 'Are you sure?' } %>
<%= link_to "approve",[:edit,#post] %>
<%= link_to "decline",[:edit,#post] %>
<% end %>
</p>
<p class="body"><%= #post.body %></p>
<div id="comments">
<h2><%= #post.comments.count %> Comments</h2>
<%= render #post.comments %>
<h3>Add a comment:</h3>
<%= render "comments/form" %>
</div>
</div>
post.rb
class Post < ActiveRecord::Base
has_many :documents
has_many :comments, dependent: :destroy
belongs_to :user
validates :title, presence: true, length: {in: 5..15}
validates :body, presence: true,length: {in: 5..200}
def self.up(id,params)
puts "aaaaaaaa#{id}"
puts "bbbbbbbbbbbbb#{params}"
end
def self.user_post(id)
role = User.find_role(id)
if role == 'admin'
Post.all
elsif role == 'user'
Post.where(user_id: id)
elsif role == 'developer'
Post.where(user_id: id)
end
end
def self.icon(extension)
case extension
when 'pdf'
EXTENSION[1]['pdf']
when 'png' || 'jpg' || 'jpeg'
EXTENSION[0]['png']
when 'doc' || 'odt'
EXTENSION[2]['doc']
end
end
####limit to upload files not more than ######
def self.upload(files,post_id)
files.each do |file|
#file_extension=file.content_type.split('/')[1]
doc = Document.new(document: file,post_id: post_id )
#save is a method which will save the content in the database
doc.save!
end
end
end
routes.rb
Rails.application.routes.draw do
root "posts#index"
devise_for :users
resources :posts do
resources :comments
end
resources :uploads
end
There are a bunch of problems with your code.. I'll not try to address them all but the specific problem you are facing..
first you need to generate path for both approve and decline action..
resources :posts do
patch '/approve' => 'posts#approve', as: :approve #posts_approve_path(post) is the route helper for this
patch '/decline' => 'posts#decline', as: :decline #posts_decline_path(post) is the route helper for this
resources :comments
end
this will generate the required routes.
now inside your view wherever you want to approve and decline.
<%= link_to "approve",posts_approve_path(#post.id), method: :patch %>
<%= link_to "decline",posts_decline_path(#post.id), method: :patch %>
In your controller you need to modify find_params as the generated routes will pass :posts_id as the :id param.
def find_params
id = params[:id] || params[:posts_id]
Post.find(id)
end
Now in your controller add 2 new methods to implement the functionality.
def approve
#post = find_params
#post.update_attribute(:status, 'approved') if #post.present?
redirect_to post_path(#post), notice: 'Post approved'
end
def decline
#post = find_params
#post.destroy if #post.present?
redirect_to post_path(#post), notice: 'Post deleted'
end
I have a many_to_many association between Articles and Categories, using has_and_belongs_to_many in a Rails 4 app:
Here are the corresponding migration and classes:
class CategoriesArticles < ActiveRecord::Migration
def change
create_table :articles_categories, id: false do |t|
t.belongs_to :category, index: true
t.belongs_to :article, index: true
end
add_index :articles_categories, [:category_id, :article_id]
end
end
class Category < ActiveRecord::Base
has_and_belongs_to_many :articles
end
class Article < ActiveRecord::Base
has_and_belongs_to_many :categories
end
When a user creates a new article, I simply want to give him or her the option to select categories that he/she wants to associate with the new article. I want the user to be able to select these categories with checkboxes.
Here's the ArticlesController:
class ArticlesController < ApplicationController
before_action :set_article, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!, only: [:new, :create, :edit, :destroy, :update]
before_action :verify_own_article, only: [:destroy]
respond_to :html
...
def new
#categories = Category.all
#article = Article.new
respond_with(#article)
end
def create
# Creates article object with current_user_id, initial_comment, and URL
#article = current_user.articles.build(article_params)
# Uses Pismo (gem) to grab title, content, photo of URL
#article.populate_url_fields
if #article.save
flash[:success] = "Article created!"
# Might need to change the location of this redirect
redirect_to root_url
else
flash[:notice] = "Invalid article."
redirect_to new_article_path
end
end
def update
#article.update(article_params)
flash[:notice] = "Article successfully updated."
respond_with(#article)
end
private
def set_article
#article = Article.find(params[:id])
end
def article_params
params.require(:article).permit(:url, :title, :datetime, :content, :photo, :initial_comment)
end
# Ensure that a signed in user can only delete articles that they have posted
def verify_own_article
#article = current_user.articles.find_by_id(params[:id])
end
end
Here's the article new.html.erb view:
<h1>New article</h1>
<%= render 'form' %>
<%= link_to 'Back', articles_path %>
... and the form partial:
<%= form_for(#article) do |f| %>
<% if #article.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#article.errors.count, "error") %> prohibited this article from being saved:</h2>
<ul>
<% #article.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :url %><br>
<%= f.text_field :url %>
</div>
<div class="field">
<%= f.label :initial_comment %><br>
<%= f.text_field :initial_comment %>
</div>
<% #categories.each do |t| %>
<div class="field">
<%= f.label t.name %>
<%= f.check_box "categories[#{t.id}]" %>
<br />
</div>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
However, this is erroring for me, specifically the lines:
<% #categories.each do |t| %>
<div class="field">
<%= f.label t.name %>
<%= f.check_box "categories[#{t.id}]" %>
<br />
</div>
<% end %>
Specifically, it's telling me:
undefined method 'categories[1]' for #<Article:0x007f401193d520> when I try to render the New Article page. How do I fix this? Thanks.
It is better to use Rails collection_check_boxes helper instead of trying to create those checkboxes by hand. This helper already creates all the parameter / markup stuff you need in order to add or exclude items of a HABTM relation, all under the hood. So you might change you view to include the following:
<%= f.collection_check_boxes :categories_ids, #categories, :id, :name %>
Don't forget to add this in your strong parameters declaration (since you'll have to receive the selected categories ids and bind them to your Article model):
params.require(:article).permit(
:url, :title, :datetime, :content,
:photo, :initial_comment, categories_ids: []
)
For further customizations (html styling or structure for each checkbox), please refer to the complete documentation
I hope it helps :)
I was following this tutorial http://www.sitepoint.com/nested-comments-rails/ to implement nested comments for an image board. It worked fine until I made "comments" belong to "boards" and then had to nest my routes.
Here are my routes:
Rails.application.routes.draw do
root "boards#index"
devise_for :users do
get '/users/sign_out' => 'devise/sessions#destroy'
end
resources :boards do
resources :comments
get '/comments/new/(:parent_id)', to: 'comments#new', as: :new_comment
get '/comments/(:parent_id)', to: 'comments#destroy', as: :delete_comment
get '/comments/edit/(:parent_id)', to: 'comments#edit', as: :edit_comment
end
end
Here is my form:
<%= form_for [#board, #comment] do |f| %>
<% if #comment.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#comment.errors.count, "error") %> prohibited this comment from being saved:</h2>
<ul>
<% #comment.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<%= f.hidden_field :parent_id %>
<div class="form-group">
<% if #comment.parent_id == nil %>
<%= f.label :title %>
<%= f.text_field :title, class: 'form-control' %>
<% else %>
<% nil %>
<% end %>
</div>
<div class="form-group">
<%= f.radio_button(:user_id, current_user.id) %>
<%= f.label(:user_id, "I want to post as myself") %>
<%= f.radio_button(:user_id, nil) %>
<%= f.label(:user_id, "I want to post anonymously") %>
</div>
<div class="form-group">
<%= f.label :content %>
<%= f.text_area :content, class: 'form-control', required: true %>
</div>
<div class="form-group">
<%= f.label :image %>
<%= f.file_field :image %>
</div>
<%= f.submit class: 'btn btn-primary' %>
<% end %>
And here is my controller:
class CommentsController < ApplicationController
def index
#comments = Comment.hash_tree
end
def new
#comment = Comment.new(parent_id: params[:parent_id])
end
def edit
#comment = Comment.find(params[:parent_id])
end
def create
if params[:comment][:parent_id].to_i > 0
parent = Comment.find_by_id(params[:comment].delete(:parent_id))
#comment = parent.children.build(comment_params)
else
#comment = Comment.new(comment_params)
end
if #comment.save
redirect_to root_url
else
render 'new'
end
end
def update
#comment = Comment.find(params[:id])
if #comment.update(comment_params)
redirect_to #comment
else
render 'edit'
end
end
def make_parent
#comment.parent_id = nil
#comment.save
end
def destroy
#comment = Comment.find(params[:parent_id])
#comment.destroy
respond_to do |format|
format.html { redirect_to comments_url }
end
authorize! :destroy, #comment
end
private
def comment_params
params.require(:comment).permit(:title, :content, :user_id, :image)
end
end
I've tried setting a custom route in the form, this gets the form to at least appear, however when you hit the submit button it returns 'No route matches [POST] "/boards/1/comments/new"'. If I got to the controller and then change the corresponding "get" to a "post" then it causes the form to just reappear after pressing submit and nothing is added to the database. I've also experimented with shallow nesting my routes as per my instructors advice but this didn't work.
Your association between boards and comments must be:
board.rb
has_many :comments
comment.rb
belongs_to :user
routes.rb
resources :boards do
resources :comments, only: [:new, :edit, :destroy]
end
this will create a route
new_board_comment GET /boards/:board_id/comments/new(.:format) comments#new
edit_board_comment GET /boards/:board_id/comments/:id/edit(.:format) comments#edit
board_comment DELETE /boards/:board_id/comments/:id(.:format) comments#destroy
I've created a small app with a bar to post messages and I want to add a delete button to every message so I can delete it.
This is my code:
Controller:
class HomeController < ApplicationController
def create
#message = Message.new(params[:message])
if #message.save
redirect_to "/home/show_msg"
else
render :action => "/home/home_page"
end
end
def home_page
#message = Message.new
end
def show_msg
#postwords = Message.all
end
def index
end
end
View (home_page):
<center><h1>MiNibloG</h1></center>
<br>
<br>
<div align="center">
<%= form_for #message, :url => "/home/create" do |f| %>
<%= f.label :postword %>
<%= f.text_field :postword %>
<%= f.submit "Post" %>
<% end %>
</div>
View (show_msg):
<center>
<h1>Posts list</h1>
<br>
<br>
<ul>
<% #postwords.each do |p| %>
<%= p.postword %><br><br>
<% end %>
</ul>
</center>
And this is the migration:
class CreateMessages < ActiveRecord::Migration
def self.up
create_table :messages do |t|
t.string :postword
t.timestamps
end
end
def self.down
drop_table :messages
end
end
Please take a look at the scaffolding, scaffold fits ideally for you situation.
rails generate scaffold message postwords:string
This command generate for you views, controller and migration.
For deleting with your own non REST controller, use something like code below(didnt test it)
<% #postwords.each do |p| %>
<%= p.postword %>
<%= link_to 'Destroy', destroy(p) %><br>
<% end %>
in controller:
def destroy
#message = Message.find(params[:id])
#message.destroy
respond_to do |format|
format.html { redirect_to some_route }
format.xml { head :ok }
end
end
add destroy action in the routes.rb