Rails 3.2: A Nested-Form Demo, Part 2: Accelerate to Attack Speed!
Overview
In our previous post, our intrepid heroes were hurtling headlong into the trenches of the Starfighter Recognition Guide. They maneuvered through the superstructure of the application, setting up a relationship between themselves (the Pilots) and the Ships they fly. In this post, our ace Rebel pilots will make their approach to the surface of our application.
The Controller
Because all of the heavy lifting is being handled by the ActiveRecord configuration defined in our domain model, the controller for the Ship model is pretty standard fare. As a quick refresher, here’s what the create method in the ships_controller looks like:
def create
@ship = Ship.new(params[:ship])
if @ship.save
redirect_to(ships_path, :notice => "The #{ @ship.name } ship has been saved successfully.")
else
render(:new, :error => @ship.errors)
end
end
If you’re interested in looking at the controller in its entirety, you can check it out here.
The Helper Methods
Taking a cue from the Railscast, I’ve added a couple of helper methods to make my life a little easier:
app/helpers/application_helper.rb:
module ApplicationHelper
def link_to_remove_fields(name, f, options = {})
f.hidden_field(:_destroy) + link_to_function(name, "remove_fields(this)", options)
end
def link_to_add_fields(name, f, association, options = {})
new_object = f.object.class.reflect_on_association(association).klass.new
fields = f.fields_for(association, new_object, :child_index => "new_#{ association }") do |builder|
render(association.to_s.singularize + "_fields", :f => builder)
end
link_to_function(name, "add_fields(this, \"#{ association }\", \"#{ escape_javascript(fields) }\")", options)
end
end
The code above is lifted pretty much verbatim from the Railscast, but I still think it’s worth going over:
link_to_remove_fields:This method will create a hidden_destroyfield (which tells us whether or not the record should be deleted) and a hyperlink that will invoke a javascript method to update our_destroyfield.link_to_add_fields:This method will:- Create a new instance of our association object (a new
Pilotin this case). - Builds a form that we can use to edit our new
Pilotobject. - The
:child_indexwill be a placeholder that will be replaced by a unique value generated in javascript (more on that in a minute). - Build a hyperlink containing a form for our
Pilotobject.
- Create a new instance of our association object (a new
The fields_for Method
Let’s take a look at what’s going on with the second and third lines of link_to_add_fields (lines 8 and 9 in the code shown above). We’re using the fields_for method to build the Pilot input fields for us. At a high level, the fields_for method allows us to build an HTML form without the <form> tags. That means we can put our fields in the “parent” form without any problems.
The fields_for method takes a few parameters:
record_name: The name of the type of record we want to create. In our case, we’ll pass “Pilots” for this parameter.record_object: An instance of the object we want to add/edit. In our case, this will be aPilotobject. We created a newPilotobject on the previous line of ourlink_to_add_fieldsmethod (line 7 in the code shown above).options: Any options that we might want to pass to thefields_formethod. In our case, we want a way to uniquely identify eachPilotwe create. So, we use a:child_index, and set it to a “placeholder”. For this specific example, our placeholder text will be “new_pilots”. This placeholder will be replaced with a unique identifier when the form is shown (i.e. when theadd_fieldsjavascript method is called).
In our fields_for block, we’re asking it to render our _pilot_fields.html.erb partial view. Observe the second parameter, :f => builder is the parent form (i.e. the form that contains the input fields for the Ship we’re working with). This means that the Pilot fields rendered in our call to fields_for will be a part of the “Add a Ship” form, which is exactly what we want.
At this point, our fields variable should look like a bit like this (tidied up so it can actually be read):
Add a Pilot
In the last line of the link_to_add_fields method (line 12 in the code above), we hand off the contents of the fields variable to the link_to_function method. The link_to_function method is setting up a link that will call our add_fields javascript method (described in the next section). The fields value will be used as the content parameter of the add_fields method.
The Javascript
What’s a web app without a little javascript? Our helpers shown above make use of the Rails built-in link_to_function helper. The link_to_function will be used to call the javascript methods shown below.
app/assets/javascripts/application.js:
function remove_fields(link) {
$(link).prev("input[type=hidden]").val("1");
$(link).closest(".fields").hide();
}
function add_fields(link, association, content) {
var new_id = new Date().getTime();
var regex = new RegExp("new_" + association, "g");
$(link).parent().after(content.replace(regex, new_id));
$('#new-pilot-fields').modal('show');
}
Again, the code shown above comes pretty much verbatim from the Railscast. The remove_fields function is pretty straightforward:
- Find the hidden input field that comes before our “Remove” (our
_destroyfield) link and set it’s value to 1 (i.e.true, meaning we want ActiveRecord to delete this record for us). - Find the closest element that has a
class="fields"(in our case, this will be a<TR>element) and hide it. Presto! As far as the UI is concerned, we’ve deleted aPilot!
The add_fields function is a little more involved, but there’s nothing too fancy:
- Generate an arbitrary unique id based on the current time.
- Build a regular expression that will search for “new_” + [whatever the name of our
associationis] (“new_pilots” in our case). - Search through our
content(the string that represents ourPilotform) and replace “new_pilots” with the unique id we generated in Step 1. - Add the
Pilotform to the DOM.- To be very clear, the HTML shown in the description of the
fields_formethod will be added to our Add a Ship screen (which we haven’t seen yet, but trust me – it’s on its way).
- To be very clear, the HTML shown in the description of the
- Display the
Pilotform as a modal popup.- The
modalmethod comes from Twitter Bootstrap.
- The
Now, when a user clicks our “Add a Pilot” link (which we’ll see in the next article), a modal form with our Pilot input fields will appear. Hooray!
Summary: Cut the Chatter, Red 2!
We’re bouncing through the magnetic field with our deflectors on. While we haven’t started our attack run on the views yet, our approach is set up – the code in this post will make setting up our views a whole lot easier. In our next installment, we’ll switch on our targeting computers and attack the views. Before they know it, our Rebels will have a full-fledged Starfighter Recognition Guide with which to record their exploits and tales of derring-do.
Stay on target!
Leave a comment
Recent Posts
- Gonzo’s Puppet Journey: Updating an Existing Package on Solaris 10 Using Puppet 2.7
- iOS Unit Testing With OCMock
- Why Stakeholders Need To Be Involved In Scrum
- NuGet Config File Transformation Causes Duplicate Entries On Update
- Load Testing with Locust on Windows
- Writing A Custom LINQ Provider With Re-linq
- AutoMapper Profile Organization
- Rails 3.2: A Nested-Form Demo Part 4: Switch to Targeting Computer!
- SharpRepository: Configuration
- Rails 3.2: A Nested-Form Demo, Part 3: We’re Starting Our Attack Run!
- Rails 3.2: A Nested-Form Demo, Part 2: Accelerate to Attack Speed!
- Rails 3.2: A Nested-Form Demo, Part 1: All Wings Report In!
- iOS Behind the Curve
- Distributed Transaction Coordinators, Port 135, and Firewalls – Oh My!
- SharpRepository: Getting Started
- Find Performance Problems Using JMeter, MySQL and Xdebug/Webgrind
- Taming Hot Key Context Shifting When Running A Windows VM In Virtualbox On OSX
- Integrating Twitter’s Bootstrap Into Your Project
- Mobile payments, tags and more using NFC
- Stress Pig
- Dear Client Services, What Works?
- What Would Steve Do?
- Still Using Fiddler to Test & Debug Your REST Services?
- Write-through and Generational Caching Make a Great Team
- Thinking Recursively
- Development Incentives, What’s the Payoff?
- How do you like them Apples?
- “Optional” Software Development Practices Series — Code Review
- Adding Images to Select Lists in MVC3
- “Optional” Software Development Practices Series
- You Get What You Pay For…
- Outsourcing Safety Tips
- Facebook IPO
- The Ballad of Tim Toady
- The Little Schemer
- Newsflash: Mom leaves tech job at 5p.m.
- Flashback!
- I <negative_emotion> Windows 8!
- Prefix vs. Postfix Increment and Decrement Operators in C++
- Corporate videos: viral boon or epic fail?
- Recruitin’ Time!
- Reference vs. pointer parameters in C++
- The IE8 "hover" Bug: The Most Awesome IE Bug Ever?
- When is perfect perfect enough?
- SOPA/PIPA: Anti-Censorship Protest or Techies Revenge?
- A Decade of Fairway
- Handling Session Timeout Gracefully
- Generating Software Diagrams
- The Audacity of Nope
- The Origins of Culture
- Scrum Overview in Prezi – not another boring slideshow
- Numbers don’t lie: LinkedIn Statistics
- What is your favorite software development tool?
- Best Practices for Selecting Onshore, Nearshore or Offshore Information Technology Outsourcing (ITO) Providers
- Sign of the Times
- Advantages and Risks of Offshoring, Nearshoring or Onshoring
- Does Outsourcing Mean Offshoring?
- Too little, too late?
- New Favorite Lunch Spot
- Why should I care about functions as first-class citizens?
- PHP Remote Debugging with XDebug and NetBeans
- Installing SubText with Web PI
- ROI Primer
- Learn Domain-Driven Design
- Learn Behavior-Driven Development
- Mario Kart Tournament
- F# in 90 Seconds
- Website Vulnerabilities
- Scrum Overview
- Language Club
- Top 12 Favorite Podcasts Ever…
- Fairway Dart Tournament
- Learn Lean Software Development and Kanban Systems
- Android – Eclipse Quick Start
- Learn Functional Programming
- Backup & Restore Strategy
- Smartphone Screens – Another Wireless Variable
- Wireless Application Market
- Head First AOP





