Multi-page Multi-Model Form in Rails - ruby-on-rails-3

I'm trying to create a multi-page form that uses multiple models. I have an applicant and this applicant has more than one address (one to many relationship).
I would like the first page to contain information about the applicant, and then the page after that to have the form for the address(es)
This is what I have at the moment:
applicant.rb
has_many :addresses, :dependent => :destroy
accepts_nested_attributes_for :addresses
address.rb
belongs_to :applicant
applicants_controller.rb:
def new
session[:applicant_params] ||= {}
#applicant = Applicant.new(session[:applicant_params])
2.times do
#addresses=#applicant.addresses.build
end
session[:address_params] = #addresses.attributes
end
def create
session[:applicant_params].deep_merge!(params[:applicant]) if params[:applicant]
session[:address_params] ||= params[:address]
#applicant = Applicant.new( session[:applicant_params] )
#applicant.addresses.new(session[:address_params])
if params[:forward_button] or params[:back_button]
#applicant.current_step = session[:applicant_step]
if params[:back_button]
#applicant.previous_step
else
#applicant.next_step
end
session[:applicant_step]=#applicant.current_step
render "new"
else
.....
In the new view:
<%= form_for #applicant do |f| %>
<%= render "#{#applicant.current_step}_step", :f => f %>
<p><%= f.submit "Continue", :name => "forward_button" unless #applicant.last_step? %> </p>
<p><%= f.submit "Back", :name => "back_button" unless #applicant.first_step? %></p>
<% end %>
#applicant.current_step will be either address_step or applicant_step, and these are below:
_applicant_step.html.erb
<div class="field">
<%= f.label :first_name %><br />
<%= f.text_field :first_name ,:width=>'10px', :size=>"20", :maxlength=>"20" %>
</div>
<div class="field">
<%= f.label :middle_name %><br />
<%= f.text_field :middle_name %>
</div>
....
_address_step.html.erb
<%= f.fields_for :addresses do |u| %>
<div class="field">
<%= u.label :address %><br />
<%= u.text_area :address , :cols=> 20, :rows=>5%>
</div>
<div class="field">
<%= u.label :telephone, "Telephone Number" %><br />
<%= u.text_field :telephone %>
</div>
...
And that's it. Now the problem I have is as follows, I want to save the address information as well as the applicant information. And I thought just by saying: #applicant.addresses.new then they will be included in the session information of the applicant, but they weren't (when I got the the address page, there were no fields!). So I created a new session variable to hold the address information. But I have a problem. Everytime I go from one page to the other (in the create action) a new address field (with all its attributes) is created and added to the form. So first I have one address, then I have 2 and so on. Am I going about this the wrong way? How can I have a multi-page form, with multiple models (that are related), and when going from one page to the next the data is not erased.. Until I eventually reach the last page where I could submit (and save) all the models..
I would be grateful if anyone could help..
Thank you.

You can certainly get this working, but (depending on the number of steps you need), it could get pretty convoluted.
Have you considered using a single new and create request on the server side, and using Javascript to break up the form into multiple steps on the client side? It seems like this could be a lot simpler. Your Rails application will behave more like a standard REST application, and you can break this up however you see fit on the client side.
I have not used it myself, but a jQuery plugin such as this one should do the trick:
http://thecodemine.org/

Related

undefined method `model_name' for Mailer - Rails 5

I am trying to make a contact me form in Rials 5 with the built in Mail features.
I put it together a lot like a regular controller and views with a Mailer as the model. But when I have it all written out I get the error:
undefined method `model_name' for #ContactMailer
Contact is the name of the Mailer.
The error comes up when I try to view the new.html.erb page with rails server running. It looks like this:
Screen Shot
Here is the code I am working with:
contacts_controller.rb
class ContactsController < ApplicationController
def new
#contact = ContactMailer.new
end
def create
#contact = ContactMailer.new(params[:name, :email, :message])
if #contact.deliver
flash[:success] = "Your message has been sent."
else
flash[:error] = "There was a problem sending your message."
render 'new'
end
end
And
new.html.erb
<%= form_for #contact do |f| %>
<div class="form-group">
<%= f.label :name %>
<%= f.text_field :name, class: 'form-control' %>
</div>
<div class="form-group">
<%= f.label :email %>
<%= f.email_field :email, class: 'form-control', required: true %></div>
<div class="form-group">
<%= f.label :message %>
<%= f.text_area :message, class: 'form-control' %>
</div>
<%= f.submit "Send", class: 'btn btn-default' %>
<% end %>
There is also the generated Mailer and views, but I have not changed anything with those.
From what I have been able to research the error could be that I have not initialized the #contact variable, but I believe that I have. And now I am lost. Any help would be appreciated.
As additional information:
My plan is for this contact form to send an email using send grid on Heroku.
You are trying to build a mailer.Thats not how they are used. They are not models.
Instead you would do something like:
ContactMailer.some_method_you_added_to_your_mailer(params[:name, :email, :message]).deliver_now
You don't use new on them.
Suggest you read over http://guides.rubyonrails.org/action_mailer_basics.html .

