oh hai.

Capistrano recipe: update your remote shared assets and database to your local application

Posted: April 15th, 2009 | Author: Nick | Filed under: rails | Tags: , , | 2 Comments »

When working on a production site there is often a need to get the latest data from the database or the latest shared assets onto your local development machine.

Shared Assets are typically items that users uploads through some interface that you don’t want to store in your git repository but don’t want to overwrite on deploy. For example you have a user model and people can upload their avatar. We don’t want that image file in the repository but we do want to keep it on the live site.

When I talk about shared assets I mean specifically items in the public directory. These items should be in your .gitignore file as well as a symlink that is setup in your deploy recipe. A directory like public/uploads or public/images/photos. You shouldn’t be using this for javascript files, css, regular images, etc.

The live database often contains data that modifies either the content or display of your site. Various items such as blog posts, recent messages, and many other items.

Wouldn’t it be nice if you could have an easy way to make your local development site look just like the production site?

Assuming you already have a functioning deploy recipe the following is an additional namespace you can add to have this functionality. Also, make sure that you have your keys on your remote server so it doesn’t prompt you to login.

First lets setup the shared host that we want to pull this information from. We could probably pull from one of the roles but for now we are just going to use a single shared host. Modify this recipe as you see fit, this is just more of a base for people to work off of.

Specify the shared host to pull your remote assets and database from

set :shared_host, "nickhammond.com"

Now lets setup the namespace and recipe for the two tasks that we want to accomplish.

