Skip to main content

16 Newsletter Sign Up Form with country_select

Episode 49 · April 4, 2015

How to plan and build a newsletter sign up form

Gems


Transcripts

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
rake db:migrate

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

main_controller.rb

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

app/views/main/index.html.erb

<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

app/controllers/leads_controller.rb

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

app/controllers/leads_controller.rb

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.

app/views/layouts/application.html.erb

<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

app/models/lead.rb

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.

app/controllers/leads_controller.rb

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 %>

That should, if all goes well, properly render a country_select for us, and it does, so this connects really really easily, it allows you to choose any of these countries that you like, and it also supports a lot of options, such as priorities, limiting to only certain countries, doing stuff with preselecting ones if you want. Lot's of things you can do with this, so take a look at the documentation, it's really easy to use, but this is all that Gareth was interested in seeing for his sign up form. Now, you could take this and go the extra step, and add a JavaScript AJAX response, and changing this to a remote form, we're not going to cover that, because it's not really that important for a simple Sign Up, if you create the lead here, re-rendering a page is not a big deal, it might be a little bit more flexible to make this a remote: true option, and then render a format.js thing if you want, the remote: true option just goes in the form, right here, and then you could create app/views/leads/create.js.erb inside your views folder, and you can render any sort of JavaScript here, so if you want to hide the new_lead form, you can do that with jQuery after they submit it, it's kind of up to you on how you want to structure it, and potentially the other option you'll need to do is add the respond_to block on here if you're using the redirects like this, so if you're not rendering an html template during create, you're going to need a response block, and then your html request, your normal form submit, you'll redirect, and then for your JavaScript ones, you'll just simply render a JavaScript template. That happens by not passing a block into the format.js like you do in the format html. You want to make sure that you do that on both situations, success or failure, and then inside your JavaScript views, you have access to erb's so you could check to see

app/views/leads/create.js.erb

<% 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 to 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.

That wraps up this episode, if you want to read more about our discussion on our Newsletter sign up form, you can take a look at the forum, and read the "Simple newsletter sign up" thread, if you want to see more episodes like this one, where we cover a little bit more concrete of a feature, and a little less theory, let me know, I know that there's a big interest in both sides, so there's a lot of: Hey, I need to build this feature for my site, but there's also like: Hey, I want to learn how to write better JavaScript for my rails app, and that a little bit more conceptual thing, so leave a note in the comments, and let me know what you think, which ones seem more relevant to you, maybe you want to learn both. Let me know, this is good feedback for me, and it help me make the next episodes even more useful for you guys, so please leave a note in the comments, and I will talk to you soon.

Transcript written by Miguel

Discussion


Gravatar
Nathan Rofkahr (10 XP) on

Well I for one appreciated this :)


Gravatar
Routine Tracker on

Loved the concrete implementation of a feature, but I certainly wouldn't be opposed to some in-depth Javascript (or even CoffeeScript).

Gravatar
Chris Oliver (167,500 XP) on

Definitely plan on doing more Javascript / CoffeeScript soon.

Gravatar
Jared Smith (210 XP) on

That would be awesome!


Gravatar
Kohl Kohlbrenner on

@Chris It might be a bit ambitious, but what are your thoughts on making a screencast deconstructing a rails app(just using plain ruby)? I completed a PHP project last month without the use of a framework for the first time ever and it really opened my eyes to how the MVC construct works. It helped me understand the relationship between the pages, how variables are sent from controller to view, and I was able to identify more clearly, "okay this page is a view, this is a controller, etc". just a suggestion!

Gravatar
Chris Oliver (167,500 XP) on

Yes!! I'm definitely planning on doing a "Rails-from-scratch" series so you can see exactly how all this comes together from just regular old Ruby. Really looking forward to it but need to get it all mapped out beforehand.

Gravatar
S K Chowdhury Masum on

Rails-from-scratch! sound great when I will see that series live?

Gravatar
Chris Oliver (167,500 XP) on

Pretty soon! It's a lot of content so I need to make sure I plenty of time to plan it all out.

Gravatar
S K Chowdhury Masum on

i just started counting for that glorious moment!

Gravatar
derk on

yeah it would be very helpful


Gravatar
Nathan Anderson (30 XP) on

Great video. Thanks for sharing. This has been something that I was curious about for a while. Personally, I had one of those "duh" moments where a couple of Rails concepts that I was struggling with came together (even though they weren't directly targeted in this video). Thanks for the help!


Gravatar
Manuel Quintanilla on

setting up mandrill with something like this? trying to get an email sent everytime someone submits their name and email information


Gravatar
Vũ Nguyên on

Very useful, I learned a lot from this video. Thank you :D


Gravatar
Anthony Candaele on

great video, thanks! What happens though with the form data that was submitted? Is it displayed in a view, or is it send to an email address?

I'm asking this because a client as me to implement a sign-up button for a newletter on his Rails application.

This video looks great to get me started implementing this sign-up form. But I'm not sure on how to process the submitted data.
Any advice is welcome,

greetings,

Anthony

Gravatar
Chris Oliver (167,500 XP) on

In this case, the information is just saved to the database so you could do what you want with it.

Gravatar
Anthony Candaele on

yeah, I just read out the subscription emails in the admin section of my application. Maybe it would be handy to have a function that copies all the subscriptions to the local systems clipboard so they can easily be pasted somewhere else.

Gravatar
Anthony Candaele on

there's one issue with the above implementation. After signing up, the success message is always shown. Is there a way to only show the success message once, when signing up successfully?


Gravatar
iBoring on

Hi Chris, it's really useful video, thanks a lot
I want to ask on how to generate PDF for this submitted newsletter form?
I have a client asking me to enable a feature that after visitors clicked the submit button, visitors can receive PDF from the form they filled in.


Login or create an account to join the conversation.