Routing a post says missing template - ruby-on-rails

I'm rather confused about this; I have a custom route here.
I have a groups/:id/new_caretaker. This has a form on it. Whenever that form is POSTed it should go to the same page; But to a different method.
However, if I post the form it says Missing template groups/create_caretaker, application/create_caretaker
How can I fix this?
Here's my form:
<%= form_tag(controller: "groups", action: "create_caretaker", method: "post") do %>
<div class="field">
<%= text_field_tag('email') %>
</div>
<%= submit_tag "Opslaan"%>
<% end %>
And my routes:
get "/groups/:id/new_caretaker" => "groups#new_caretaker", :as => :new_caretaker
post "/groups/:id/new_caretaker" => "groups#create_caretaker"
Added groups.controller methods:
Note: new_caretaker gets #group from a :before_action
def new_caretaker
end
def create_caretaker
email = params[:email]
if !email.blank?
userToAdd = User.find_by_email(email)
if userToAdd.blank?
#User doesn't exist
else
#User does exist
respond_to do |format|
if #group.users.find_by_id(userToAdd)
#theAlert = 'Deze gebruiker zit al in de groep en is niet toegevoegd'
format.html { render action: 'new_caretaker' }
format.json { render json: #theAlert, status: :unprocessable_entity }
else
#group.users << userToAdd
format.html { redirect_to #group, notice: 'De begeleider is toegevoegd.' }
end
end
end
end
end

The if !email.blank? => false and if userToAdd.blank? => true branches don't render or redirect. Therefor rails if looking for a template with the name of that action.

I'd suggest to enhance you routing editing routes.rb:
resources :groups do
member do
get 'new_caretaker'
post 'create_caretaker'
end
end
And then you call this redirect:
redirect_to group_new_caretaker_path(#group)
in the controller when needed.
This way, you'll also get a params[:group_id] param in the new_caretaker controller method to be able to handle group-related data (if needed).

The fix: I did not render anything in the if, just in the else. Some good tips in the answers though.

Related

How to proceed accept and reject feature with rails

I'm trying a feature where user can request for offer and it can be accepted or rejected , I'm new to rails. i can't figure out what's the good way to proceed this.
offer create method
def create
#offer = Offer.new(offer_params)
pp offer_params
#barter = Barter.find(params[:barter_id])
#offer = Offer.new(offer_params)
#offer.barter = #barter
#offer.user = current_user
respond_to do |format|
if #offer.save
format.js do
#barter = Barter.find(params[:barter_id])
end
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: #review.errors, status: :unprocessable_entity }
end
end
end
offer submission
<%= form_for([ #barter, #barter.offers.new] ) do |form| %>
<%= form.text_area :message %><br>
<%= form.submit "leave" %>
<% end %>
here I want to make it accepted or rejected , I've given a boolean value and simply make it false when rejected
<%= form_tag([ #barter, #barter.offers.new] ) do %>
<%= hidden_field_tag :reject, :value => true %><br>
<%= submit_tag "reject" %>
<% end %>
is there a good way to do this? and how can i make it disappear when i accept this.
Sorry but thats not even close. You're just creating a new offer record in the form when what you should be doing is to update an existing record - and while you potentially do this through PATCH /offers/:id its going to be very ambigeuos in terms of intent.
The simplest way I cn think of handle this would be to simply add two additional RESTful routes to update the offers.
Start by adding the routes:
resources :offers, only: [] do
patch :accept
patch :decline
end
And en enum attribute to the model:
class AddStatusToOffers < ActiveRecord::Migration[7.0]
def change
add_column :offers, :status, :integer, default: 0, index: true
end
end
class Offer < ApplicationRecord
# ...
enum status: {
pending: 0,
accepted: 1,
rejected: 2
}
end
This is a better idea then adding a boolean since your boolean would either need to be a tri-state boolean (nullable) which is regarded as a very bad practice or default to false in which case you can't differentiate between the offers a users has replied to or not.
Then add the controller methods for your new endpoints:
class OffersController
before_action :set_coffer, only: %i{ show edit update destroy accept decline }
# ...
# PATCH /offers/:id/accept
# #TODO authorize that the user should actually be allowed the offer
def accept
if #offer.accepted!
redirect_to #offer, notice: 'Offer accepted'
else
redirect_to #offer, notice: 'Offer could not be accepted - please try again'
end
end
# PATCH /offers/:id/reject
# #TODO authorize that the user should actually be reject the offer
def reject
if #offer.rejected!
redirect_to #offer, notice: 'Offer rejected'
else
redirect_to #offer, notice: 'Offer could not be rejected - please try again'
end
end
private
def set_offer
#offer = Offer.find(params[:id])
end
end
You can then simply add buttons/links that send the request to update the offer:
<%= button_to "Accept", accept_offer_path(offer), method: :patch %>
<%= button_to "Reject", reject_offer_path(offer), method: :patch %>
This is not the only way to solve the issue. If you for example want to record a message where the user can say why they rejected an offer I would model replies to an offer as a completely seperate resource.

In Rails How to display errors in my comment form after I submit it?

I have a very straight-forward task to fulfil --- just to be able to write comments under posts and if the comments fail validation display error messages on the page.
My comment model uses a gem called Acts_as_commentable_with_threading, which creates a comment model after I installed.
On my post page, the logic goes like this:
Posts#show => display post and a form to enter comments => after the comment is entered, redisplay the Post#show page which has the new comment if it passes validation, otherwise display the error messages above the form.
However with my current code I can't display error messages if the comment validation fails. I think it is because when I redisplay the page it builds a new comment so the old one was erased. But I don't know how to make it work.
My codes are like this:
Comment.rb:
class Comment < ActiveRecord::Base
include Humanizer
require_human_on :create
acts_as_nested_set :scope => [:commentable_id, :commentable_type]
validates :body, :presence => true
validates :first_name, :presence => true
validates :last_name, :presence => true
# NOTE: install the acts_as_votable plugin if you
# want user to vote on the quality of comments.
#acts_as_votable
belongs_to :commentable, :polymorphic => true
# NOTE: Comments belong to a user
belongs_to :user
# Helper class method that allows you to build a comment
# by passing a commentable object, a user (could be nil), and comment text
# example in readme
def self.build_from(obj, user_id, comment, first_name, last_name)
new \
:commentable => obj,
:body => comment,
:user_id => user_id,
:first_name => first_name,
:last_name => last_name
end
end
PostController.rb:
class PostsController < ApplicationController
before_action :authenticate_user!, except: [:index, :show]
def show
#post = Post.friendly.find(params[:id])
#new_comment = Comment.build_from(#post, nil, "", "", "")
end
end
CommentsController:
class CommentsController < ApplicationController
def create
#comment = build_comment(comment_params)
respond_to do |format|
if #comment.save
make_child_comment
format.html
format.json { redirect_to(:back, :notice => 'Comment was successfully added.')}
else
format.html
format.json { redirect_to(:back, :flash => {:error => #comment.errors}) }
end
end
end
private
def comment_params
params.require(:comment).permit(:user, :first_name, :last_name, :body, :commentable_id, :commentable_type, :comment_id,
:humanizer_answer, :humanizer_question_id)
end
def commentable_type
comment_params[:commentable_type]
end
def commentable_id
comment_params[:commentable_id]
end
def comment_id
comment_params[:comment_id]
end
def body
comment_params[:body]
end
def make_child_comment
return "" if comment_id.blank?
parent_comment = Comment.find comment_id
#comment.move_to_child_of(parent_comment)
end
def build_comment(comment_params)
if current_user.nil?
user_id = nil
first_name = comment_params[:first_name]
last_name = comment_params[:last_name]
else
user_id = current_user.id
first_name = current_user.first_name
last_name = current_user.last_name
end
commentable = commentable_type.constantize.find(commentable_id)
Comment.build_from(commentable, user_id, comment_params[:body],
first_name, last_name)
end
end
comments/form: (this is on the Posts#show page)
<%= form_for #new_comment do |f| %>
<% if #new_comment.errors.any? %>
<div id="errors">
<h2><%= pluralize(#new_comment.errors.count, "error") %> encountered, please check your input.</h2>
<ul>
<% #new_comment.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<% end %>
I would instead use nested routes to create a more restful and less tangled setup:
concerns :commentable do
resources :comments, only: [:create]
end
resources :posts, concerns: :commentable
This will give you a route POST /posts/1/comments to create a comment.
In your controller the first thing you want to do is figure out what the parent of the comment is:
class CommentsController < ApplicationController
before_action :set_commentable
private
def set_commentable
if params[:post_id]
#commentable = Post.find(params[:post_id])
end
end
end
This means that we no longer need to pass the commentable as form parameters. Its also eliminates this unsafe construct:
commentable = commentable_type.constantize.find(commentable_id)
Where a malicous user could potentially pass any class name as commentable_type and you would let them find it in the DB... Never trust user input to the point where you use it to execute any kind of code!
With that we can start building our create action:
class CommentsController < ApplicationController
before_action :set_commentable
def create
#comment = #commentable.comments.new(comment_params) do |comment|
if current_user
comment.user = current_user
comment.first_name = current_user.first_name
comment.last_name = current_user.last_name
end
end
if #comment.save
respond_to do |format|
format.json { head :created, location: #comment }
format.html { redirect_to #commentable, success: 'Comment created' }
end
else
respond_to do |format|
format.html { render :new }
format.json { render json: #comment.errors, status: 422 }
end
end
end
private
# ...
def comment_params
params.require(:comment).permit(:first_name, :last_name, :body, :humanizer_answer, :humanizer_question_id)
end
end
In Rails when the user submits a form you do not redirect the user back to the form - instead you re-render the form and send it as a response.
While you could have your CommentsController render the show view of whatever the commentable is it will be quite brittle and may not even provide a good user experience since the user will see the top of the post they where commenting. Instead we would render app/views/comments/new.html.erb which should just contain the form.
Also pay attention to how we are responding. You should generally avoid using redirect_to :back since it relies on the client sending the HTTP_REFERRER header with the request. Many clients do not send this!
Instead use redirect_to #commentable or whatever resource you are creating.
In your original code you have totally mixed up JSON and HTML responses.
When responding with JSON you do not redirect or send flash messages.
If a JSON POST request is successful you would either:
Respond with HTTP 201 - CREATED and a location header which contains the url to the newly created resource. This is preferred when using SPA's like Ember or Angular.
Respond with HTTP 200 - OK and the resource as JSON in the response body. This is often done in legacy API's.
If it fails do to validations you should respond with 422 - Unprocessable Entity - usually the errors are rendered as JSON in the response body as well.
Added.
You can scrap your Comment.build_from method as well which does you no good at all and is very idiosyncratic Ruby.
class PostsController < ApplicationController
before_action :authenticate_user!, except: [:index, :show]
def show
#post = Post.friendly.find(params[:id])
#new_comment = #post.comments.new
end
end
Don't use line contiuation (\) syntax like that - use parens.
Don't:
new \
:commentable => obj,
:body => comment,
:user_id => user_id,
:first_name => first_name,
:last_name => last_name
Do:
new(
foo: a,
bar: b
)
Added 2
When using form_for with nested resources you pass it like this:
<%= form_for([commentable, comment]) do |f| %>
<% end %>
This will create the correct url for the action attribute and bind the form to the comment object. This uses locals to make it resuable so you would render the partial like so:
I'm assuming your form_for submits a POST request which triggers the HTML format in CommentsController#create:
def create
#comment = build_comment(comment_params)
respond_to do |format|
if #comment.save
make_child_comment
format.html
format.json { redirect_to(:back, :notice => 'Comment was successfully added.')}
else
format.html
format.json { redirect_to(:back, :flash => {:error => #comment.errors}) }
end
end
end
So, if #comment.save fails, and this is an HTML request, the #create method renders create.html. I think you want to render Posts#show instead.
Keep in mind that if validations fail on an object (Either by calling save/create, or validate/valid?), the #comment object will be populated with errors. In other words calling #comment.errors returns the relevant errors if validation fails. This is how your form is able to display the errors in #new_comment.errors.
For consistency, you'll need to rename #new_comment as #comment in the posts#show action, otherwise you'll get a NoMethodError on Nil::NilClass.
TL;DR: You're not rendering your form again with your failed #comment object if creation of that comment fails. Rename to #comment in posts, and render controller: :posts, action: :show if #comment.save fails from CommentsController#create
I have figured out the answer myself with the help of others here.
The reason is that I messed up with the JSON format and html format (typical noobie error)
To be able to display the errors using the code I need to change two places ( and change #comment to #new_comment as per #Anthony's advice).
1.
routes.rb:
resources :comments, defaults: { format: 'html' } # I set it as 'json' before
2.
CommentsController.rb:
def create
#new_comment = build_comment(comment_params)
respond_to do |format|
if #new_comment.save
make_child_comment
format.html { redirect_to(:back, :notice => 'Comment was successfully added.') }
else
commentable = commentable_type.constantize.find(commentable_id)
format.html { render template: 'posts/show', locals: {:#post => commentable} }
format.json { render json: #new_comment.errors }
end
end
end

Create rails record from two ids

The functionality I'm trying to build allows Users to Visit a Restaurant.
I have Users, Locations, and Restaurants models.
Locations have many Restaurants.
I've created a Visits model with user_id and restaurant_id attributes, and a visits_controller with create and destroy methods.
Thing is, I can't create an actual Visit record. Any thoughts on how I can accomplish this? Or am I going about it the wrong way.
Routing Error
No route matches {:controller=>"restaurants", :location_id=>nil}
Code:
Routes:
location_restaurant_visits POST /locations/:location_id/restaurants/:restaurant_id/visits(.:format) visits#create
location_restaurant_visit DELETE /locations/:location_id/restaurants/:restaurant_id/visits/:id(.:format) visits#destroy
Model:
class Visit < ActiveRecord::Base
attr_accessible :restaurant_id, :user_id
belongs_to :user
belongs_to :restaurant
end
View:
<% #restaurants.each do |restaurant| %>
<%= link_to 'Visit', location_restaurant_visits_path(current_user.id, restaurant.id), method: :create %>
<% #visit = Visit.find_by_user_id_and_restaurant_id(current_user.id, restaurant.id) %>
<%= #visit != nil ? "true" : "false" %>
<% end %>
Controller:
class VisitsController < ApplicationController
before_filter :find_restaurant
before_filter :find_user
def create
#visit = Visit.create(params[:user_id => #user.id, :restaurant_id => #restaurant.id])
respond_to do |format|
if #visit.save
format.html { redirect_to location_restaurants_path(#location), notice: 'Visit created.' }
format.json { render json: #visit, status: :created, location: #visit }
else
format.html { render action: "new" }
format.json { render json: #visit.errors, status: :unprocessable_entity }
end
end
end
def destroy
#visit = Visit.find(params[:user_id => #user.id, :restaurant_id => #restaurant.id])
#restaurant.destroy
respond_to do |format|
format.html { redirect_to location_restaurants_path(#restaurant.location_id), notice: 'Unvisited.' }
format.json { head :no_content }
end
end
private
def find_restaurant
#restaurant = Restaurant.find(params[:restaurant_id])
end
def find_user
#user = current_user
end
end
I see a lot of problems here. The first is this line of code in your VisitController's create action (and identical line in your destroy action):
#visit = Visit.create(params[:user_id => #user.id, :restaurant_id => #restaurant.id])
params is a hash, so you should be passing it a key (if anything), not a bunch of key => value bindings. What you probably meant was:
#visit = Visit.create(:user_id => #user.id, :restaurant_id => #restaurant.id)
Note that you initialize #user and #restaurant in before filter methods, so you don't need to access params here.
This line of code is still a bit strange, though, because you are creating a record and then a few lines later you are saving it (if #visit.save). This is redundant: Visit.create initiates and saves the record, so saving it afterwards is pretty much meaningless. What you probably want to do is first initiate a new Visit with Visit.new, then save that:
def create
#visit = Visit.new(:user_id => #user.id, :restaurant_id => #restaurant.id)
respond_to do |format|
if #visit.save
...
The next thing I notice is that you have not initiated a #location in your create action, but you then reference it here:
format.html { redirect_to location_restaurants_path(#location), notice: 'Visit created.' }
Since you will need the location for every restaurant route (since restaurant is a nested resource), you might as well create a method and before_filter for it, like you have with find_restaurant:
before_filter :find_location
...
def find_location
#location = Location.find(params[:location_id])
end
The next problem is that in your view your location_restaurant_path is passed the id of current_user and of restaurant. There are two problems here. First of all the first argument should be a location, not a user (matching the order in location_restaurant_path). The next problem is that for the _path methods, you have to pass the actual object, not the object's id. Finally, you have method: :create, but the method here is referring to the HTTP method, so what you want is method: :post:
link_to 'Visit', location_restaurant_visits_path(#location, restaurant.id), method: :post
You'll have to add a find_location before filter to your RestaurantController to make #location available in the view here.
There may be other problems, but these are some things to start with.
location_id is nil and the path definition doesn't say (/:location_id) forcing a non-nil value there in order to route to that path; create a new route without location_id if you can derive it from a child's attribute (i.e. a restaurant_id refers to a Restaurant which already knows its own location_id).

Rendering new form for a nested Ruby on Rails Resource

In my Ruby on Rails application, each group has_many :expenses. I have nested my routes, so expenses are entered only as child entities of their parent groups. Here's an excerpt from routes.rb.
resources :groups do
resources :expenses
end
I cannot figure out how to render the 'new' action in the case of an expense not saving when it is submitted through /groups/:group_id/expenses/new. In my expenses_controller.rb, here is how the create action is defined:
def create
#expense = Expense.new(params[:expense])
#expense.group_id = params[:group_id]
if #expense.save
redirect_to group_expense_path(#expense.group.id, #expense.id)
else
render 'new'
end
end
Everything works fine if I satisty expense validation and #expense.save winds up working. However, when it fails and the code tries to render 'new' I get:
undefined method `expenses_path' for #<#<Class:0x007fd408b1fd58>:0x007fd408f21ca8>
So, I am assuming I have something about my nested routing wrong. How do I return the user to the new form but still display to him/her through the flash[] params the errors with the data they originally attempted to submit?
The problem is that #group is not initialized
So in your controller just do
#expense = Expense.new(params[:expense])
#group = Group.find(params[:group_id])
#expense.group_id = #group.id
Looks like you need to explicitly specify the url for form_for in your view.
Something likeā€¦
<%= form_for #expense, :url => group_expenses_path(#group.id) do |f| %>
...
<% end %>
In your <%= form_for %> you have used #group for url, because expenses belongs_to groups. But inside your create action in the controller you have not defined what is #group, so first you should define it as:
#expense = Expense.new(params[:expense])
#group = Group.find(params[:group_id])
#expense.group_id = #group.id
Also I would suggest to use respond_to in your controller:
respond_to do |format|
if #expense.save
format.html { redirect_to group_expense_path(#group.id, #expense.id), :notice => "Any msg you want" }
else
format.html { render :action => "new" }
end
end
All of these are in your create action inside the controller.
Also for different rendering methods look up: http://guides.rubyonrails.org/layouts_and_rendering.html
Hope this helps!

How to stay at same url when validation fails

I'm using Ruby on Rails 2.3.8 and I've got a registration form in which I receive a parameter as follows: /registration/4, which 4 is the id of a user who recommended the user that is about to register in the website.
The problem is that if the validation fails when the user submits the registation (the form renders to the controller users, action create_particular) the site will redirect to /users/create_particular, and therefore I lose the parameter with value 4 that I had before. Besides, I want the user to stay at the same url, which is /registration/4
How can I do that?
Then you should rewrite your create method. You should use redirect_to :back instead of render :action
UPD
def new
#word = Word.new(params[:word])
#word.valid? if params[:word]
end
def create
#word = Word.new(params[:word])
if #word.save
redirect_to #word
else
redirect_to new_word_path(:word => params[:word] )
end
end
Looks quite dirty, but this is just a scratch
UPD 2
This is really not the best solution, but it works
# routes.rb
match 'words/new' => 'words#create', :via => :post, :as => :create_word
# words_controller
def new
#word = Word.new
end
def create
#word = Word.new(params[:word])
respond_to do |format|
if #word.save
format.html { redirect_to(#word, :notice => 'Word was successfully created.') }
else
format.html { render :action => "new" }
end
end
end
# views/words/new.html.erb
<%= form_for(#word, :url => create_word_path) do |f| %>
...
<% end %>
Submit to the current URI (e.g. action=""). When the submission is valid, redirect. POST->Redirect->GET is a good habit.
From the top of my head:
Edit your controller (registrations_controller.rb file). Create method by default contains following piece of code:
if #registration.save
format.html { }
format.xml { }
else
format.html { }
format.xml { }
end
Add redirect_to (:back) between brackets to else format.html{}
Ok I solved the problem by doing the following:
1) I created two routes with the same path, but with different conditions method (one it's post and the other one is set to get)
2) I changed the form in order to post to the POST action defined above
3) I added render => :my_action when the validation fails
So that's pretty much it.
Thanks anyway for all your help.
Hidden field. That user ID param has a name by which you extract it in your controller, right? So just put that value in a hidden field of the same name, then it will survive a round-trip.
For example:
<%= hidden_field_tag :referring_user_id, params[:referring_user_id] %>

Resources