Topic: 5 short Ruby on Rails how-tos

Here are a few useful things I have discovered while learning in Ruby on Rails, I hope they are useful to you and feel free to correct me on anything that is not right or if you have a better solution to any of these problems.

I am very new to programming and I am self-taught, so I do not claim to know what I am talking about. I do, however, try to do things "correctly," so please do not hesitate to enlighten me further if you see something you don't like.



1. Escaping HTML

Purpose: to increase security from cross-site scripting, and to ensure the the proper rendering of your page.


Let's say a hypothetical user wanted to be cool and decided that he'd enter this as his username:

<span style="color:red; font:bold 48px 'Comic Sans MS';border:thick dashed orange; background-color:green;">WomenLoveMe77</span>

If you don't escape the HTML, you'd end up with this when the username is displayed. But thankfully, if you escape the HTML the username will just be the text that was input and the browser will not render it as HTML.

Just use

<%=h @user.userName %>

<!-- instead of -->

<%= @user.userName %>

<!-- By the way, the "h" is really an alias for this: -->
<%= html_escape(@user.userName) %>


2. Encoding email links:

Purpose: to decrease the harvesting of email addresses by those pesky interweb spyderbots.


You can encode mail_to links using javascript, or if you don't want to require javascript, use hex instead:

<%= mail_to @user.email, "Email Me", :subject => "Hi there", :encode => "javascript" %>
  # => <script type="text/javascript">eval(unescape('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%65%78%61%6d%70%6c%65%40%65%78%61%6d%70%6c%65%2e%63%6f%6d%3f%73%75%62%6a%65%63%74%3d%48%69%25%32%30%74%68%65%72%65%22%3e%45%6d%61%69%6c%20%4d%65%3c%2f%61%3e%27%29%3b'))</script>

<%= mail_to @user.email, "Email Me", :subject => "Hi there", :encode => "hex" %>
  # => <a href="mailto:%65%78%61%6d%70%6c%65@%65%78%61%6d%70%6c%65.%63%6f%6d?subject=Hi%20there">Email Me</a>


This certainly does not provide foolproof protection from your address getting out there to spammers, but it definitely helps and is an easy way to decrease the chances of a spambot getting addresses off of your site.

If you want something more secure than this, check out the CipherMail plugin, but this is a nice and easy way to make things a little more secure.




3. Using a select menu to redirect to the selection

Purpose: to create a select menu that redirects to the selected item's page on change.


You can read about the different select menu helpers here. But here is an example using the collection_select method.

To make the redirect happen, all you have to do is add something like :onchange => "document.location = '/controller/' + this.value" to your select method:

<%= collection_select(:contact, :id, Contact.find(:all, :order => :firstName), :id, :fullName, { :prompt => "Select a Contact" }, :onchange => "document.location = '/contacts/' + this.value")%>


You can use virtual attributes to display additional
info in the dropdown instead of just one field.

# contact.rb

def fullName
  "#{firstName} #{lastName}"
end


4. Creating a two-tiered navigation system with dynamic links

Purpose: to built a dynamic site-wide navigation system.


Here is a solution I came up with for a global navigation link system. The site for which I built this is not restful and is mostly static content, so please excuse the ugly non-named routes I am using.

Goals:
1. Global and completely dynamic system
2. Two-tiered:
  a. main-links that are displayed on every page
  b. sub-links that are only displayed with their parent
3. Display to the user what the current page is.
4. Easily modifiable and understandable. All links are consolidated.


We start by creating a couple of app-wide helper methods that serve as a sort of site map. This will provide a way for us to easily see what our links will be on each page, which is nice since this scenario includes sub-navigation which is only visible when it's parent is selected.

# application_helper.rb

def get_main_links
  @mainLinks = [
    "Home",
    "Products",
    "About Us"
    ]
end

def get_sub_links(owner)
  case owner
  when "home"
    @subLinks = [
      'Welcome',
      'Watch Demo Video'
    ]

  when "products"
    @subLinks = [
      'Now Shipping',
      'In Development'
    ]

  when "about_us"
    @subLinks = [
      'Our Employees',
      'Contact Us'
    ]
  end
end


I also setup a little method to convert our human-readable links to something good for display in the URL:

def linkify(string)
  string.gsub(' ', '_').downcase
end

# linkify("About Us") => "about_us"


If anyone knows how to turn that into an inflector so we can do "@string.linkify" instead please let me know.


In the view we will use two methods built into rails to make the links dynamic:

link_to_unless_current
This will check both the controller and the action, so we will use this for the sub-links, but it won't work for the main ones, so we'll use:

link_to_unless
With this, we can be more specific. We can pass controller.controller_name and compare that to the main nav links.


Now for the view code:

<!-- layouts/application.html.erb -->

<div id="navigation">
 
  <div id="mainLinks">
    <ul>
      <%- get_main_links
      for l in @mainLinks -%>

      <!-- get the sublinks for each main link so we know what action to send -->
      <% get_sub_links(linkify(l)) %>
      <li><%= link_to_unless controller.controller_name == linkify(l), l, { :controller => linkify(l), :action => linkify(@subLinks.first) } %></li>
      <%- end -%>
    </ul>
  </div>
 
  <div id="subLinks">
    <ul>
      <!-- get the sublinks for the current controller -->
      <%- get_sub_links(controller.controller_name)
      for l in @subLinks -%>
      <li><%= link_to_unless_current l, :action => linkify(l) %></li>
      <%- end -%>
    </ul>
  </div>
 
</div> <!-- End Navigation -->


5. Tagging a single field with multiple check boxes

