Rails validating with two models - ruby-on-rails

Ok, bit confused on how to solve this issue.
I have one form and two models. Here is my form:
<% if #booking.errors.any? %>
<% #booking.errors.full_messages.each do |msg| %>
<p class="error"><%= msg %></p>
<% end %>
<% end %>
<% if #guest.errors.any? %>
<% #guest.errors.full_messages.each do |msg| %>
<p class="error"><%= msg %></p>
<% end %>
<% end %>
<%= form_for :booking, url: bookings_path do |f| %>
<%= label_tag :email, "Guest's Email Address" %>
<%= text_field_tag :email %>
<%= f.label :nights, "Nights" %>
<%= f.text_field :nights %>
<%= f.label :nights, "People" %>
<%= f.text_field :people %>
<%= f.label :nights, "Arrival Date" %>
<%= f.text_field :arrival %>
<% end %>
As you can see, the email field isn't part of the form builder. The email address will be used to create a new Guest record if the email doesn't already exist. Once I have the ID of the guest then the booking record can be made also.
Here is the code for create action in my BookingController - where the form is submitted to...
...
def create
accommodation = current_user.accommodation
guest = Guest.find_or_create_by(:email => params[:email])
#booking = accommodation.bookings.new(post_params.merge(:guest_id => guest.id))
if #booking.save
flash[:success] = 'The booking has been added successfully.'
redirect_to :controller => 'bookings', :action => 'index'
else
render 'new'
end
end
...
I do realise this question isn't new but I can't find a good solution anywhere to my problem - I want to be able to set the form up properly (if necessary) and validate all fields using the two models. Then I need to display the error messages. At the moment, my email is ignored during validation and I'm not sure what to do next.
Any help much appreciated.

It seems to me that the easiest way to is to validate the email in the controller itself and add any validation error to the booking variable. Something like this:
def create
accommodation = current_user.accommodation
guest = Guest.find_or_create_by(:email => params[:email])
#booking = accommodation.bookings.new(post_params.merge(:guest_id => guest.id))
if #booking.save
flash[:success] = 'The booking has been added successfully.'
redirect_to :controller => 'bookings', :action => 'index'
else
<% if params[:email].blank> %>
#booking.errors.add(:email, "can't be blank.")
<% end %>
#You can do the same thing for whatever other validation errors you have
render 'new'
end
end
Note: I did not test the code
This is probably not the best way possible but it gets the job done and is easy. You could use accept_nested_attributes_for but it seems to me a little bit unnecessary considering that you are only validating an email. Nevertheless, if you want to do it the cleanest way, stick with accept_nested_attributes_for.
EDIT
Actually, your code is the right track. You just made a syntax error. The real reason your guests errors are not being shown is that you used a local variable instead of a instance variable. Try this:
#guest = Guest.find_or_create_by(:email => params[:email])
Your error messages should be displayed with the code you already have
<% if #guest.errors.any? %>
<% #guest.errors.full_messages.each do |msg| %>
<p class="error"><%= msg %></p>
<% end %>
<% end %>
EDIT 2
In order to avoid a booking instance from beings saved in case the guest email is invalid you can do something like this:
if !#guest.errors.any? && #booking.save
flash[:success] = 'The booking has been added successfully.'
redirect_to :controller => 'bookings', :action => 'index'
else
Therefore, if the guest has any errors, the if statement will terminate before the #booking.save statement is executed.

You can try to do a transaction. If one of them is invalid, rails will do a rollback and you can render the errors.
Follow this question code, see if it helps.

Related

Create multiple records simultaneously on one form

After trying to accomplish the deceptively tricky task of building a form that enables the creation of multiple users at once, I wanted to pay it forward by sharing how I did it. Somewhat to my surprise, I was unable to find any SO answers that addressed this problem. Instead, the questions/answers kept addressing the scenario of creating a model as well as its associations on one form (using nested_attributes).
I must attribute the answer to a Ryan Bates' post from 2007 http://archive.railsforum.com/viewtopic.php?id=2696.
Your controller
def new
#owners = Array.new(3) { Owner.new } # set up any defaults here
end
def create
#owners = params[:owners].values.collect { |owner| Owner.new(owner) }
if #owners.all?(&:valid?)
#owners.each(&:save!)
redirect_to :action => 'index'
else
render :action => 'new'
end
end
Your View - new.erb.html
<% form_tag :action => 'create' do %>
<% #owners.each_with_index do |owner, index| %>
<% fields_for "owners[#{index}]", owner do |f| %>
First Name: <%= f.text_field :firstname %>
Last Name: <%= f.text_field :lastname %>
<% end %>
<% end %>
<%= submit_tag %>
<% end %>