namespace :update do
  desc "Mirrors the remote shared public directory with your local copy, doesn't download symlinks"
  task :shared_assets do 
    if shared_host
      # Used friendly options so it's easier to read
      # I'm using rsync so that it only copies what it needs
      # Windows users you can use the download method within capistrano and pass recursive => true
      run_locally("rsync --recursive --times --rsh=ssh --compress --human-readable --progress #{user}@#{shared_host}:#{shared_path}/public/ public/")
    else
      puts "Ummmm. You should define a shared_host variable so I know where to get the files from."
    end  
  end
 
  desc "Dump remote production database into tmp/, rsync file to local machine, import into local development database"
  task :database do 
    # First lets get the remote database config file so that we can read in the database settings
    get("#{shared_path}/config/database.yml", "tmp/database.yml")
 
    # load the production settings within the database file
    remote_settings = YAML::load_file("tmp/database.yml")["production"]
 
    # we also need the local settings so that we can import the fresh database properly
    local_settings = YAML::load_file("config/database.yml")["development"]
 
    # dump the production database and store it in the current path's tmp directory. I chose to use the same filename everytime so that it would just overwrite the same file rather than creating a timestamped file.  If you want to use this to create backups then I would recommend putting something like Time.now in the filename and not storing it in the tmp directory
    run "mysqldump -u'#{remote_settings["username"]}' -p'#{remote_settings["password"]}' -h'#{remote_settings["host"]}' '#{remote_settings["database"]}' > #{current_path}/tmp/production-#{remote_settings["database"]}-dump.sql"
 
  # If your database is large you might want to bzip it up and then extract it later
 
    # run_locally is a method provided by capistrano to run commands on your local machine. Here we are just rsyncing the remote database dump with the local copy of the dump
    run_locally("rsync --times --rsh=ssh --compress --human-readable --progress #{user}@#{shared_host}:#{current_path}/tmp/production-#{remote_settings["database"]}-dump.sql tmp/production-#{remote_settings["database"]}-dump.sql")
 
    # now that we have the upated production dump file we should use the local settings to import this db.  
    run_locally("mysql -u#{local_settings["username"]} #{"-p#{local_settings["password"]}" if local_settings["password"]} #{local_settings["database"]} < tmp/production-#{remote_settings["database"]}-dump.sql")
 
  end
end


Rails magic form helper - create field based on type

Posted: March 21st, 2009 | Author: Nick | Filed under: rails | Tags: , | No Comments »

The following is a helper that adds onto the regular form_for helper within rails. The script below adds methods onto the default form_for to generate fields automatically based on the type of the field. If you have a really basic model you can also generate the entire form based on the data type of each column.

# put this in lib/magic_form.rb
class MagicForm < ActionView::Helpers::FormBuilder
  def input_fields(opts = {})
    object = @object.class
    columns = object.columns.delete_if {|key,value| opts[:exclude] && opts[:exclude].include?(key.name) || key.name == "id" }
    columns.collect {|o| input_for(o.name)}
  end
 
  def input_for(field, opts = {})
    field = field.to_s
    field_label = self.label(field, opts[:label])
    type = @object.class.columns_hash[field].type.to_s
 
    if field.include?("_id")
      field = self.collection_select field, field.gsub("_id","").camelize.constantize.all, opts[:option_id] ? opts[:options_id] : :id, opts[:option_label] ? opts[:option_label] : :name
    else
      field = case type
        when "string", "integer", "float"
          self.text_field field, opts
        when "boolean"
          self.check_box field, opts
        when "datetime"
          self.datetime_select field, opts
        when "date"
          self.date_select field, opts
        when "time"
          self.time_select field, opts
        when "text"
          self.text_area field, opts
        when "binary"
          self.file_field field, opts
        eles
          self.text_field field, opts
        end
    end
 
    if type == "boolean"
      "<p>\n\t#{field}\n\t#{field_label}\n\n"
    else
      "<p>\n\t#{field_label}\n\t<br />\n\t#{field}\n</p>\n"
    end
  end
end
 
# Example usage
# app/admin/views/listings/new.html.haml
%h2 New listing
= render :partial => "form", :locals =&gt; {:listing => @listing}
 
# app/models/listing.rb
class Listing < ActiveRecord::Base
  has_one :status
end
 
# app/models/status.rb
class Status < ActiveRecord::Base
  validates_presence_of :name
  belongs_to :listing
end
 
# app/admin/views/llistings/_form.html.haml
# single field usage
# f.input_for :price prints out the label along with the proper field. integer, string, float is a
# text field, text is a text area, boolean is a checkbox, date field is a date selector, datetime
# is date time selector, etc.
# f.input_for :status_id makes a label with a select field and populates the select input
# with all of the options from the status table.  The way that the helper determines the
# associated model's data could probably be improved upon, I'm sure there's a better way.
- form_for ([:admin, @listing]), :builder =&gt; MagicForm  do |f|
  = f.input_for :price
  = f.input_for :status_id
  = submit_tag "Create new listing"
 
# app/admin/views/listings/_form.html.haml
# complete form maker
# f.input_fields will make the entire form for you from Model.columns (excluding id, didn't
# want to use content_columns since that strips out all fields that have _id).  This probably
# won't be used much but it's here for really basic models where nothing too complicated is going on.
- form_for ([:admin, @listing]), :builder => MagicForm  do |f|
  = f.input_fields, :exclude => ['created_at']
  = submit_tag "Create new listing"
 
# the helper doesn't support has_many associations just has_one associations.
# This is meant to be used as a replacement for most of the simple cases for
# administering content, not for complex forms or associations.

This form builder was inspired by Merb’s control_for method which I can no longer find documentation on. Hopefully something similar will make it into Rails 3.

Let me know if you have tips on how to add on or improve the script or if you find it useful.


YM4R with multiple domains

Posted: March 2nd, 2009 | Author: Nick | Filed under: rails | Tags: , , , | No Comments »

I didn’t find this option documented anywhere until I started digging through the code within the library. I had an instance where I was using A CMS to run multiple sites that all needed a use for google maps. With the regular implementation of YM4R you would normally get an error from google maps stating that this Google Maps API key isn’t valid for this domain. By simply passing in the host option you can then use the additional api keys you have setup in your config/gmaps_api_key.rb

In your config/gmaps_api_key.rb there needs to be a key for each domain that you wish to have google maps on.

production:
 host.com: longapikeythatmakesnosense
 anotherhost.com: jsdkfljskfljsgklsjgkl

Then in your layout file you need to print out the correct API key for communicating with google maps. This can be done by just passing in the host option with the request.host as it’s value.

< %= GMap.header(:host => request.host) %>


PHP to Ruby on Rails Translations

Posted: April 20th, 2008 | Author: Nick | Filed under: php, ruby | Tags: , , , | No Comments »

I used to develop primarily in PHP and dabbled into ASP but then I learned about Ruby and the Rails framework and now I cringe when I need to develop something new that isn’t in Ruby. There are a ton of tutorials on Rails so if you aren’t familiar or want to learn just do some googling. My intention with this post is to compare the differences in chunks of common code between php and ruby. Items such as for loops, array manipulation in ruby, various string functions in ruby and php, etc. I obviously won’t write everything so if you think of something that would be handy to know then leave a comment with it in there. Yes I realize PHP has frameworks that add some of the handy features that Rails does.

Create Array:
Ruby:

myArray = ["dog","cat","mouse"]

PHP:

$myArray = array("dog","cat","mouse")

*With Ruby there’s no need to specify that it’s an array, it knows from the brackets. Also notice that PHP uses parentheses and Ruby uses brackets.

Using element of array by position

Ruby:

myArray[1] = "cat"

PHP:

$myArray(1) = "cat"

First Elements:

Ruby:

myArray.first = "dog"

PHP:

first($myArray) = "dog"

Ruby:

myArray.first(2) = ["dog","cat"]

PHP:

for ($i=0; $i < 1; $i++ ){$first2elements .= myArray($i);}

* If there is a better way to do this in PHP let me know if it doesn’t involved a loop.

Print out elements of Array

Ruby:

 myArray.inspect

PHP:

printr($myArray)

Comments

Ruby:

# COMMENT

*Ruby doesn’t have multiline comments at the moment, just get a good editor that has a shortcut for it such as Textmate or vim with the vim NERDCommenter plugin.

PHP:

// SINGLE LINE COMMENT
/* MULTILINE COMMENT */

View embed Tags
Ruby:

< %=%>

PHP: (Short version), (Long Version)

< ?php= ?>
< ?= ?>

*Both languages support putting an equal sign after the opening to print out what is in the tag. In Ruby if something isn’t meant to be printed out such as the opening loop tag then it will get mad at you since there’s nothing to print in that portion.

A quick note on Ruby before I go into the string section. You can call quite a few methods on arrays and strings that permanately change the current object you are working on. The Exclamation point is what tells Ruby to keep the changes you made to it or if you leave it off it remains the same when you call it again. For instance you can do…

String Manipulation

Ruby:

s = "hello world"

PHP:

$s = "hello world"
s.capitalize => "Hello World"
ucwords($s) = "Hello World"
s.capitalize! = "Hello World" *This keeps the string Capitalized
s.capitalize = "Hello World"

*The string is capitalized but only for this call on the capitalize method, if you want you can store it in a new variable to keep the capitalization.

Other good things to know about Ruby

Form Validation
Ruby:

validates_presence_of :animal_name #This goes in the definition of the model such as 'animals', going into detail about how models work is beyond the scope of this tutorial. Other handy ones you can use in rails are...
validates_acceptance_of #Good to use this for checkboxes.
validates_format_of #Can use regular expressions to validate fields such as email address.
validates_length_of :animal_name, :maximum = 20 #A max of 20 characters is aloud
validates_length_of :animal_name, :within= 5..20 #Needs to be within 5 to 20 characters
validates_numericality_of :feet #Makes sure that the user specifies a number for amount of feet
validates_presence_of :animal_name #Make sure animal name isn't blank
validates_uniqueness_of :animal_name #Make sure you only have one entry for 'dog' as an animal name

PHP: There are several PHP frameworks that have different ways of doing similar validation techniques try, CakePHP is a very popular one.

Link Creation

< %= link_to "link name" "/direction/for/link" %>
< %= javascript_include_tag "myJSFile" %>
< %= stylesheet_link_tag "myCSSFile" %>

A great resource for PHP to rails/ruby translations is http://railsforphp.com