in my app I'm creating the categories in admin(so the admins creates, updates and destroys the categories name) and then when the users will create the posts they will select( or I'm thinking for switch with checkbox) a category for the posts.
I decide to do this implementation with a has many through for posts and categories. But I' having doubts for implements:
the post_params;
the methods for add the categories and then destroy the categories
and the parts for create, update and destroy the posts.
How can I implement this? It's a better way do different? So if someone help me with this I will appreciate.
Post.rb
class Post < ActiveRecord::Base
has_many :categorizations
has_many :categories, through: :categorizations
...
def add_category(category)
categorizations.create(category_id: category.id)
end
def remove_category(category)
categorizations.find_by(category_id: category.id).destroy
end
category.rb
class Category < ActiveRecord::Base
has_many :categorizations
has_many :posts, through: :categorizations
validates :name,
presence: true,
length: { minimum: 3, maximum: 30 },
uniqueness: true
end
categorization.rb
class Categorization < ActiveRecord::Base
belongs_to :post
belongs_to :category
validates :post_id, presence: true
validates :category_id, presence: true
end
controllers/admin/PostsController
class Admin::PostsController < Admin::ApplicationController
def new
#post = Post.new
#categories = Category.all.map{ |c| [c.name, c.id]}
end
def create
#post = Post.new(post_params)
#post.author = current_user
#post.categories << params[:category_id]
if #post.save
flash[:notice] = "Post has been created."
redirect_to #post
else
flash[:alert] = "Post has not been created."
render "new"
end
end
def destroy
#post = Post.find(params[:id])
#post.destroy
flash[:notice] = "Post has been deleted."
redirect_to posts_path
end
private
def post_params
params.require(:post).permit(:title,
:subtitle,
:content,
:attachment,
:attachment_cache,
:remote_attachment_url,
:categorizations_attributes => [:id,
:post_id,
:category_id,
:_destroy,
:categories_attributes => [:id,
:name]
]
)
end
end
controllers/posts_controller.rb
class PostsController < ApplicationController
before_action :set_post, only: [:show, :edit, :update]
def index
#posts = policy_scope(Post)
end
def show
authorize #post, :show?
end
def edit
authorize #post, :update?
end
def update
authorize #post, :update?
if #post.update(post_params)
flash[:notice] = "Post has been updated."
redirect_to #post
else
flash.now[:alert] = "Post has not been updated."
render "edit"
end
end
private
def set_post
#post = Post.find(params[:id])
rescue ActiveRecord::RecordNotFound
flash[:alert] = "The post you were looking for could not be found."
redirect_to posts_path
end
def set_category
#category = Category.find(params[:category_id])
end
def post_params
params.require(:post).permit(:title, :subtitle, :content, :attachment, :attachment_cache, :remove_attachment, :remote_attachment_url, :category_id)
end
end
posts/_form.html.slim
= simple_form_for([:admin, #post], :html => { :multipart => true }) do |f|
= select_tag(:category_id, options_for_select(#categories), :prompt => "Select ad Category")
routes
Rails.application.routes.draw do
namespace :admin do
root 'application#index'
resources :posts, only: [:new, :create, :destroy]
resources :categories
resources :users do
member do
patch :archive
end
end
end
devise_for :users
root "posts#index"
resources :posts, only: [:index, :show, :edit, :update]
end
In your form
= select_tag(:category_ids, options_for_select(#categories), :prompt => "Select ad Category", multiple: true)
In your controller
params.require(:post).permit(:title, :subtitle, :content, :attachment, :attachment_cache, :remove_attachment, :remote_attachment_url, :category_ids)
Also need to give some advise, in your posts_controller.rb
remove below line
rescue ActiveRecord::RecordNotFound
Insterad of this write it in application_controller.rb , so it will work for whole application.
What I do for fix was put on the PostsController this:
class Admin::PostsController < Admin::ApplicationController
before_action :set_categories, only: [:new, :create]
.
.
.
private
def set_categories
#categories = Category.all.select(:id, :name)
end
def post_params
params.require(:post).permit(:title,
:subtitle,
:content,
:attachment,
:attachment_cache,
:remote_attachment_url,
category_ids:[]
)
end
And on the form I changed for use checkboxes and with that I can select and add more categories for a Post:
= f.association :categories, label: "Select the Categories", as: :check_boxes , collection: #categories.map{|c| [c.name, c.id]}, include_hidden: false
Add on the class Post and Category a dependent: :destroy for destroy the joins properly.
has_many :categorizations, dependent: :destroy
has_many :categories, through: :categorizations
And was necessary delete the validation for post_id on categorization so I just comment because when was uncommented when I try to create a Post it wasn't possible, So I do this:
class Categorization < ActiveRecord::Base
belongs_to :post
belongs_to :category
#validates :post_id, presence: true
validates :category_id, presence: true
end
And work's!
Related
I understand that this issue has been raised several times, but I can't find to find a way around..
I am using this solution to create a multi-step form without the Wicked gem:
Best way to make Multi-Steps form in Rails 5
It seems that I am able to create a product when I use binding.pry and enter the needed commands inside my rails console.
However, the app in itself does not function and I can't manage to work around it..
Two issues throw the ActionController::ParameterMissing error:
1) First, anytime I intend to press the back_button it raises ParameterMissing error (see at the end for exact error message).
2) When I get to the last_step of the form, it displays all the needed information, but will neither create nor save new products (also ParameterMissing).
Here is my code:
Routes
get 'products/new', to: 'products#new', as: 'new_product'
post 'products', to: 'products#create', as: 'create_new_product'
resources :categories, only: [:index, :show, :new, :edit, :destroy] do
resources :sub_categories, only: [:index, :show, :new, :edit, :destroy]
resources :products, only: [:index, :show, :destroy]
end
Products Controller
class ProductsController < ApplicationController
skip_after_action :verify_authorized, except: :index, unless: :skip_pundit?
skip_after_action :verify_policy_scoped, only: :index, unless: :skip_pundit?
before_action :set_category, only: [:index, :show]
def new
session[:product_params] ||= {}
authorize #product = Product.new(session[:product_params])
#product.current_step = session[:product_step]
#product.user = current_user
end
def create
session[:product_params].deep_merge!(params_product) if params_product
authorize #product = Product.new(session[:product_params])
#product.current_step = session[:product_step]
#product.user = current_user
if #product.valid?
if params[:back_button]
#product.previous_step
elsif #product.last_step?
if #product.all_valid?
#product.save!
flash[:notice] = 'Your product was created successfully'
redirect_to newest_products_path && return
end
else
#product.next_step
end
end
session[:product_step] = #product.current_step
if #product.new_record?
return render :new
else
session[:product_step] = session[:product_params] = nil
end
end
private
def set_category
authorize #category = Category.find(params[:category_id])
end
def params_product
params.require(:product).permit(:name, :price, :description, :category, :category_id,
:sub_category, :sub_category_id, :user, :user_id, :id)
end
end
Product Model
class Product < ApplicationRecord
attr_writer :current_step
belongs_to :user, optional: true
belongs_to :sub_category
belongs_to :category, inverse_of: :products
validates :category, presence: true
validates_presence_of :name, :category_id, if: lambda { |e| e.current_step == "card" }
validates_presence_of :sub_category_id, :description, :price, if: lambda { |e| e.current_step == "details" }
def current_step
#current_step || steps.first
end
def steps
%w[card details confirmation]
end
def next_step
self.current_step = steps[steps.index(current_step) + 1]
end
def previous_step
self.current_step = steps[steps.index(current_step) - 1]
end
def first_step?
current_step == steps.first
end
def last_step?
current_step == steps.last
end
def all_valid?
steps.all? do |step|
self.current_step = step
valid?
end
end
end
New Products View
<%= form_for #product, url: create_new_product_path do |f| %>
<%= render "#{#product.current_step}_step", :f => f %>
<div class="bottom-signup">
<%= f.submit "Continue" unless #product.last_step? %>
<%= f.submit "Submit Product" if #product.last_step? %>
<%= f.submit "Back", :name => "back_button" unless #product.first_step? %>
</div>
<% end %>
Here is the exact error that is thrown by ActionController:
ActionController::ParameterMissing in ProductsController#create
param is missing or the value is empty: product
#around ligne (96)
95 def params_product
96 params.require(:product).permit(:name, :price, :description, :category, :category_id,
97 :sub_category, :sub_category_id, :user, :user_id, :id)
98 end
And finally, here is what appears if I raise params.inspect:
params.inspect
=> "<ActionController::Parameters {\"utf8\"=>\"✓\", \"authenticity_token\"=>\"AHfNwkeFOWBeEup+fCCvfZv1RowuP/YULHjo8kTnzer5YNCY7lMYAKzrrWJBVMcfOO+P1GmZGgi9PDpa/09rzw==\", \"commit\"=>\"Submit Product\", \"controller\"=>\"products\", \"action\"=>\"create\"} permitted: false>"
If someone understands where I'm wrong, I'd be more than glad to discuss it!
Thanks in advance.
Best,
Ist
I have 2 model with the association many_to_many. When creating a brand I have error:
NameError in Admin::Brands#new
Showing /.../app/views/admin/brands/_form.html.slim where line #3 raised:
uninitialized constant Brand::BrandCatalog
What am I doing wrong?
#app/models/category.rb
class Category < ActiveRecord::Base
has_many :brand_catalogs
has_many :brands, through: :brand_catalogs
end
#app/models/brand.rb
class Brand < ActiveRecord::Base
has_many :brand_catalogs
has_many :categories, through: :brand_catalogs
end
#app/models/brandcatalog.rb
class BrandCatalog < ActiveRecord::Base
belongs_to :category
belongs_to :brand
end
migration
#db/migrate/20151230092013_create_brand_catalogs.rb
class CreateBrandCatalogs < ActiveRecord::Migration
def change
create_table :brand_catalogs, id: false do |t|
t.integer :category_id
t.integer :brand_id
end
add_index :brand_catalogs, [:category_id, :brand_id]
end
end
brands controller
#app/controllers/admin/brands_controller.rb
class Admin::BrandsController < Admin::BaseController
before_action :require_login
load_and_authorize_resource
before_action :load_brand, only: [:edit, :update, :destroy]
def index
#brands = Brand.all
end
def new
#brand = Brand.new
end
def edit
end
def create
#brand = Brand.create(brand_params)
if #brand.save
redirect_to admin_brands_path, notice: 'Brand was successfully created.'
else
render :new, notice: 'Something wrong!'
end
def update
end
def destroy
end
private
def load_brand
#brand = Brand.find(params[:id])
end
def brand_params
params.require(:brand).permit(:title, {category_ids: []})
end
end
brands form
# views/admin/brands/_form.html.slim
= bootstrap_form_for [:admin, #brand] do |f|
div class='form-group'
= f.collection_check_boxes(:category_ids, Category.all, :id, :title)
div class='form-group'
= f.text_field :title, class: 'form-control'
= f.submit 'Save', class: 'btn btn-success'
Try renaming your #app/models/brandcatalog.rb file to brand_catalog.rb. Modelnames needs to have matching filenames but with underscore instead of camelcase.
For example a model called ThisIsMyModel should have its file named this_is_my_model.rb
I'm somewhat of a newbie with ruby on rails and went off of samurails.com single table inheritance with rails 4 tutorial to add different comment types. This worked great but the problem I'm running into is when I try to use polymorphic associations to get comments and the specific type to function under other models such as project and challenge. A regular comment works, but the specific types do not.
I haven't seen anything that clearly says how to make this work or another option of going about it so any help would be greatly appreciated.
class Comment < ActiveRecord::Base
has_merit
acts_as_votable
belongs_to :commentable, :polymorphic => true
belongs_to :user
belongs_to :commenttype
belongs_to :project
def self.types
%w(Question Idea Problem)
end
def commentable_type=(sType)
super(sType.to_s.classify.constantize.base_class.to_s)
end
scope :questions, -> {where(type: 'Question')}
scope :ideas, -> {where(type: 'Idea')}
scope :problems, -> {where(type: 'Problem')}
end
class Question < Comment
end
class Idea < Comment
end
class Problem < Comment
end
class Project < ActiveRecord::Base
belongs_to :user
has_many :comments, :as => :commentable, :class_name => "Comment"
has_many :questions, :as => :commentable, :class_name => "Question"
has_many :ideas, :as => :commentable, :class_name => "Idea"
has_many :problems, :as => :commentable, :class_name => "Problem"
delegate :questions, :ideas, :problems, to: :comments
end
class CommentsController < ApplicationController
before_action :set_commentable, only: [:index, :new, :create]
before_action :set_type
before_action :set_comment, only: [:show, :edit, :update, :destroy]
def index
#comments = type_class.all
end
def show
end
def new
#comment = type_class.new
end
def edit
end
def create
#comment = #commentable.comments.new(comment_params)
#comment.user = current_user
if #comment.save
redirect_to :back, notice: "#{type} was successfully added."
else
render action: 'new'
end
end
def update
if #comment.update(comment_params)
redirect_to #comment.commentable, notice: "#{type} was successfully updated."
else
render action: 'edit'
end
end
def destroy
#user = current_user
#comment = #commentable.comments.where(comment_user: current_user).first
#commentable.comment.destroy
respond_to do |format|
format.html { redirect_to #commentable, notice: "Comment was deleted." }
format.js
end
end
private
def set_comment
#comment = type_class.find(params[:id])
end
def set_type
#type = type
end
def type
Comment.types.include?(params[:type]) ? params[:type] : "Comment"
end
def type_class
type.constantize
end
def set_commentable
#commentable = find_commentable
end
# add more commentable models here
def find_commentable
if params[:challenge_id]
Challenge.find(params[:challenge_id])
else
end
end
def find_commentable
if params[:project_id]
Project.find(params[:project_id])
else
end
end
def comment_params
params.require(type.underscore.to_sym).permit(:body, :type, :user_id, :commentable_id, :commentable_type, :commentable, :comment_type)
end
end
module CommentsHelper
def sti_comment_path(type = "comment", comment = nil, action = nil)
send "#{format_sti(action, type, comment)}_path", comment
end
def format_sti(action, type, comment)
action || comment ? "#{format_action(action)}#{type.underscore}" : "#{type.underscore.pluralize}"
end
def format_action(action)
action ? "#{action}_" : ""
end
end
<%= form_for [commentable, Comment.new], :html => { :multipart => true } do |f| %>
<%= f.text_area :body, class: "form-control", placeholder: "What's on your mind?" %>
<%= f.label :type %><br>
<%= f.select :type, Comment.types.map {|r| [r.humanize, r.camelcase]}, {}, disabled: #type != "Comment" %>
<%= f.submit "Post", class: "btn pull-right" %>
I have items, that each have multiple (threaded) comments.
The threading is done via a parent key, that points towards another comment.
I just can't get the create action to work properly, I got it far enough to submit into the database, but it didn't have item_id and parent_id set.
I tried form_for [#item, #comment] instead of form_for :comment, url: item_comments_path( #item ) but it didn't help either.
When I look at comment_params in the create action I get this:
Parameters: {"utf8"=>"✓", "comment"=>{"body"=>"examplecommentgoeshere"}, "parent_id"=>"4", "commit"=>"Create Comment", "item_id"=>"4"}
Can anyone help?
Models:
class Item < ActiveRecord::Base
belongs_to :user
has_many :comments, dependent: :destroy
has_many :images, as: :item,
dependent: :destroy
validates :title, presence: true,
allow_blank: false
validates :description, presence: true,
allow_blank: false
validates :user, presence: true,
allow_blank: false
validates :layout, presence: true
end
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :item
has_many :responses, class_name: "Comment",
foreign_key: "parent_id",
dependent: :destroy
has_one :image, as: :item,
dependent: :destroy
belongs_to :parent, class_name: "Comment"
validates :body, presence: true,
allow_blank: false,
length: { minimum: 10 }
end
Comment Controller:
class CommentsController < ApplicationController
before_filter :findParent
before_filter :find, only: [:update, :destroy, :show]
before_filter :is_logged_in?, only: [:create, :update, :destroy]
before_filter :has_permission?, only: [:update, :destroy]
def new
#comment = Comment.new
end
def create
logger.debug comment_params.inspect
#comment = current_user.comments.build(comment_params)
if #comment.save
redirect_to #item
else
logger.debug #comment.errors.inspect
session[:errorbj] = #comment
redirect_to #item
end
end
[...]
private
def comment_params
params.require(:comment).permit(:body, :parent)
end
private
def find
#comment = Comment.find(params[:id])
end
private
def findParent
#item = Item.find params[:item_id]
end
Item View:
<p>
<h3>Add a comment:</h3>
<%= render partial: 'comments/form', locals: { parent: #item } %>
</p>
Partial comments/form:
<%= form_for :comment, url: item_comments_path( #item ) do |f| %>
<p>
<%= f.label :body %><br>
<%= f.text_area :body %>
</p>
<p>
<%= f.label :image %><br>
<nope>
</p>
<%= hidden_field_tag :parent_id, parent.id %>
<p>
<%= f.submit %>
</p>
<% end %>
From another stackoverflow thread I concluded that the only way to create an object in a multi-association is to do something along the lines of this:
#comment = current_user.comments.build( comment_params.join( { parent_id: <parent.id> } ) )
Other stackoverflow posts however said not to do
#item = Item.new( item_params.join( { user_id: current_user.id } )
So is there really no better way?
i think there are a lot of errors in your code. i hope that i can put that into a form that might help you figure out the fix by yourself:
use form_for [#item, #comment]
findParent should be find_parent
findParent should set a #parent
findParent should find a Comment
the new action should initialize with a parent #comment = Comment.new parent: #parent
hidden_field_tag :parent_id, parent.id should be form.hidden_field :parent
the parent association should be set via comment_params
you don't need the Rails.logger statement, its all in logs/development.log
don't put objects into the session
why is there item and image?
learn more about debugging! http://nofail.de/2013/10/debugging-rails-applications-in-development/
It turns out this is much easier than it seemed.
This is how I ended up doing it:
class CommentsController < ApplicationController
before_filter :findParent, only: [:create]
[...]
def create
#comment = current_user.comments.build(comment_params)
#comment.item = #item
if #comment.save
redirect_to #item
else
session[:errorbj] = #comment
redirect_to #item
end
end
Error:
No route matches {:action=>"new", :controller=>"comments", :parent_id=>1}
routes.rb:
MyApp::Application.routes.draw do
resources :posts do
resources :comments
end
resources :topics do
resources :posts
end
root :to => "posts#index"
end
models:
class Topic < ActiveRecord::Base
has_many :posts, :dependent => :destroy
attr_accessible :name, :post_id
end
class Post < ActiveRecord::Base
belongs_to :topic, :touch => true
has_many :comments, :dependent => :destroy
accepts_nested_attributes_for :topic
attr_accessible :name, :title, :content, :topic, :topic_attributes
end
class Comment < ActiveRecord::Base
has_ancestry
attr_accessible :name, :content
belongs_to :post, :touch => true
end
view:
<%= link_to "Reply", new_post_comment_path(#post, :parent_id => comment.id) %>
controller:
class CommentsController < ApplicationController
respond_to :html, :xml
def show
#post = Post.find(params[:id])
#comments = #post.comments.order("updated_at").page(params[:page])
end
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.build(params[:comment])
if #comment.save
flash[:notice] = "Replied to \"#{#post.title}\""
redirect_to(#post)
else
flash[:notice] = "Reply failed to save."
redirect_to(#post)
end
end
def new
#post = Post.find(params[:post_id])
#comment = Comment.new(:parent_id => params[:parent_id])
# #comment = #post.comments.build
end
def destroy
#post = Post.find(params[:post_id])
#comment = #post.comments.find(params[:id])
#comment.destroy
redirect_to post_path(#post)
end
end
By reading the code you might have gathered that I am trying to get the ancestry gem to work with nested resources. I've been using the Railscasts episode on the Ancestry gem to guide me. Thanks for reading my question.
Try to pass comment id
link_to "Reply", new_post_comment_path(#post, :parent_id => comment.id).
You need to use the nested path: link_to "Reply", new_post_comment_path(#post, :parent_id => comment).
rake routes can be your friend.