Append/Update params to an existing form using form_for

Fairly new to rails, meaning I believe I have most of the basics down, but definitely still a student of the art.
What I'm trying to accomplish: append params to an existing form using form_for.
So, I need to find the id for an existing form and pass a new hash to this form using the JobsController to append several columns of data.
Steps are to select the job number from a select_box, add any changes/notes for that job using text_fields/check_boxes, then submit. Also view used is a partial, found in views/jobs.
Current problem, despite several attempts, is:
Overriding the default 'post' method --tried several combinations of the following: method => 'put', :action => update, :controller => :jobs
class Job < ActiveRecord::Base
attr_accessible :note, :twgid, :padd, :lnkbr, :txtwr, :datewr, :pinmiss, :headch, :name, :nucopy, :minrend, :majrend, :lnkwr, :gmail, :hotmail, :aol, :outlook, :note, :linkwr
belongs_to :form
end
JobsController:
( scaffolded )
View: (jobs/_form.html.erb)
<%= form_for #job do |f| %>
<fieldset>
<div class="form-horizontal">
<div class="control-group">
<span class="control-label">
<%= f.label :name, "TWGID" %></span>
<%= f.select :name, options_from_collection_for_select(Job.all, :name, :name), :prompt => 'Select' %>
</fieldset>
<fieldset>
<div class="control-group">
<span class="control-label">
<%= f.label :padd, "Add Padding?" %></span>
<%= f.check_box :padd %>
<%= f.hidden_field, :updated, :value => '1' %>
<% end %>
##Routes##
get 'signup', to: 'users#new', as: 'signup'
get 'login', to: 'sessions#new', as: 'login'
get 'logout', to: 'sessions#destroy', as: 'logout'
resources :sessions
resources :users
resources :forms
resources :projects
resources :jobs
root :to => 'forms#index'
I'm not 100% sure what your trying to do but let me take a crack at it.
Well, the first thing I see is that in update_job on the first line you try grabbing a Change object and putting it into #change. The problem is that your using #change.id which obviously hasn't been set yet. If you are getting the id through params you will want to use:
#change = Change.find(params[:id])
Second, you are trying to update an attribute of Job but have a Change object. This will not work.
What I think your trying to do overall is to build a form that updates a change and will also update its associations (jobs in this case). A quick link on how to do this is here:
Form Helpers <--- Search for Nested Attributes Examples
Here is the code I would suggest:
<%= form_for #change do |f| %>
<%= f.fields_for :job do |j| %>
Updated : <%= j.check_box :updated %>
<% end %>
<% end %>
This will put the updated option in params[:change][:jobs_attributes] and when you send params[:change] into your update like so, #change.update_attributes(params[:change]) it will update your job as well.

Rails 3.2 HTML5 multiple file upload, Carrierwave and no Javascript