Purpose: to provide a simple, dynamic solution for tagging a model with various options that are not mutually exclusive.

Here's what I like about this method:
1. No DB modification or extra relationships needed, just uses a single string field
2. Simple and pretty output of tags. As a string by default, but easily converts into an array.
3. Allows for multiple selections in one field as opposed to a select menu, which is limited to one.


In this example I'll use an address, which can take multiple roles (shipping, billing, etc.)

Let's start with the view so we can see where we are headed:
 

<!-- inside form for "address" --> 
 
  <%= tag_with_check_boxes "address", "addressOptions", @address, ["Shipping", "Billing", "Store", "Office", "PO Box", "Home"] %>

<!-- or say you want to wrap each box in a div with a class of 'inlineCheckbox' -->

  <%= tag_with_check_boxes "address", "addressOptions", @address, ["Shipping", "Billing", "Store", "Office", "PO Box", "Home"], { :startTag => "<div class='inlineBoxes'>", :endTag => "</div>" } %>


This will create a checkbox for each tag you pass, and optionally wrap it in some tags so you have control over the formatting.
 
  Now for the helper method:
 
# Application Helper

def tag_with_check_boxes(modelName, virtualAttribute, object, tags = [], options = {})
  options.reverse_merge! :startTag => "", :endTag => "" # allows for options to be blank

  @storedTags = object.send(virtualAttribute) # use "send()" to dynamically pass the attribute name
  @checkboxTags = ""

  for tag in tags
    @checkboxTags += options[:startTag] + check_box_tag("#{modelName}[#{virtualAttribute}][]", tag, tag_stored?(tag, @storedTags)) + "<label>#{tag}</label>" + options[:endTag]
  end

  @checkboxTags
end

# set boxes as checked or unchecked for editing already existing data.
def tag_stored?(tag, storedTags)
  if storedTags # check if the record exists so it doesn't break the "edit" action if the field is empty or the "new" action.
    storedTags.include? tag
  end
end


Now we need to add a virtual attribute to handle the array we will send through the form:

# Address Model

def addressOptions # we need an alias getter method for 'addressType' in order to keep the function dynamic
  self.addressType
end

def addressOptions=(tags)
  self.addressType = tags.join(', ')
end


This does not take care of any validation or anything like that, but you can do that if you choose.

If you don't ship to PO Boxes, for example, you could validate that "PO Box" and "Shipping" are not both checked at the same time.

If you want to play with the tags as an array, just do:

@address.addressType.split(', ')

  and you can do whatever else you need with it.
 
 
  One potential problem with this method is that if you want to remove all tags from an entry, the update action doesn't know that you deselected the checkboxes since it only updates values that are sent to it (blank checkboxes == no addressOptions param). If you want to allow the removal of all tags, you can add this to the "update" action of your controller:
 

if not params[:address][:addressOptions]
  @address.addressType = ""
end

Conclusion

I hope that this was beneficial to you, and please let me know how I can improve upon these methods. Thanks for reading, and many thanks to Ryan Bates of Railscasts for helping me learn Ruby on Rails, and for encouraging me and many others to contribute to the community.

Last edited by Zef (2008-05-05 03:42:41)

Re: 5 short Ruby on Rails how-tos

-1

Re: 5 short Ruby on Rails how-tos

Nice Info Thanks for Sharing.

Re: 5 short Ruby on Rails how-tos

Nice topic. Big thanks for info. Its real help.

Re: 5 short Ruby on Rails how-tos

Thank you for the tutorial, this was easy to follow and learn even for a newbie like me.

Re: 5 short Ruby on Rails how-tos

Thanks for sharing to the underclassmen of ROR university ;-).

Re: 5 short Ruby on Rails how-tos

I have been using recipe # 3 for a long time to basically direct a user to a different controller/action upon selection of drop-down value.  Now IE8 seems to be causing some issues, where as an onchange event is triggered right after the page is loaded, even though the user doesn't change anything in the drop down field.  The good news is that the user doesn't even know it happens and is not really impacted.  The bad news is that I end up getting these bogus GET requests and they are all erroring out.  As I mentioned before, this doesn't happen in any other browser, except for IE8.  This is what I see in my log.

Here is the successfull loading of the page:

Processing ViewsController#show (for X.X.X.X at 2010-07-21 06:36:22) [GET]
  Parameters: {"action"=>"show", "id"=>"118", "controller"=>"views"}
Rendering template within layouts/change_requests
Rendering views/show

And then right after it, automatically triggered by IE8 and this select onchange javascript:

Processing ViewsController#show to html (for X.X.X.X at 2010-07-21 06:36:24) [GET]
  Parameters: {"format"=>"html", "action"=>"show", "id"=>"blank", "controller"=>"views"}

ActiveRecord::RecordNotFound (Couldn't find View with ID=blank):

Last edited by karant (2010-07-21 03:12:08)

Re: 5 short Ruby on Rails how-tos

Bump

Re: 5 short Ruby on Rails how-tos

I use the h helper a lot, but would it be more simple to escape the html before you even save the data? Perhaps something like this:

before_save :strip_html_tags

def strip_html_tags
  self.username.gsub!(/<\/?.*?>/, "")
end

Re: 5 short Ruby on Rails how-tos

Thanks for the 4. Creating a two-tiered navigation system with dynamic links tutorial.

Last edited by dpuglisi (2011-01-14 09:00:53)

Re: 5 short Ruby on Rails how-tos

aarongodin, it's  a good idea. Has you already tried it? What's the result, i wonder?

Last edited by gregorv (2012-06-19 09:56:43)