In the forum, Gareth posted a question on how to build a simple newsletter, or release sign up form. So basically he wants to build a landing page that has a form, and you can type in some information like your name, email address, and it will collect that information and save it to a database record in your application. So today we're going to go talk about how to build one of those, and walk through the whole thought process of how you're supposed to go about doing this. Really, the first place we need to start is what data you want to save. So we want to start with the model, and we want to design the database table according to what information we want to collect. We can go into our console and generate a new model
rails g model Lead name email device country
This will create the database table for us, and now that that's done, we can go into our application and actually build the form for this. Right now, the best way to start might be to check out the routes, root is the only route that I have, the 'main#index', and the main_controller.rb does nothing at all, and the main index file simply says: "Sign up for my newsletter", and that's what you see here. Here you actually want to render a form for a new lead, so we want to actually create a form so that someone can type in their information and we can save it in the database. The way that we want to do that, is we can go into our
class MainController < ApplicationController def index @lead = Lead.new end end
This will create one in memory which is important for us, so that we can use that lead variable right here in our form_for. So we can make a regular form_for and we'll have a couple different fields. I'm going to keep this simple and just say
<h1>Sign up for my Newsletter!</h1> <%= form_for @lead do |f| %> <%= f.text_field :name %> <%= f.email_field :email %> <%= f.text_field :device %> <%= f.text_field :country %> <%= f.submit %> <% end %>
Country will be a text field for now, to make sure that it works, but eventually this will become a drop-down. Now that we have these, we can have an submit button, and we can re-render our page, and we should have gotten the form, but what we haven't done yet is created the routes to connect this form to our rails app. So in order to make this form work, we need a
resources :leads in our config/routes.rb file, and this will basically say: Hey, when there's a form_for a lead, it generates a route, but it has to exist inside of our rails app routes files. So in order to create a form like that, we need that. This is great. This is "name", "email", "device" and "country". If we go here and add
placeholder: "Name", and we copy this and we edit these accordingly, then we can see this a little bit better, and you'll be able to style this as you go along, so you probably want to wrap these fields in divs and stuff like that so they look prettier, but we're going to go real simple and basic here, we have our forms, we have our routes, but we haven't created the controller, and nothing is going to save this information. So if we just click "Create Lead" right now, we're going to get "uninitialized constant LeadsController", and that's telling us that we need to create the controller, and all we have right now is the application controller main controller, we can edit
class LeadsController < ApplicationController end
We can actually leave this as it is, and we can see what the next step is, that rails will tell us to do, so we can kind of follow these errors to learn what to do next, and now rails is telling us: Hey, you submitted a form, we found the controller, but you didn't have the create action inside of it, so now we can create the create action, and if we save this, now what's going to happen is it's going to give us a "Missing Template", this is going to say that rails went through the controller in the action, but it didn't know afterwards, is simply redirect to the root path and then at least this won't break anymore, so if we do that, we can keep clicking this and it will successfully work. Now we haven't done any of the actual work inside of here, and that's going to be important, so we actually need to capture the information from the params, and save it to a lead. The way we'll do that is like normal, we'll create a lead in memory, we're going to create a method called lead_params
class LeadsController < ApplicationController def create @lead = Lead.new lead_params if @lead.save redirect_to root_path, notice: "Saved Successfully!" else redirect_to root_path, alert: "Failed to save" end private def lead_params params.require(:lead).permit(:name, :email, :device, :country) end end
When we save that, now we're going to get a new lead in memory, we're going to have those values assigned from the form, but we also need to make sure to save it. So if we do
@lead.save we can redirect to the root path, and if it doesn't work we can do something else. In both cases right now, I'm going to redirect to the root path, but we can have different notices.
notice: "Saved successfully!", and maybe we can have an alert here instead and we can say
alert: "Failed to save". Now my application layout does not have a way to display those flash messages, so those were flash messages that we put in the end of the redirect_to there, so if I go back to the leads_controller.rb, this option here of setting notice or alert will send a flash message, and then here we can render out those flash messages.
<div class="container"> <% if notice %> <%= notice %> <% end %> <% if alert %> <%= alert %> <% end %> <%= yield %> </div>
Now we should be able to create the lead, and we'll see that it was saved successfully, and everything was correct, so that's great. These notice and alert methods, these are actually methods, not variables that rails gives you their shortcuts to accessing those flash messages, because normally you could say
flash[:notice] instead of "notice" itself, it's just a little helper for you to make that a little cleaner to read. So we want to go to the lead, and we want to validate
validates :name, :email, :device, :country, presence: true
We want to make sure that all of these are present when you submit this form, so now when we create the lead, it will have failed to save, because the validations did not pass. And that happens inside our leads controller right here, if save fails, then it will render an alert, if it succeeds, it will send a notice, so this is all we really need to do for saving this. So we've got everything done, the nice part might be to set a cookie if it's successful, and then not re-render the form. So instead of giving a notice, we could delete this.
if @lead.save cookies[:saved_lead] = true #rest of code
And then inside of our index, instead of rendering the form every time, we can say:
<% if cookies[:saved_lead] %> Thanks for signing up! <% else %> // display the form <% end %>
The very first time you come to the site, you type in your information, when you "Create lead", now, instead of the flash at the top, we get "Thanks for signing up" that we created, and we don't see the form, and that also means that if we refresh the page you won't see the form again because it saved the cookie, so it remembers that our information was saved in the database which is great. The trouble is, and this is common with every single thing like this, if you don't force them to add an account, if they come back and the cookie is gone for some reason, like incognito mode, they'll see the form again, so you might get duplicates, and then you can edit your save, and create a new user here, so you could look them up by the email address, and then update the record instead, but I'm going to allow duplicates in this example to keep it simple. I cleared my cookies here, and the last thing we need to do is change the device and country to be select boxes. So rather than having a text field for these, you can have a select and pass in the options that you want available. In this case, device should be "iPhone" or "Android", and that's an array of those two items, we can refresh and it will give us those two items, you'll notice that placeholder is no longer available, and that is because it changes to prompt when you're using a select tag. This will now say "Device", and you can now choose "iPhone" or "Android", but it also will submit nothing when "Device" is selected, so you want to make sure your users choose their devices. "Choose your device" or something like that is going to be helpful, or you can preselect an option an leave out the prompt, that is up to you.
<%= f.select :device, ["iPhone", "Android"], prompt: "Device" %>
country_select is a gem that if fantastic for, of course, selecting countries. In this case, their instructions are super duper simple, we can add the country_select gem to our gemfile, close out our server, run
bundle, restart our rails server, we'll let that finish, and we'll take a look at the documentation. country_select is really simple, all you have to do is do a country_select instead and that is it.
<%= f.country_select :country %>
remote: true option, and then render a format.js thing if you want, the
<% if @lead.errors.any? %> alert("Please fill out the entire form") <% else %> $("#new_lead").hide() <% end %>
That is up to you, and I'm not going to cover that here, but that is the gist of how you set up your own email capture leads form in a rails application. One last thing that might be useful to explain, is that if you want, one thing you could do here, is you could take this chunk, and make it a reusable partial, you could move this somewhere, I would take the whole thing, and then, a piece that you could do to make this even more reusable is to change
Lead.new, and then your main controller, and any other controller that you use does not need to set this variable. It's not exactly great practice, but it does make for this form to be reused anywhere, so you could put it on the homepage, on the blog, on any sort of pages that you're building, and it makes for a really flexible partial that you can drop in anywhere, without having the dependency in the controller every single time.
Transcript written by Miguel