Currently using Rails 3.2 and Carrierwave.
I have multiple files setup, but it requires multiple file fields but I only want one file field. I will provide this as the default if the browser does not support the HTML5 multiple property.
Controller
def new
#ad = Ad.new
5.times { #ad.images.build } // provides multiple file fields in the view.
end
def create
ad = Ad.new(params[:ad])
user = User.find(session[:user_id])
if user.ads << ad
flash[:notice] = "Ad successfully saved."
redirect_to ad_listing_path(ad.id, ad.slug)
else
render :new, :alert => "Ad was not saved."
end
end
View
<%= f.fields_for :images do |a| %>
<% if a.object.new_record? %>
<%= a.file_field :image, :multiple => true %><br>
<% end %>
<% end %>
If 5.times { #ad.images.build } is providing my multiple fields, what is the proper way to display 1 file field that accepts multiple?
This seems to be a popular issue with no good answers, so I'm going to completely answer it here. Before I start I will mention that the code is available at https://github.com/mdchaney/multi, but follow along to see how to do this in the easiest way possible.
Before we go there, in HTML 5 a file input field can have the "multiple" attribute set. If it's set, the result is the same as having multiple file inputs of the same name. In Rails, setting "multiple: true" for the file input field built by the form builder will cause it to upload as an array of files.
<%= f.file_field :files, :multiple => true %>
becomes
<input id="model_files" multiple="multiple" name="model[files][]" type="file" />
where "model" is the name of your model. This input control will (in Chrome, at least) have a label of "Choose Files" instead of "Choose File".
CarrierWave cannot deal with this natively. It uses a single text field to store information about a single file, and some internal logic to determine where that file (and possibly its derivatives) are stored. It would be possible to hack it to put information for multiple files in a single text field, choosing an encoding with a set delimiter. This would require a lot of work and hacking on CarrierWave.
I don't care to hack CarrierWave, though, so the issue turns into the fact that having multiple files attached to one item is actually a one to many relationship, or in Rails terms a "has_many". So it's possible to add the files from the file input field to multiple attached records using a simple attribute writer.
With that, I present the simplest way to do this that uses an HTML 5 file input field with the multiple attribute set. There are ways to do it with jQuery and flash, but I am presenting this to show specifically how to do it with straight HTML 5.
In our sample, we will have a simple model for "uploads", each of which will have a name and any number of linked files, which will be stored in another model called linked_files (making it easy, right?). The linked_file will hold the original filename, provided content type, and of course the field for CarrierWave to store its information.
Let's create the scaffold for uploads and then just the model for linked_files:
rails g scaffold Upload name:string
rails g model LinkedFile upload:references filename:string mime_type:string file:string
With that done, we can set limits if we wish on the fields and add the "not null" constraint:
class CreateUploads < ActiveRecord::Migration
def change
create_table :uploads do |t|
t.string :name, limit: 100, null: false
t.timestamps
end
end
end
class CreateLinkedFiles < ActiveRecord::Migration
def change
create_table :linked_files do |t|
t.references :upload, null: false
t.string :filename, limit: 255, null: false
t.string :mime_type, limit: 255, null: false
t.string :file, limit: 255, null: false
t.timestamps
end
add_index :linked_files, :upload_id
end
end
Now, let's fix up the Upload model by adding a new attribute writer called "files":
class Upload < ActiveRecord::Base
has_many :linked_files, inverse_of: :upload, dependent: :destroy
accepts_nested_attributes_for :linked_files, reject_if: :all_blank, allow_destroy: true
validates_associated :linked_files
attr_accessible :name, :files, :linked_files_attributes
def files=(raw_files)
raw_files.each do |raw_file|
self.linked_files.build({filename: raw_file.original_filename, mime_type: raw_file.content_type, file: raw_file})
end
end
validates :name, presence: true, length: { maximum: 100 }
end
Most of that is the normal declarations for the Rails model. The only real addition here is the "files=" method, which takes a set of uploaded files in an array and creates a "linked_file" for each one.
We need a CarrierWave uploader:
class FileUploader < CarrierWave::Uploader::Base
storage :file
def store_dir
"uploads/#{model.class.to_s.underscore}/#{model.id}/#{mounted_as}"
end
end
This is the simplest uploader possible, you may wish to restrict the type of file uploaded or whatever. Now, the LinkedFile model:
class LinkedFile < ActiveRecord::Base
mount_uploader :file, FileUploader
belongs_to :upload, inverse_of: :linked_files
attr_accessible :file, :filename, :mime_type, :file_cache, :remove_file
validates :filename, presence: true, length: { maximum: 255 }
validates :mime_type, presence: true, length: { maximum: 255 }
end
And that has nothing special, only added :file_cache and :remove_file as accessible attributes for the file uploader.
We are now done except for the views. We really only have to change the form, but we'll also change the "show" to allow access to the uploaded files. Here's the _form.html.erb file:
<%= form_for(#upload, { multipart: true }) do |f| %>
<% if #upload.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#upload.errors.count, "error") %> prohibited this upload from being saved:</h2>
<ul>
<% #upload.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<% if f.object.linked_files.size == 0 -%>
<div class="field">
<%= f.label :files %><br />
<%= f.file_field :files, :multiple => true %>
</div>
<% else -%>
<fieldset>
<legend>Linked Files</legend>
<%= f.fields_for :linked_files do |lff| -%>
<div class="field">
<%= lff.label :filename %><br />
<%= lff.text_field :filename %>
</div>
<div class="field">
<%= lff.label :mime_type %><br />
<%= lff.text_field :mime_type %>
</div>
<div class="field">
<%= lff.label :file, 'File (replaces current selection)' %><br />
<%= lff.file_field :file %>
<%= lff.hidden_field :file_cache %>
</div>
<div class="field">
<%= lff.check_box :_destroy %>
Remove this file
</div>
<hr />
<% end -%>
</fieldset>
<% end -%>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
I have added two sections of code. If the #upload object has no "linked_files" associated with it, I simply show the multiple file input. Otherwise, I show each linked_file with all its information. It would be possible to add a "files" method to Upload and handle it that way, but doing so would lose the mime type across requests.
You can easily test this as the upload "name" is a required field. Start a server and go to http://127.0.0.1:3000/uploads to see the application. Click the "new" link, choose some files and hit "Create Upload" without providing a name. The next page will show all of your files now waiting. When you add a name everything is saved. Let's modify the "show" action to show the linked_files:
<p id="notice"><%= notice %></p>
<p>
<b>Name:</b>
<%= #upload.name %>
</p>
<p>
<b>Files:</b><br />
</p>
<table>
<thead><tr><th>Original Filename</th><th>Content Type</th><th>Link</th></tr></thead>
<tbody>
<% #upload.linked_files.each do |linked_file| -%>
<tr>
<td><%= linked_file.filename %></td>
<td><%= linked_file.mime_type %></td>
<td><%= link_to linked_file.file.url, linked_file.file.url %></td>
</tr>
<% end -%>
</tbody>
</table>
<%= link_to 'Edit', edit_upload_path(#upload) %> |
<%= link_to 'Back', uploads_path %>
In this I've simply added a header for "Files" and a table which shows all of them and provides a link for viewing. Nothing fancy, but it works.
If I were making this into a real application I would probably also provide a list of files or minimally a count of files on the uploads index page, also.
So that's it. Again, the entire test app is available at github if you want to download it, but I have put the entirety of my Rails generating statements and changes in this post.
Multiple uploads for one file field isn't really supported by HTML. You can get around it with some JavaScript plugins. Two that come to mind:
Uploadify
jQuery File Upload
Unfortunately, CarrierWave does not support the HTML5 multiple attribute (yet).
https://github.com/carrierwaveuploader/carrierwave/issues/984

Voting app in rails 3: how do I link to vote method?

To teach myself Rails, im building an extremely simple Voting app.
There are 2 models, Question and Option. Question has_many Options and Option belongs_to Question.
Using the standard scaffolding, I have reached a stage where you can add a question, view it, and add options to it and see these options.
What I would like to do now is add the code that increases an option.count value by one when clicking on a link. I have a vote_up method in the Option model:
class Option < ActiveRecord::Base
validates :text, :presence => :true
belongs_to :question
def vote_up
self.count += 1
end
end
My Options controller looks like:
class OptionsController < ApplicationController
def create
#question = Question.find(params[:question_id])
#option = #question.options.create(params[:option])
redirect_to question_path(#question)
end
end
My Question model looks like:
class Question < ActiveRecord::Base
validates :text, :presence => {:message => 'A question normally has text...'}
has_many :options, :dependent => :destroy
def vote
# Maybe the vote code will go here???
end
end
And my Question controller has the usual new, create, edit, destroy methods that the scaffold creates. V little customisation here.
My show.html.erb view where I would like to put the link to the vote method looks like:
<p id="notice"><%= notice %></p>
<p>
<b>Question <%= #question.guid %></b>:
<%= #question.text %>
</p>
<% if #question.options.count == 0 %>
<p>Shame! there are currently no options to vote on. Add some! </p>
<% elsif #question.options.count == 1 %>
<p>One option in a vote is a dictatorship... Maybe add some more?</p>
<% end %>
<h2>Options:</h2>
<% #question.options.each do |option| %>
<p>
<%= option.text %>: ** Link to vote here!
</p>
<% end %>
<h2>Add an option to vote on</h2>
<%= form_for([#question, #question.options.build]) do |f| %>
<div class="field">
<%= f.label :text %><br />
<%= f.text_field :text %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
<% if #question.options.count == 0 # Only show edit if no options saved. %>
<%= link_to 'Edit', edit_question_path(#question) %> |
<% end %>
<%= link_to 'Back', questions_path %>
So what I am trying to do is add a "vote" link next to each option that calls the vote_up method in the options model. This is probably laughably easy, but i've hit a wall and would really appreciate any help.
Also, any suggestions on how to do this better would be welcome!
Thanks
Simon
I think #oded-harth has showed a right way, but I have two remarks:
First of all, Rails is a beautiful language and is written to simplify our developer lives ;) When programming in Rails you must never forget that. With this in mind, I want to point you to "increment()" method. So you can simply up-vote without unnecessary += 1. To down-vote use decrement(). I believe you can use it like this: option.increment(:count)
Second, I think it's a little dirty to have a whole form for a simple vote action. You can use something like this
<%= link_to "Vote Up", :url => { :action => :vote_up, :option_id => option.id }, :method => :put %>
To make it work you'll have to set your route something like this:
resources :votes
put :vote_up
end
What I would do is make the vote_up method in the controller:
def vote_up
option = Option.find(params[:option_id])
option.count += 1
redirect (to where do you want...)
end
And in the view I would call that method this way:
<%= form_for( option, :url => { :action => "vote_up", :option_id => option.id} ) do |f| %>
<%= f.submit("vote up") %>
<% end %>

Rails - Using form_for and fields_for, how do you access the sub-object while in the fields_for block?

In my first rails app I'm trying to use form_for and fields_for to create a nested object form. So far so good, but I can't figure out how to access the sub-object while in the fields_for block. I've pre-populated a field in the sub-object with data that I want to show in the user instructions.
Models
Garage:
has_many :cars, :dependent => :destroy
accepts_nested_attributes_for :cars
Car:
belongs_to :garage
Garage Controller
def new
#garage = Garage.new
for i in 1..5
#garage.cars.build :stall_number => i
end
end
_form.html.erb
<%= form_for #garage do |f| %>
<%= f.label :title, "Garage Name" %><br />
<%= f.text_field :title %>
<% f.fields_for :cars do |builder| %>
<p>Enter license for car parked in stall: <%= car.stall_number %></p>
<%= f.label :license, "License #:" %><br />
<%= f.text_field :license %>
<%= end %>
<%= end %>
As you can see, inside the builder block for :cars, I want to show, in my user instructions, the field: car.stall_number (populated in my controller with an integer):
<p>Enter license for car parked in stall: <%= car.stall_number%></p>
I've tried a many different ideas: #car.stall_number, object.car.stall_number, etc. No joy. Multiple searches and a look at the fields_for source code haven't helped my understanding. I would appreciate any guidance.
Update: For clarification, per Dan's suggestion I have tried builder.stall_number but it results in a
NoMethodError: undefined method 'stall_number' for #<ActionView::Helpers::FormBuilder:0x00000102a1baf0>
I just dealt with this today myself.
You can access the object of the fields_for through:
builder.object
where builder is your fields_for form builder object. In your particular case, you can say:
<p>Enter license for car parked in stall: <%= builder.object.stall_number%></p>
That should do it for you!
The way you are trying is does not work because you want to access car without filling that variable for data.
I guess you want to have multiple blocks of stalls, where you can enter license plates. For each stall you will need your own fields_for.
I would suggest something like that:
<%= form_for #garage do |f| %>
<%= f.label :title, "Garage Name" %><br />
<%= f.text_field :title %>
<% for i in 1..5 %>
<% f.fields_for #garage.cars[i] do |builder| %>
<p>Enter license for car parked in stall: <%= builder.stall_number%></p>
<%= builder.label :license, "License #:" %><br />
<%= builder.text_field :license %>
<% end %>
<% end %>
<% end %>
Within the fields_for you need to use the form object you define there, in this case builder. Since the data there are not mapped to the outer form (f), but to the cars object (builder).