Rendering in Rails (ruby 2.2.1, rails 4.2.1) - ruby-on-rails

My problem is in rendering error messages, when #post.save returns false. I don't want use flash messages.
I have two resourses:
config/routes.rb
resources :users
resources :posts, only: [:create, :destroy]
User can have many microposts on his personal page (e.g. users/1). And I need to add ability of creating microposts from user's show view. Creating with valid information works well, but I want to display error messages from model Post in another way.
app/controllers/posts_controller.rb
...
def create
#post = current_user.posts.build(post_params)
if #post.save
redirect_to user_path(current_user)
else
#?????
render ???
end
end
app/views/users/show
...
<%= form_for(#post) do |form| %>
<%= render 'shared/error_messages', object: form.object %>
<%= form.text_area :text, placeholder: "Что у Вас нового?" %>
<%= form.submit "Добавить" %>
<% end %>
In partial I am using object.errors.full_messages. Any ideas?

Related

How do I get a destroy method to work for nested resources in Rails?

Currently learning Ruby on Rails and creating a simple blog app with comments. I have a Comment model and an Article model. Comment is polymorphic and both models have many comments.
I'm trying to come up with a destroy method that's able to delete both the comments that belong to Comment and the ones that belong to Article (and that remain as [deleted] without destroying their children, much like in Reddit, although I haven't even gotten to that part).
I have tried different paths but I haven't got it right yet. Nested paths still confuse me a little and I'm not sure on how to pass the params that the path requests when creating the link_to.
These are my files:
routes.rb:
Rails.application.routes.draw do
get 'comments/new'
get 'comments/create'
get 'articles/index'
get 'articles/show'
root 'articles#index'
resources :articles do
resources :comments
end
resources :comments do
resources :comments
end
end
article.rb:
class Article < ApplicationRecord
has_many :comments, as: :commentable
end
comment.rb:
class Comment < ApplicationRecord
belongs_to :commentable, polymorphic: :true
has_many :comments, as: :commentable
end
comments_controller.rb:
class CommentsController < ApplicationController
before_action :find_commentable
def new
#comment = Comment.new
end
def create
#comment = #commentable.comments.new(comment_params)
if #comment.save
redirect_back(fallback_location: root_path)
else
redirect_back(fallback_location: root_path)
end
end
def destroy
#comment = #commentable.comments.find(params[:id])
#comment.destroy
redirect_back(fallback_location: root_path)
end
private
def comment_params
params.require(:comment).permit(:body)
end
def find_commentable
if params[:article_id]
#commentable = Article.find_by_id(params[:article_id])
elsif params[:comment_id]
#commentable = Comment.find_by_id(params[:comment_id])
end
end
end
show.html.erb, where the form for commments that belong to Article.rb is:
<h1> <%= #article.title %> </h1>
<p> <%= #article.body %> </p>
<small>Submitted <%= time_ago_in_words(#article.created_at) %> ago </small> <br/>
<h3>Comments</h3>
<%= form_for [#article, Comment.new] do |f| %>
<%= f.text_area :body, placeholder: "Say something!" %> <br/>
<%= f.submit "Submit" %>
<% end %>
<ul class="parent-comment">
<%= render partial: 'comments/comment', collection: #article.comments %>
</ul>
<%= link_to "Index", articles_path %>
And the partial _comment.html.erb , which displays the comments that belong to the article as well as those that belong to other comments, and where I'm trying to integrate the link_to:
<p> <%= comment.body %> </p>
<small>Submitted <%= time_ago_in_words(comment.created_at) %> ago </small> <br/>
<%= form_for [comment, Comment.new] do |f| %>
<%= f.text_area :body, placeholder: "Add a reply!" %><br/>
<%= f.submit "Reply" %>
<%= link_to "Delete", comment_path(comment), method: :delete %>
<% end %>
<ul>
<%= render partial: 'comments/comment', collection: comment.comments %>
</ul>
Whenever I do seem to get the path right, NoMethodError in CommentsController#destroy — undefined method `comments' for nil:NilClass comes up. Why would the controller show it as undefined? It worked in the new method, as far as I can see.
Could you give some guidance as to what I should do or what I should fix? I'm not sure how to delete the parent comments, either, and I haven't managed to find information that suits this case. If you know where to point me to, I'm all eyes.
Thank you.
Because of your design model structure.
Your view
<%= link_to "Delete", comment_path(comment), method: :delete %>
Your find_commentable
elsif params[:comment_id]
#commentable = Comment.find_by_id(params[:comment_id])
end
#commentable will be a Comment class, so it won't have .comments methods as your Article class
check carefully to destroy the method
def destroy
#comment = #commentable.comments.find(params[:id])
#comment.destroy
redirect_back(fallback_location: root_path)
end
use #comment = #commentable.comments.find_by(id: params[:id]) and check whether #comment has some value or not?
just add one condition like this and it won't throw the error:
#comment.destroy if #comment
if #comment is nil and trying to destroy then it will throw the error.

Rails form rendering wrong URL after validation errors (not keeping passed parameter)

On a project show page, I pass a very simple parameter on my 'create new task' that stores which project its from:
#project.id), :class => "btn btn-info col-md-12" %>
so that when i create a new task for it, it stores it in the URL on my new task form like this:
http://localhost:3000/task/new?project_id=5
My New form is as follows:
<div class="container sign-in-register">
<div class="authform">
<%= form_for #task, :html => {:multipart => true} do |f| %>
<h3>Add a task for this project...</h3><br/>
<%= f.label :name %>
<%= f.text_field :name, class: 'form-control' %>
<%= hidden_field_tag 'project_id', #project_id %>
<%= f.fields_for :taskrelationships do |ff| %>
<%= ff.hidden_field :taskproject_id, value: #project_id %>
<%= ff.label :task_url %>
<%= ff.text_field :task_url, class: 'form-control' %>
<% end %>
<br clear="all">
<%= f.submit "Save Task", class: "btn btn btn-info" %>
<% end %>
</div>
</div>
As you can see i'm using nested attributes in the form (I am creating both a task and a TaskRelationship. Now, when I try to save without filling out all the required fields a validation is thrown but for some reason it redirects me to:
http://localhost:3000/tasks
instead of the original:
http://localhost:3000/tasks/new?project_id=5
I have read many posts and none seem to answer this particular case. The stackO post below was close but when I try it with task instead of user it still cannot find the task_ID
Render error on new page with customize url like http://localhost:3000/education_informations/new?user_id=10
How can I have rails simply render the same exact url as I started with - it seems like this should be easy so must be missing something small.
My controller actions:
def new
#project_id = params[:project_id]
#task = Task.new
#task.taskrelationships.build
end
def create
#project = Project.find(params[:project_id])
#task = Task.new(task_params)
if #task.save
flash[:success] = "This task has been added."
#task.taskrelationships.create!(#taskrelationships_params)
redirect_to tasks_project_path(#project)
else
#task.taskrelationships.build(#taskrelationships_params)
flash[:alert] = #task.errors.full_messages.to_sentence
render :new
end
end
private
def task_params
#taskrelationships_params = params.require(:task).permit(taskrelationships_attributes: [
:task_url,
:taskproject_id
])[:taskrelationships_attributes]["0"]
params[:task].delete(:taskrelationships_attributes)
params.require(:task).permit(
:name,
:user_id,
taskrelationships_attributes: [
:task_url,
:taskproject_id
]
).merge(owner: current_user)
end
UPDATE W / ROUTES
resources :projects do
resources :reviews, except: [:destroy]
member do
get :tasks
end
end
resources :tasks
resources :taskrelationships, only: [:create, :destroy] do
post :vote, on: :member, controller: "task_relationships"
end
thanks for any assistance...
Ok firstly an explanation as to what is going on here:
When you invoke http://localhost:3000/task/new?project_id=5 you are actually being routed to the new action on the task controller (with a project_id param).
Your new action then sets the variables and rails will render the new.html.erb which contains your new task form.
When you submit the form it actually is doing a http POST to /tasks, which routes to the create action of your tasks controller. That url and http method is a result of what is generated from the form_for helper:
<%= form_for #task, :html => {:multipart => true} do |f| %>
This is why the url changes from /tasks/new?project_id=5 to /tasks
Now the create action if it fails the validation simply renders the new form - it is not redirecting anywhere - the url remains unchanged from what it was when it entered this action - meaning, it remains as /tasks.
You do not actually need to navigate to /tasks/new?project_id=5 to render the new form but what you do need to do is set #project_id in the controller so the view has access to that variable (just as it does in the new action):
def create
#project = Project.find(params[:project_id])
#task = Task.new(task_params)
if #task.save
flash[:success] = "This task has been added."
#task.taskrelationships.create!(#taskrelationships_params)
redirect_to tasks_project_path(#project)
else
#task.taskrelationships.build(#taskrelationships_params)
#project_id = #project.id
flash[:alert] = #task.errors.full_messages.to_sentence
render :new
end
end
So, to clarify the change in url is not a redirection it's just that the form is posting to a different url than /tasks/new, and this is actually just a cosmetic issue.
Now if it is a concern to you, you can change the routing to something like the following:
resources :tasks, except: [:create, :new]
post 'new_task' => 'tasks#create'
get 'new_task' => 'tasks#new'
This is mapping the POST and GET http methods to /new_task so the url appears the same for the new and create action invocations. Note you do need to change the url in the form_for helper to use this new route:
<%= form_for #task, url: 'new_task', multipart: true do |f| %>
Since Rails default behaviour in forms is with remote: true, you can move the content of the form to a partial(let's name it _my_form.html.erb), add to the controller action (let's say in create action):
respond_to do |format|
format.js {}
end
Then add a create.js.erb file where you will render the form partial
$("#form").html(
"<%= j render partial: 'my_form', locals: { entity: #entity } %>"
);
Thus, validation errors and all attributes will be accessible inside the form and there is no need to hack the "Rails approach"
I had to adapt and experiment with the currently accepted answer,
and the following ended up working well (using schools rather than tasks):
In config/routes.rb:
resources :schools do
...
end
post 'schools/new' => 'schools#create'
And create the form like:
= simple_form_for [#school], url: 'new' do |f|
Thus the path /schools/new was preserved on validation :)

rails - undefined method `model_name'

I have a basic blogging site in which I want to allow users to offer 'corrections' to posts (just think of it as a comment). The correction object belongs to a post, which in turn belongs to a user (for which I'm using Devise).
I would like the form to create a new correction to be nested in the page for the post,so I'm just rendering the form in posts/show.html.erb with <% render :template => "corrections/new" %>. I'm getting a 'undefined method model_name' error from the line form_for line in corrections/new.html.erb though.
Here's the form:
<% form_for [#correction, :url=> user_post_corrections_path(current_user, #post, #correction)], html: { multipart: true} do |f| %>
<div class="field">
<%= f.label :correction %>
<%= f.text_field :correction %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Here's the corrections_controller:
class CorrectionsController < ApplicationController
def new
#post = Post.find(params[:post_id])
#correction = current_user.#post.corrections.build
respond_with(#correction)
end
def create
#post = Post.find(params[:post_id])
#correction = current_user.#post.corrections.build
if #correction.save
redirect_to user_post_path(current_user, #correction.post_id)
end
end
end
In my routes.rb:
resources :users do
resources :posts do
resources :corrections
end
end
Any help with this would be much appreciated.
There are several issues I can see, first off:
The form is not rendered:
The form for should be evaluated not computed so you should use <%= %> tag instead of <%%> tag, so it becomes:
<%= form_for #correction, url: user_post_corrections_path(current_user, #post, #correction), html: { multipart: true} do |f| %>
Second: You are saying form_for #correction, and you said this form is shown in posts/show, this means that the show action for the post should have the following in its controller:
#correction = Correction.new user: current_user, post: #post
This is assuming you are using the #post variable for the post show meaning you have something like
#post = Post.find params[:id]
This line is completely off
#correction = current_user.#post.corrections.build
you can just say:
#correction = Correction.new correction_params
#correction.user = current_user
#correction.post = #post
if #correction.save
redirect_to user_post_path(current_user, #correction.post_id)
end
The important part is your show action on the posts controller having the initialization of your #correction object.
This line looks off:
#correction = current_user.#post.corrections.build
#post is an instance variable, not a method on current_user
Did you intend to build a correct with the current user? The following should work:
#post.corrections.build(user: current_user)
If your corrections model has_many :users, through: :posts, then the following should work:
#post.corrections.build(users: [current_user])
EDIT
The format of your form_for is also incorrect. The url: key in form_for doesn't belong in the resource array (the first arg in your form_for)
The following modification should resolve the error, provided user_post_corrections_path takes :id, :post_id, and :correction_id
form_for #correction, url: user_post_corrections_path(current_user, #post, #correction), html: { multipart: true} do |f|
I'd also reduce your routing to two levels of nesting if possible.
Perhaps this is easier?
resources :posts do
resources :corrections
end
end
If it's implied a post belongs to the current_user then it may not be necessary to have /users in the route path.

Showing form error messages

I'm having trouble getting my redirect and error messages to work. From what I've read you cant get a forms errors to show up when you use redirect so I am trying to use render after it fails.
I have a new post form on a topic page. The url is "topic/1". If you make a post about the topic and something is wrong with the input I want it to go back to the page at topic/1 and display errors and I cant figure out how to get it to go back. Redirect (:back) does what I want but doesnt show the forms errors.
The form on the topic's show.html page:
<%= form_for(#post) do |f| %>
<%= render 'shared/post_error_messages' %>
<%= f.label :title, "Post Title" %>
<%= f.text_field :title %>
<%= f.label :content %>
<%= f.text_field :content %>
<%= f.hidden_field :parent_id, value: 0 %>
<%= f.hidden_field :topic_id, value: #topic.id %>
<%= f.hidden_field :user_id, value: current_user.id %>
<%= f.submit "Create Post" , class: "btn btn-small btn-primary" %>
<% end %>
Create action in the Posts controller
def create
#post = Post.new(post_params)
#topic = Topic.find_by(id: params[:topic_id])
if #post.save
redirect_to #post
else
#topic = Topic.new
render "/topics/show"
end
end
I guess I'm mostly trying to do the render with the id from the page that the form was originally on.
Errors
The problem isn't anything to do with the way you're rendering the form (render or redirect) - it's to do with the way you're handling your ActiveRecord object.
When you use form_for, Rails will append any errors into the #active_record_object.errors method. This will allow you to call the following:
form_for error messages in Ruby on Rails
<%= form_for #object do |f| %>
<% #location.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
<% end %>
This only works if you correctly create your ActiveRecord object, which you seem to do
--
Nested
#config/routes.rb
resources :topics do
resources :posts, path: "", path_names: {new: ""}, except: [:index] #-> domain.com/topics/1
end
You'll be much better using the following setup for a nested route:
<%= form_for [#topic, #post] do |f| %>
...
<% end %>
This allows you to create a form which will route to the topics_posts_path, which is basically what you need. The controller will then balance that by using the following:
#app/controllers/topics_controller.rb
Class TopicsController < ApplicationController
def new
#topic = Topic.find params[:topic_id]
#post = Post.new
end
def create
#topic = Topic.find params[:topic_id]
#post = Post.new post_params
end
private
def post_params
params.require(:post).permit(:attributes)
end
end
You are overwriting the Topic you original found with a brand new, empty one - which shouldn't be necessary and which is causing the posts related to it to disappear.
Also - if your topic and post are related - you should create the post on the appropriate association #topic.posts instead of the main Post class.
The #topic.posts.new means that the post's topic-id is automatically updated with the value of the #topic.id ... which means you don't need to set it in the hidden-field on the form.
In fact it's better if you don't - just delete that hidden field entirely.
If you add that to the first time you get a new post too (eg in topics/show) then you won't need to pass in a value to the hidden-field.
Also I'd do the same for all the other hidden-fields on the server-side too. You don't really want the user to use firebug to hack the form and add some other user's id... so do it in the create action and don't bother with the hidden field
This should work:
def create
#topic = Topic.find_by(id: params[:topic_id])
#post = #topic.posts.new(post_params)
#post.user = current_user
#post.parent_id = 0
if #post.save
redirect_to #post
else
render "/topics/show"
end
end
if it doesn't - let me know what error messages you get (so we can debug)

Rails 4 Routing Error - no rout matches [POST] "/categories/new"

While learning Rails 4 I stuck while trying to add categories to posts of my simple blog. I have generated model, ran the migration and added a controller. No matter what I do now when trying to create a category, I keep running into same mistake: no route matches [POST], which is weird, as I seem to have all the code in place. Please help!
categories controller
class CategoriesController < ApplicationController
def index
#categories = Category.all
end
def new
#category = Category.new
end
def create
#category = Category.new(category_params)
#category.save
redirect_to new_category_path, alert: "Category created!"
end
def show
#category = Category.find(params[:id])
end
def destroy
#category = Category.find(params[:id])
#category.destroy
redirect_to categories_path
end
private
def category_params
params.require(:category).permit(:name)
end
end
routes.rb
Blog::Application.routes.draw do
get 'tags/:tag', to: 'posts#index', as: :tag
resources :categories
resources :posts do
resources :comments
end
root 'welcome#index'
end
category.rb
class Category < ActiveRecord::Base
validates :name, presence: true
has_many :posts
end
new.html.erb
<%= form_for :category do |f| %>
<p>
<%= f.label :name %><br>
<%= f.text_field :name %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
/categories/new
No route matches [POST] "/categories/new"
You should have in your view
<%= form_for #category do |f| %>
<p>
<%= f.label :name %><br>
<%= f.text_field :name %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
#category object is used by form_for method to figure out form url.
If you pass only Symbol to form_for method, without specifying url explicitly, form will be generated with url being the current url.
Although the #category works, if you read a bit further you will see that they will explain why the your code sends No route matches [POST] "/categories/new".
The guide actually explains that you need to specify the url: posts_path for the form to use the right route.
There's one problem with this form though. If you inspect the HTML
that is generated, by viewing the source of the page, you will see
that the action attribute for the form is pointing at /posts/new. This
is a problem because this route goes to the very page that you're on
right at the moment, and that route should only be used to display the
form for a new post.
The form needs to use a different URL in order to go somewhere else.
This can be done quite simply with the :url option of form_for.
Typically in Rails, the action that is used for new form submissions
like this is called "create", and so the form should be pointed to
that action.
Edit the form_for line inside app/views/posts/new.html.erb to look
like this:
<%= form_for :post, url: posts_path do |f| %>
In this example, the
posts_path helper is passed to the :url option. What Rails will do
with this is that it will point the form to the create action of the
current controller, the PostsController, and will send a POST request
to that route.

Resources