Rails form called from a another controller's action - ruby-on-rails-3

I have models User, Event, Rsvp. I want to have a Rsvp form appear in the show page of my Event model. A User can create many Events but I don't need to know which User create which Event (at least I don't think). A User can also rsvp to an Event. My models looks like so:
user.rb
has_many :rsvps
event.rb
has_many :rsvps
rsvp.rb
belongs_to :user
belongs_to :event
I think my RsvpsController should look like this:
class RsvpsController < ApplicationController
def new
#user = current_user
#event = Event.find(params[:id])
#rsvp = Rsvp.new
end
end
Now in my show page of an Event I want a form in the sidebar which lets Users rsvp to the Event. I have something like this but it's not quite right. I'm especially not sure what is the first argument that goes into form_for:
form_for [#user, #event, #rsvp], url: {controller: 'rsvps_controller', action: 'create' } do |f|
in my events/show.html.haml I try to render the Rsvp new action like this:
=render template: 'rsvps/new'
but this instead renders an event/new partial
EDIT
I think I know why this render is rendering an Event new partial. Inside Rsvps/new.html.haml I have render 'form'. I also have a _form.html.haml in my Events folder.
EDIT2
rsvps_controller.rb
def create
#rsvp = current_user.rsvps.build(params[:rsvp])
if #rsvp.save
flash[:success] = "You have RSVPed."
redirect_to Event.find(params[:event_id])
else
end
end

since you want to create the reservation for the current user, you should only pass the event to the rsvp create action. for the form, use
<%= form_for Rsvp.new do |f| %>
<%= f.hidden_field :event_id, value: #event.id %>
This should give you access to the event that the current user wants to rsvp. So in your create action of the Rsvp controller, you can build the reservation by using the following code
def create
#rsvp = current_user.rsvps.build params[:rsvp]
if #rsvp.save
...
end

Related

Ruby on Rails pass id to new create form

Ok, I've searched high and low, read tutorials, watched videos and I am still not getting any where with this. I've read similar questions here, but questions were more complex or lacked answers - so here goes...
I have models Account and Invoice. When showing an Account, I'd like a link to 'Create new invoice' which relates to that account. (Later I'd actually like a select field to choose an Account when creating an Invoice, but I'll leave that to another excruciation).
Here are my models...
Account:
class Account < ActiveRecord::Base
attr_accessible :name, :invoice
attr_accessible :name, :invoice
has_many :invoices
end
and Invoice:
class Invoice < ActiveRecord::Base
belongs_to :account
attr_accessible :amount_pretax, :amount_total, :date_sent, :project, :status, :tax, :account, :account_id
end
Now, in my /views/accounts/show.html.erb
<p id="notice"><%= notice %></p>
<p>
<b>Name:</b>
<%= #account.name %>
</p>
<%= link_to 'New Invoice', new_invoice_path(:account_id=>#account.id) %>
<%= link_to 'Edit', edit_account_path(#account) %> |
<%= link_to 'Back', accounts_path %>
So, what's happening is, when I click on the New Invoice link it shows the new form, with the account field populated with this weird text: #<Account:0x10fe16bc0> and then when I submit the form I get this error:
ActiveRecord::AssociationTypeMismatch in InvoicesController#create
with this statement: Account(#2281084000) expected, got String(#2267210740)
along with this:
app/controllers/invoices_controller.rb:45:in `new'
app/controllers/invoices_controller.rb:45:in `create'
This is what is in the Invoices Controller:
def new
#invoice = Invoice.new(:account_id => params[:account_id])
respond_to do |format|
format.html # new.html.erb
format.json { render :json => #invoice }
end
end
def create
#invoice = Invoice.new(params[:invoice])
....
end
The above is where I think I'm going wrong, but what to put this those lines is beyond me at the moment. I'm totally a beginner, any help to solve this functionality will surely teach me loads.
Thanks for your time.
When you click the New invoice link on the /views/accounts/show page, I suppose that you want that your new invoice belongs to this account.
So in your form, you don't have to let the user choose an account. You can for example replace the corresponding field by a hidden_field:
<%= f.hidden_field :account_id, :value => params[:account_id] %>
Also in the new action of your controller, replace #invoice = Invoice.new(:account_id => params[:account_id]) by #invoice = Invoice.new
Hope this helps.
you did not post the code of your form, but i guess that you are using a text-field for handling the account association. THIS IS WRONG!
if you use a text-field, then rails will try storing it as a string => Account(#2281084000) expected, got String(#2267210740)
you need to use some kind of relational field like a dropdown or whatever to select one of the accounts that are already there.
there are tons of good examples out there, this might help you: http://railscasts.com/episodes/102-auto-complete-association-revised

Link to new record with automatic belongs_to relationship?

In my Rails 3 application I have a simple model relationship which is as follows:
Animal Model
has_many :claims
Claim Model
belongs_to :animal
has_many :payments
Payments Model
belongs_to :claim
The claim table has a column called animal_id and the payments table has a column called claim_id.
I can successfully list all payments owned by a claim.
What I am trying to do is add a link within the animal show view that creates a new claim with the animal_id already set and the same with the claim show view for the new payment link.
From what I have read you can manually create a link_to with the controller and action set, like so:
Claim Show View
<%= link_to 'Create a Payment', {:controller => "payments", :action => "new", :claim_id => '#claim.id'} %>
This doesn't seem to work, although it does correctly link to the new payment page.
Is there a simpler way?
Off the top of my head, and untested, I think the following would work:
= link_to 'Create Payment', payments_path(:payment=>{:claim_id=>#claim.id}), :method => :post
Adding a hash inside the url helper is just going to put params on the url, which will be interpreted by rails the same way "normal" params posted from a form are.
This will be equivalent to submitting a new payment form, so it will redirect the same way that would. If you want it to be handled via ajax just add :remote => true, and be sure your the create action on your PaymentsController responds to javascript correctly.
Update: Read your question again, you want the new payment form with the claim_id already set?
Ok, there are many ways you could do this. If you pass params to the new action you can take the claim_id from the params, the same as I showed above for creating a record:
= link_to 'New Payment', new_payment_path(:claim_id=>#claim.id)
This should link you to /payments/new?claim_id=123
Then in your controller:
def new
#payment = Payment.new(:claim_id => params[:claim_id])
end
However, as was pointed out in the comments, the better way to do this is to nest payments under claims:
In your routes:
resources :claims do
resources :payments
end
In your PaymentsController:
before_filter :load_claim
def new
#payment = #claim.payments.build
end
def create
#payment = #claim.payments.build(params[:payment])
...
end
private
def load_claim
#claim = Claim.find(params[:claim_id])
end
In your link:
= link_to 'New Payment', new_claim_payment_path(#claim)
Just be aware that now your payment path helpers will need claims in them, ie:
= link_to 'Payment', claim_payment_path(#claim, payment)
Same thing for form helpers:
= form_for [#claim, #payment] ...

Adding ajax to Rails 3 form_for

I'm learning to program and got a form running in my Rails 3 app. Now I'm attempting to add ajax to the form so the page doesn't reload after submitting.
I've followed the numerous tutorials but can't quite seem to figure out how to bring it together. The form adds new Objects to the Profile through the following model:
class Profile < ActiveRecord::Base
has_many :objects
end
class Object < ActiveRecord::Base
belongs_to :profile
end
My form in views/profiles/_object_form.html.erb:
<%= form_for(#object, :remote => true) do |f| %>
<% end %>
Where the form and its created objects are rendered in my views/profiles/_about.html.erb:
<div id="newObjects">
<%= render :partial => 'object_form' %>
</div>
<div id="objectList">
<%= render :partial => 'object', :collection => #profile.objects, :locals => {:object_count => #profile.objects.length) %>
</div>
In my objects_controller.rb I have the following create action:
def create
#object = Object.new(params[:object].merge(:author_id => current_user.id))
respond_to do |format|
if #object.save!
format.html {redirect_to profile_path(#object.profile) }
format.js { render }
else
format.html { redirect_to #profile, :alert => 'Unable to add object' }
end
end
end
In views/objects/create.js.erb:
$('#objectList').append("<%= escape_javascript(render #profile.object)) %>");
So I have a form calling an action in another controller to which I want to add ajax. What happens at the moment is that I need to reload the profile to show the newly created object. What am I doing wrong?
CLARIFICATION: Other than the create action in the ObjectsController, I only reference #object once elsewhere. That's in the ProfilesController's show action:
def show
#profile = Profile.find(params[:id])
#superlative = #profile.superlatives.new`
end
Not sure if this is a full code snippet for your create action, but looks like you are trying to call render on an instance variable that doesn't exist... #profile is never set in the create method in the ObjectController...
Perhaps you meant to type $('#objectList').append("<%= escape_javascript(render #object)) %>");
Also noticed that in your existing code you're making a call to render #profile.object, but the Profile class has a has_many relationship with your Object class, so if that was the right code, then you should type render #profile.objects (plural, not singular).
But I would think you would likely want the code I mentioned above, since you are appending onto the list of objects, not rendering the list again?

Rails 3 model with accepts nested attributes not updating children

I've got a model setup where a user can create a quiz with many questions and many answers on each question
The models look like this:
model Page < AR::Base
end
model Quiz < Page
has_many :questions
accepts_nested_attributes_for :questions, :allow_destroy => true
end
model Question < AR::Base
belongs_to :quiz
has_many :answers
accepts_nested_attributes_for :answers, :allow_destroy => true
end
model Answer < AR::Base
belongs_to :question
end
And my form looks like this:
= form_for #quiz do |f|
f.fields_for :questions do |qf|
# fields omitted, have fields for id, content, etc
qf.fields_for :answers do |af|
# fields omitted, have fields for id, answer, etc
f.submit 'save'
Everything works wonderfully when I edit just the quiz or when I add new questions and answers, but when I edit existing questions and answers, the changes aren't persisted in the DB. I can see the correct nested parameters being sent into the controller and when inspected the #quiz after calling update_attributes it shows the updated questions and answers but they aren't being persisted after the page is updating.
I've never had this sort of issue before and am having trouble spotting the cause, can anyone share some insight?
Thanks!
As requested, controller code: (Quiz is an STI subclass of Page)
PagesController < ApplicationController
def update
#page = #section.pages.find(params[:id])
if #page.update_attributes(params[#page.type.downcase.underscore])
redirect_to online_course_section_pages_path(#online_course, #section), :notice => "Your page has been updated"
else
render :edit
end
end
end
EDIT:
Found the problem was because of using #page.type.downcase.underscore instead of #page.type.underscore.downcase so update attributes was being passed nil instead of the actual data
Found the problem was because of using #page.type.downcase.underscore instead of #page.type.underscore.downcase so update attributes was being passed nil instead of the actual data

How to redirect page after confirmation in Devise

Say a user clicks a link to a page that is protected. They are then redirected to a sign in screen where they can log in. If they do, then are successfully redirected to that page. But if they don't have an account they have to sign up. This is where things get tricky because I'm doing an email confirmation.
By clicking a link it creates a new session can I can't automatically redirect the user to that protected page. I'm trying to change this by putting in a reference to the redirect inside the confirmation link. I would like to do:
<%= link_to 'Confirm my account', confirmation_url(#resource, :confirmation_token => #resource.confirmation_token, :redirect_to => stored_location_for(#resource)) %>
But I can't figure out how to get access to stored_location_for (or if that is even the right location to get). It is defined in Devise::Controllers::Helpers, but it is an instance method so I can't do Devise::Controllers::Helpers.stored_location_for(…).
How do I get access to stored_location_for or what is the better way of doing this?
My goal is to do that and then in my custom ConfirmationsController define:
def show
if params[:redirect_to]
session["user_return_to"] = params[:redirect_to]
end
super
end
That should work right?
I figured it out. I'm not sure if this changes with the update Devise did yesterday in making Devise::Mailer put most of its functionality into a module. (See the code and ticket for more information).
Basically it boils down to not being able to access the session inside of a mailer view. Therefore you have to pass the redirect as a variable. Devise uses an after_create method on your resource (User in my case) which then sends the confirmation email. This meant I couldn't just pass the session variable directly to the mailer. Thus I feel like this is a pretty nasty work-around in order to get this functionality, but here is the code:
To get the redirect_to variable into the mailer you have to add a variable to the user, thus:
class User < ActiveRecord::Base
…
attr_accessor :return_to
…
end
Then you have to set that variable when you create the user for the first time.
I already had a custom controller setup for registration. (See Devise' Readme on how to set this up, or see #ramc's answer for direction). But it was relatively easy to do this part, I just added it to the parameters and let the rest take care of itself.
class RegistrationsController < Devise::RegistrationsController
def create
params[:user][:return_to] = session[:user_return_to] if session[:user_return_to]
…
super
end
end
Now the user has a variable return_to which is set. We just need to access that in the confirmation_instructions email. I've already rewritten part of confirmation_instructions.html.erb so inside there I just added:
<% if #resource.return_to %>
<%= link_to 'Confirm my account', confirmation_url(#resource, :confirmation_token => #resource.confirmation_token, :redirect_to => #resource.return_to) %>
<% else %>
<%= link_to 'Confirm my account', confirmation_url(#resource, :confirmation_token => #resource.confirmation_token) %>
<% end %>
(For those who are new to this, #resource is the variable Devise uses to define your user).
Now once the user clicks on that link we need to redirect them. #ramc's before filter works well for this:
class ConfirmationsController < Devise::ConfirmationsController
before_filter :set_redirect_location, :only => :show
def set_redirect_location
session[:user_return_to] = params[:redirect_to] if params[:redirect_to]
end
end
That will take care of the case where a new user goes to a protected page then signs up, clicks on the confirmation link and is properly redirected to the protected page.
Now we just need to take care of the case where a user does the above, but instead of clicking on the link, they try to go back to the protected page. In this case they are asked to sign-up/sign-in. They sign-in and then are asked to confirm their email and are given the option of resending the confirmation email. They put in their email and now we need to put the redirect_to variable in that new confirmation email.
To do this we need to modify the ConfirmationController, similarly to how we did the RegistrationController. This time we need to modify the create method. The way it works out of the box is to call a class method on the user called send_confirmation_instructions. We want to rewrite that method so we can pass the return_to variable into it.
class ConfirmationsController < Devise::ConfirmationsController
def create
self.resource = resource_class.send_confirmation_instructions(params[resource_name],session[:user_return_to])
if resource.errors.empty?
set_flash_message(:notice, :send_instructions) if is_navigational_format?
respond_with resource, :location => after_resending_confirmation_instructions_path_for(resource_name)
else
respond_with_navigational(resource){ render_with_scope :new }
end
end
end
The only thing different than what comes with Devise is that first line of create, we pass two variables in. Now we need to rewrite that method:
class User < ActiveRecord::Base
def self.send_confirmation_instructions(attributes={},redirect=nil)
confirmable = find_or_initialize_with_errors(confirmation_keys, attributes, :not_found)
confirmable.return_to = redirect if confirmable.persisted?
confirmable.resend_confirmation_token if confirmable.persisted?
confirmable
end
end
confirmable becomes an instance of User (the current user based on email). So we just need to set return_to.
That's it.
Looking at the way stored_location_for has been implemented in lib/devise/controllers/helpers.rb
def stored_location_for(resource_or_scope)
scope = Devise::Mapping.find_scope!(resource_or_scope)
session.delete("#{scope}_return_to")
end
It is possible to otherwise access it using session['user_return_to']. In your case, you would lose that session object because when the user clicks on the link from the confirmation mail, it might be a new session that is spawned.
You can implement whatever you have suggested as a before filter:
class Users::ConfirmationsController < Devise::ConfirmationsController
before_filter :set_redirect_location, :only => :show
def set_redirect_location
session["user_return_to"] = params[:redirect_to] if params[:redirect_to]
end
end
In addition to this, you will have to modify the route to make devise call your controller instead of its own confirmation controller.
devise_for :users,
:controllers => { :confirmations => 'users/confirmations'}
Hope this helps :)
Note: The code snippets are not complete and only contain relevant details.
From what I can see from the comments in the devise source code, all you need to do is implement the following in your registrations_controller.rb:
def after_inactive_sign_up_path_for(resource_or_scope)
session["user_return_to"]
end