Create method wont Create New Relationship in databse even though success flash appears

I'm trying to create a relationship in the database, but cant get it to work.
why wont the create action work? why can't it find the if statement? I think i havent written the create action correctly in the relationships controller but don't know how to fix it
When a user clicks 'add relationship'. the app should create the new relationship.
At the moment a user clicks 'add relationship' and this flash success msg appears:
You are now connected !
this is the information that comes across to the view where the flash success msg appears:
relationships.html.erb
relationship: !ruby/hash:ActionController::Parameters
followed_id: '3'
commit: Add Relationship
action: create
controller: relationships
but! a relationship is not created in the database
Here is the flow users take with comments to help explain:
users/index.html.erb:
# 1. USER SEARCHES FOR ANOTHER USER IN SYSTEM:
<%= form_tag users_path, :method => 'get' do %>
<p>
<%= text_field_tag :search, params[:search] %>
<%= submit_tag "Search", :name => nil %>
</p>
<% end %>
# 2. IF ANOTHER USER IS FOUND, THEIR NAME IS PRESENTED AND AN 'ADD RELATIONSHIP' BUTTON APPEARS:
<ul>
<% #users.each do |user| %>
<li>
<%= user.name %>
<%= render 'followed', followed: user %>
</li>
<% end %>
</ul>
_followed.html.erb:
# 3. ADD RELATIONSHIP BUTTON INFORMATION. THE USER CLICKS THE BUTTON, RELATIONSHIPS/CREATE IS THE NEXT PAGE. NO RELATIONSHIP IS CREATED
<%= form_for :relationship, url: relationships_path, method: :post do |f| %>
<div class="form form-actions">
<%= f.hidden_field :followed_id, value: followed.id %>
<%= f.submit "Add Relationship", class: "btn btn-primary" %>
</div>
<% end %>
relationships_controller:
def create
if params[:relationship] && params[:relationship].has_key?(:followed_id)
#followed = User.find_by(name: params[:active_relationship][:followed_id])
#active_relationship = current_user.active_relationships.new(followed: #followed)
#active_relationship.save
flash[:success] = "You are now connected !"
else
flash[:danger] = "Relationship required"
end
end
Looks like the error is in the second line in your create controller. In your if statement you have params[:relatioship] so I guess you would write
#followed = User.find_by(name: params[:relationship][:followed_id])
and I guess the followed_id shouldn't be a name
You should refactor the code like this:
if params[:relationship]
#followed = User.find(params[:relationship][:followed_id])
#active_relationship = current_user.active_relationships.new(followed: #followed)
if #active_relationship.save
flash[:success] = "You are now connected !"
else
flash[:danger] = "Relationship required"
end
else
flash[:danger] = "Something was wrong!!"
end
You might also use pry to debug codes. Links for reference http://pryrepl.org

Repopulating form on failed validation

I have a form for a user to create a question (in additon to user model, there's a question model, with nested answers) on their profile page. It submits from the users profile page /views/users/show.html.erb to the create action of the questions_controller.rb. If it doesn't validate, I think the default for Rails is to render the form(with the invalid information in the form for the user to edit). However, since I'm submitting the form for the question model from the users profile page the prepopulation isn't happening upon failed validation; the user is forced to enter all the information in the form again. Is there a way in this context to get the form on the users show page to fill out with the information that was entered prior to submission?
questions_controller
def create
#question = current_user.questions.build(params[:kestion])
if #question.save
redirect_to current_user, :notice => "Successfully created question."
else
###render :action => 'show'
redirect_to current_user
end
end
Update
I've changed the end of the create method too
Redirect ( : back ), :notice => "something went wrong.try again"
But I still can't get the form to populate, and the validation error messages aren't showing either, only the flash notice.
Update
The show method of the users controller creates the new Question (along with the user)
def show
#user = User.find(params[:id])
#question = Question.new
3.times {#question.answers.build}
end
The /views/users/show.html.erb
<%= form_for #question do |f| %>
<% if #question.errors.any? %>
<h2><%= pluralize(#question.errors.count, "error") %> prohibited this question
from being saved: </h2>
<ul>
<% #question.errors.full_messages.each do |msg| %>
<li> <%= msg %></li>
<% end %>
</ul>
<% end %>
<p>
<%= f.label :content, "Question"%>
<%= f.text_area :content, :class => 'span4', :rows => 1 %>
</p>
<p>
<%= f.label :link, "QuoraLink" %>
<%= f.text_field :link, :class => 'span4', :rows => 1 %>
</p>
<%= f.fields_for :answers do |builder| %>
<p>
<%= render 'answer_fields', :f => builder %>
</p>
<% end %>
<p><%= link_to_add_fields "Add Answer", f, :answers %></p>
<p><%= f.submit %></p>
<% end %>
the answer_fields partial rendered from the questions partial
<p class="fields">
<%= f.label :content, "Answer..." %>
<%= f.text_field :content, :class => 'span3', :rows => 1%>
<%= f.label :correctanswer, "Correct" %>
<%= f.check_box :correctanswer, :class => 'span1' %>
<%= link_to_remove_fields "remove answer", f %>
</p>
Currently, in views/users/show.rb you do
#question = Question.new
that creates an empty new question. Then you populate the forms with this empty model.
What you could do instead is:
if session[:question]
#question = #user.questions.new(session[:question])
session[:question] = nil
#question.valid? # run validations to to populate the errors[]
else
#question = Question.new
end
Now all what's left to do is populating session[:question] in your questions_controller before redirecting to :controller=>"users", :action=>"show". Something like:
if #question.save
redirect_to current_user, :notice => "Successfully created question."
else
session[:question] = params[:question]
redirect_to current_user
end
You may need to work on serialization/deserialization additionally for populating/using session[:question]. I didn't try to compile, so am not sure.
All this is needed because when you do redirect_to your processing of the user request ends, the user browser gets a redirect status code from your server and goes for another page, sending you a new request (which lands on the path, and eventually controller/action, to which you redirected to). So, as soon as you return from the request processing, all your variables are lost. For the next request you start from scratch.
The render :action => "show" approach (that was in the original scaffold and that you commented out) worked because you didn't return back to user but simply rendered the template with a specific name using the variables you already had in place (including #question, on which 'save' was called and failed, and thus internally validations were called and populated the errors object).
Actually, that reminded me that you may want to use another approach. Instead of passing parameters through session[] and redirecting to UsersController, you may want to populate all required variables and just render the view from that controller. Like below:
if #question.save
redirect_to current_user, :notice => "Successfully created question."
else
#user = current_user
render "users/show"
end
Firstly, the reason that using redirect_to instead of render doesn't repopulate the form is that when you redirect_to, the controller logic for the action is run, whereas using render ignored the controller logic.
So when you render :action => 'show' (the "default" behaviour), it renders show.html.erb, with #question set like this:
#question = current_user.questions.build(params[:kestion])
When you redirect_to current_user, it renders show.html.erb with #question set using the code in your show action:
#question = Question.new
3.times {#question.answers.build}
This is why you get a new (blank) form, instead of a pre-populated one.
Is it really that important that you use redirect_to? If it is, you'll need to get your show method to do the validation. For example, you could rewrite your show method to something like:
def show
#user = User.find(params[:id])
if params.has_key?(:kestion)
#question = #user.questions.build(params[:kestion])
else
#question = Question.new
3.times {#question.answers.build}
end
end
and then make your form point at that page, with something like:
<%= form_for(#question, url: users_path(#question.user) do |f| %>
...
<% end %>
(depending on how your routes are set up and named). Of course, by that point the whole thing become horribly un-RESTful, a bit of a mess, and definitely not the Rails way of doing things. (The other, worse option would be to redirect back and pass the params through a get query.) In my opinion, you lose a lot for a minor gain, and I'm not sure that I'd really recommend it.

Adding a user_id to a Message model

I currently have a simple app that includes user authentication through devise and a message model(the message model uses Jquery and Faye). Both are working fine independently, but I would like to add a user_id to the messages.
I have already updated the schema and models with the relationship, but I am having trouble inputting the necessary code to have the view and controller input the relationship into the db, while keeping the jquery working. I wasn't sure of the best way, but here I tried to create a hidden field that would pull the user_id, not sure if this is even possible. Here is the applicable code, any help is appreciated.
Message index.html.erb
<ul id="chat">
<%= render #messages %>
</ul>
<%= form_for Message.new, :remote => true do |f| %>
<div class="field">
<%= f.text_field :content %>
</div>
<div class="field">
<%= f.hidden_field :user_id %>
</div>
<div class="actions">
<%= f.submit "Send" %>
</div>
<% end %>
create.js.erb for messages
<% broadcast "/messages" do %>
$("#chat").append("<%= escape_javascript render(#user.message) %>");
<% end %>
$("#new_message")[0].reset();
Messages Controller
class MessagesController < ApplicationController
def index
if #authentications = current_user
#messages = Message.all
else
redirect_to authentications_url
flash[:notice] = "You need to sign in before answering questions"
end
end
def create
#user = User.find(params[:user_id])
#message = #user.message.create(params[:message])
end
end
I think you have everything you would need, but if not, let me know and I will be happy to provide it for you.
Thanks, everyone.
two things to correct,
1)use user association to create message instance in form(probably current_user if logged-in user create a message)
<%= form_for user.messages.new, :remote => true do |f| %> #// assuming its has many association
2) if it is has_many association then change association in create action
#message = #user.messages.create(params[:message])

RoR appended ?format= on form submit

I'm creating a little newsletter application, with 'double opt-in restrictions', when I simply fill in my form (subscription page) and submit the form I get redirected to my subscribed page (which is all normal) however my form appends a querystring to my action attribute of my form (http://localhost:3000/newsletter/subscribe?format=)
routes:
match 'newsletter/subscription' => 'newsletter_subscriptions#subscription'
post 'newsletter/subscribe' => 'newsletter_subscriptions#subscribe'
controller:
class NewsletterSubscriptionsController < ApplicationController
respond_to :html
# GET /newsletter/subscription
def subscription
respond_with (#subscription = NewsletterSubscription.new)
end
# POST /newsletter/subscribe
def subscribe
# If there's already an unconfirmed record with the submitted email, use that object otherwise create a new one based on the submitted email
sub_new = NewsletterSubscription.new
sub_new.email = params[:newsletter_subscription]['email']
sub_old = NewsletterSubscription.find_by_email_and_confirmed sub_new.email, 0
#subscription = sub_old || sub_new
if #subscription.save
Newsletter.delay.subscribed(#subscription) # with delayed_job
else
render :action => "subscription"
end
end
...
end
view (newsletter_subscription/subscription.html.erb):
<h1>New newsletter_subscription</h1>
<%= form_for(#subscription, :url => newsletter_subscribe_path(#subscription)) do |f| %>
<% if #subscription.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#subscription.errors.count, "error") %> prohibited this newsletter_subscription from being
saved:</h2>
<ul>
<% #subscription.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :email %>
<br/>
<%= f.text_field :email %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
PS: I would be pleased if someone could evaluate my ruby code please (posted above), I'm still learning a lot and would like to see some 'guidelines' or feedback, I think I still can learn a lot.
Try removing the #subscription argument you're passing into newsletter_subscribe_path. Since there isn't an :id in the route and it's a new object, passing it doesn't really make sense. I'm assuming that's what is being interpreted as the format.
<%= form_for(#subscription, :url => newsletter_subscribe_path) do |f| %>
As for improvements you can make to the code, the biggest thing I see is moving the old/new subscription logic into the model.
# in NewsletterSubscription
def self.with_email(email)
find_by_email_and_confirmed(email, 0) || new(:email => email)
end
# in controller
#subscription = NewsletterSubscription.with_email(params[:newsletter_subscription]['email'])
if #subscription.save
#...
Also respond_to and respond_with aren't really necessary here since you're just dealing with HTML views. You can remove that.

Resources