Skip to content

Categories:

Organizing SASS files

While developing a couple of Facebook apps, I found out that our stylesheet file sizes were getting too big, and it forced me to be more circumspect in organizing my SASS files. I use the Compass gem and Blueprint platform exclusively when developing views, and the ease of use tends to get me spoiled at times, which quickly results in a lot of bloat.

So far, this has been my approach:

  • _init.sass groups all constants in one place - font colors, link colors, border colors, etc.
  • _base.sass is for declaring mixins into their proper scopes
  • The utilities dir is for all the disparate classes - I hate going through long stylesheets, @import is a godsend.
  • Important part: page-specific stylesheets are included in the specific view, and I mimic the view structure for easier access. This way, I only include commonly-used classes in the application stylesheet, which usually comes in at 24KB

In the view, just do


My structure in the app/stylesheets dir:

  • _base.sass
  • _init.sass
  • layouts
    • application.sass
    • admin.sass
  • utilities
    • _forms.sass
    • _links.sass
    • _navigation.sass
    • _tables.sass
  • pages
    • home
      • index.sass
      • about.sass
    • users
      • new.sass
  • Note when developing the UI for Facebook apps:
    Make sure you scope your mixins properly or else you will override the Facebook classes and mess up its components (the multifriend selector, for one).

Posted in Gems.

Capistrano-ext and Ruby Enterprise Edition

I am a big fan of the Phusion guys - I update my local Passenger and Ruby Enterprise Edition installs whenever a new version is pushed. But while trying to deploy a client’s app using Capistrano-ext to a staging server, I kept getting the following error:

MBP:app bobby$ cap staging deploy
/opt/ruby-enterprise-1.8.6-20081205/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `gem_original_require': no such file to load -- capistrano/ext/multistage (LoadError)
	from /opt/ruby-enterprise-1.8.6-20081205/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `require'
	from /opt/ruby-enterprise-1.8.6-20081205/lib/ruby/gems/1.8/gems/capistrano-2.5.3/lib/capistrano/configuration/loading.rb:152:in `require'
	from ./config/deploy.rb:4:in `load'
	from /opt/ruby-enterprise-1.8.6-20081205/lib/ruby/gems/1.8/gems/capistrano-2.5.3/lib/capistrano/configuration/loading.rb:172:in `load_from_file'
	from /opt/ruby-enterprise-1.8.6-20081205/lib/ruby/gems/1.8/gems/capistrano-2.5.3/lib/capistrano/configuration/loading.rb:89:in `load'
	from /opt/ruby-enterprise-1.8.6-20081205/lib/ruby/gems/1.8/gems/capistrano-2.5.3/lib/capistrano/configuration/loading.rb:86:in `load'
	from /opt/ruby-enterprise-1.8.6-20081205/lib/ruby/gems/1.8/gems/capistrano-2.5.3/lib/capistrano/configuration/loading.rb:86:in `each'
	from /opt/ruby-enterprise-1.8.6-20081205/lib/ruby/gems/1.8/gems/capistrano-2.5.3/lib/capistrano/configuration/loading.rb:86:in `load'
	 ... 10 levels...
	from /opt/ruby-enterprise-1.8.6-20081205/lib/ruby/gems/1.8/gems/capistrano-2.5.3/lib/capistrano/cli/execute.rb:14:in `execute'
	from /opt/ruby-enterprise-1.8.6-20081205/lib/ruby/gems/1.8/gems/capistrano-2.5.3/bin/cap:4
	from /opt/ruby-enterprise-1.8.6-20081205/bin/cap:19:in `load'
	from /opt/ruby-enterprise-1.8.6-20081205/bin/cap:19

No amount of installing/uninstalling the Capistrano and Capistrano-ext gems worked. Finally, I decided to try using the default Ruby install at /usr/bin/ruby, and the deploy went through smoothly. I have no patience to track down the exact cause, I’m just glad there’s some hair left on my head.

Posted in Gems. Tagged with , .

Location-aware “Cancel” buttons in forms

For a client app, I needed to have “Cancel” buttons in my forms, which would send the user back to the previous page he was on. The restful_authentication plugin generates authenticated_system.rb, which gives us the following methods:

# Store the URI of the current request in the session.
# We can return to this location by calling #redirect_back_or_default.
def store_location
session[:return_to] = request.request_uri
end

# Redirect to the URI stored by the most recent store_location call or
# to the passed default.  Set an appropriately modified
#   after_filter :store_location, :only => [:index, :new, :show, :edit]
# for any controller you want to be bounce-backable.
def redirect_back_or_default(default)
redirect_to(session[:return_to] || default)
session[:return_to] = nil
end

I stored the previous page’s URL in session, and added an after filter in application.rb:

after_filter :store_location, :except => [ :new, :edit ]

In my form, I have two buttons:

#  View - form has a cancel button
= submit_tag 'Cancel', :name => 'cancel_button'
= submit_tag 'Save', :disabled => false, :disable_with => "Please wait..."

In my controller, I just check if params[:cancel_button] is not nil (send the user back to the stored uri in session if so, otherwise process the form):

def create
if params[:cancel_button]
redirect_back_or_default(dashboard_path)
else
@client = Client.build(params[:client])
if @client.save
flash[:notice] = "Client saved."
redirect_to client_url(@client)
else
flash[:error] = "Client not saved."
render :action => "new"
end
end
end

Posted in Tutorials. Tagged with .

Submitted a patch to Spree

Ran across Sean’s announcement merging my patch into the Spree codebase. Nothing earth-shaking, but flattering nonetheless (took out attachment_fu and replaced it with Paperclip).

I was also working on a multi-store extension (think Shopify lite), but just can’t find the time to further the code.

Posted in Gems. Tagged with , , .

Taking TOG out for a spin

A recent post about Tog got me curious, so I thought I’d give it a test drive. According to their site, you could add social networking functionalities as plugins, which is another approach from CommunityEngine (via Rails engines) and Insoshi (a Rails app). The instructions were a little vague, so I thought I’d document the process here (with a few errors along the way).

First off:

togify my_tog_app

You will need to install acts_as_commentable. When creating the commentable migration, it will kick out errors which will require you to install the following plugins:
acts_as_scribe, acts_as_taggable_on_steroids, acts_as_abusable, seo_urls, file_column.

So, here’s the whole diatribe:

ruby script/plugin install http://juixe.com/svn/acts_as_commentable
ruby script/generate migration ActsAsCommentable
# -ERROR - Plugin 'acts_as_scribe' does not exist

script/plugin install git://github.com/linkingpaths/acts_as_scribe.git
ruby script/generate acts_as_scribe_migration
# Error - Plugin 'acts_as_taggable_on_steroids' does not exist (RuntimeError)

ruby script/plugin install http://svn.viney.net.nz/things/rails/plugins/acts_as_taggable_on_steroids
ruby script/generate acts_as_taggable_migration
# Error -  Plugin 'acts_as_abusable' does not exist (RuntimeError)

script/plugin install git://github.com/linkingpaths/acts_as_abusable.git
script/generate acts_as_abusable_migration
# Error - Plugin 'acts_as_state_machine' does not exist (RuntimeError)

ruby script/plugin install http://elitists.textdriven.com/svn/plugins/acts_as_state_machine/trunk

script/generate acts_as_abusable_migration
# Error - Plugin 'seo_urls' does not exist (RuntimeError)
script/plugin install http://svn.redshiftmedia.com/svn/plugins/seo_urls

script/plugin install http://opensvn.csie.org/rails_file_column/plugins/file_column/trunk
script/generate acts_as_abusable_migration
# Error - Plugin 'seo_urls' does not exist (RuntimeError)
script/plugin install http://svn.redshiftmedia.com/svn/plugins/seo_urls

# FINALLY:
script/generate acts_as_abusable_migration - OK
ruby script/generate acts_as_taggable_migration - OK
ruby script/generate acts_as_scribe_migration - OK
ruby script/generate migration ActsAsCommentable - OK

# Need to paste this in:
class ActsAsCommentable < ActiveRecord::Migration
def self.up
create_table "comments", :force => true do |t|
t.column "title", :string, :limit => 50, :default => ""
t.column "comment", :text, :default => ""
t.column "created_at", :datetime, :null => false
t.column "commentable_id", :integer, :default => 0, :null => false
t.column "commentable_type", :string, :limit => 15, :default => "", :null => false
t.column "user_id", :integer, :default => 0, :null => false
end
add_index "comments", ["user_id"], :name => "fk_comments_user"
end

def self.down
drop_table :comments
end
end

Before migrating, Tog needs to copy over some files:

rake tog:plugins:copy_resources

# Doing a rake db:migrate at this point will throw these errors
Adding [:mail_user_observer, :user_observer] to []
== 1 IntegrateTog: migrating ==================================================
== 1 CreateTogConfig: migrating ===============================================
== 1 CreateTogConfig: migrated (0.0000s) ======================================

== 2 PatchCommentsTable: migrating ============================================
-- add_column(:comments, :commentable_type_tmp, :string, {:null=>false, :default=>""})
rake aborted!
SQLite3::SQLException: no such table: comments: ALTER TABLE "comments" ADD "commentable_type_tmp" varchar(255) DEFAULT '' NOT NULL
)

# I edited the migration version number for 001_integrate_tog.rb to 20081011122049_integrate_tog.rb to have it run after the plugin migrations
rake db:migrate

Deleted index.html, started up the server, and voila! A running tog app.

Installing other Tog plugins

I also added Conversatio and Vault, but deleted Picto - it was choking on its migrations.

ruby script/plugin install git://github.com/tog/tog_conversatio.git

ruby script/generate migration install_conversatio

class InstallConversatio < ActiveRecord::Migration
def self.up
migrate_plugin "tog_conversatio", 4
end

def self.down
migrate_plugin "tog_conversatio", 0
end
end

# ADD:
map.routes_from_plugin 'tog_conversatio'

ruby script/plugin install git://github.com/tog/tog_picto.git
# ERROR: acts_as_rateable' does not exist
script/plugin install http://juixe.com/svn/acts_as_rateable
script/generate migration create_acts_as_rateable
#ERROR: Plugin 'acts_as_list' does not exist
script/plugin install acts_as_list

def self.up
create_table "ratings", :force => true do |t|
t.column "rating", :integer, :default => 0
t.column "created_at", :datetime, :null => false
t.column "rateable_type", :string, :limit => 15, :default => "", :null => false
t.column "rateable_id", :integer, :default => 0, :null => false
t.column "user_id", :integer, :default => 0, :null => false
end

add_index "ratings", ["user_id"], :name => "fk_ratings_user"
end

def self.down
drop_table :ratings
end

ruby script/generate migration install_picto
class InstallPicto < ActiveRecord::Migration
def self.up
migrate_plugin "tog_picto", 6
end

def self.down
migrate_plugin "tog_picto", 0
end
end
ERROR during migration: == 5 AddPrivateColumnToPhotoset: migrating ====================================
-- add_column(:photosets, :privacy, :integer)
-> 0.0196s
rake aborted!
wrong number of arguments (1 for 0)

# DELETED picto migration, moving on.
rake tog:plugins:copy_resources

# Install tog vault:
script/plugin install acts_as_tree
ruby script/plugin install git://github.com/tog/tog_vault.git
ruby script/generate migration install_vault

map.routes_from_plugin 'tog_vault'

# Try picto again:
install picto plugin again --force
ruby script/generate migration install_picto

Gave upon Picto.

You can grab a copy of the bare Tog app from GitHub.

Posted in Gems, Tutorials. Tagged with .

Up and running with Spree

I came across Spree back when it was known as RailsCart, and it has turned out to be one of the more advanced ecommerce apps in Rails. You use the Spree gem to generate a Spree app, and use extensions to customize controllers, models and views.

Salient features:
Completely Customizable Views - Spree applications utilize an extension mechanism which allows developers to override existing views or to provide new ones.

Customizable via Extensions - The Spree extension mechanism allows for more then just customized views and stylesheets. Developers can also provide additional models, migrations and controllers.

Payment Gateways - Spree supports all of the major payment gateways via the popular Active Merchant plugin

Custom Tax Logic - Spree ships with a few basic tax calculators but it is anticipated that you may need to write your own custom tax logic.

Powerful Inventory Model - Spree also provides a highly sophisticated inventory model.

International Support - basic i18n support

SEO Permalinks - Spree also offers handy SEO permalinks for your products. So instead of http://example.com/products/1 you have http://example.com/products/spree-baseball-jersey

Recently introduced: Taxonomy

I installed the gem from source, and used that to generate my Spree app. It took me a few minutes to build and install the gem, I missed the instruction to get the source running (by configuring the database.yml file):

# First, get the source:
git clone git://github.com/schof/spree.git spree

# Configure the config/database.yml file (I used the default sqlite
#DBs, so just renaming the database.yml.example file should suffice)
# Install the gem dependencies
rake gems:install

# Bootstrap the database - runs migrations and creates the admin
# account. There is also an option to load sample data, quite useful
# for the impatient who want to see something up and running quickly.
# rake db:bootstrap

# Build and install the gem:
sudo rake spree:gem:install

# That's it, you now have the installed gem. Next step is to generate
# your own Spree app:
spree my_spree_app

Configure your DBs, start your server, and start customizing.

Example

# To customize the application.html.haml file, I generated an
# extension and used that for my views:
# script/generate extension LookAndFeel

#Which gave me
create vendor/extensions/look_and_feel/app/controllers
create vendor/extensions/look_and_feel/app/helpers
create vendor/extensions/look_and_feel/app/models
create vendor/extensions/look_and_feel/app/views
create vendor/extensions/look_and_feel/db/migrate
create vendor/extensions/look_and_feel/lib/tasks
create vendor/extensions/look_and_feel/public
create vendor/extensions/look_and_feel/README.markdown
create vendor/extensions/look_and_feel/look_and_feel_extension.rb
create vendor/extensions/look_and_feel/lib/tasks/look_and_feel_extension_tasks.rake
create vendor/extensions/look_and_feel/spec/controllers
create vendor/extensions/look_and_feel/spec/models
create vendor/extensions/look_and_feel/spec/views
create vendor/extensions/look_and_feel/spec/helpers
create vendor/extensions/look_and_feel/Rakefile
create vendor/extensions/look_and_feel/spec/spec_helper.rb
create vendor/extensions/look_and_feel/spec/spec.opts

Extensions follow the usual Rails directory structure. Since extensions are loaded after the Spree gem, you can basically override anything you want.

app
controllers
helpers
models
views
db
migrate
lib
spec
controllers
helpers
models
spec.opts
spec_helper
views

I added a layouts directory:

mkdir my_spree_app/vendor/extensions/look_and_feel/app/views/layouts

Put an application.html.haml file in there, added stylesheets in the extension’s public dir, and I had my first customized layout.

Posted in Gems. Tagged with , .

Scheduled tasks using BackgrounDRb

A project I am working on required a background process to check and update subscriptions nightly, and send out billing emails to users with expiring subscriptions. I decided to use BackgrounDRb, a plugin perfectly suited for this task.

Setting up:
BackgronDRb runs as a separate server, but is just a process with access to all your Rails models.

<br /><br />
# Generate a worker model:<br /><br />
script/generate worker billing<br /><br />

This generates a worker called BillingWorker in lib/workers. My billing_worker.rb goes like this:

<br /><br />
class BillingWorker < BackgrounDRb::MetaWorker</p><br />
<p>set_worker_name :billing_worker</p><br />
<p>def create(args=nil)</p><br />
<p>end</p><br />
<p>def do_nightly_updates<br /><br />
Subscription.run_notify<br /><br />
end</p><br />
<p>

</p><br />
<p>#In subscription.rb:<br /><br />
class Subscription < ActiveRecord::Base<br /><br />
def notify!<br /><br />
UserMailer.deliver_expiring_subscription_notification(subscription)<br /><br />
end</p><br />
<p>def self.run_notify<br /><br />
find( :all, :conditions =&amp;amp;amp;amp;gt; ['send_notice=?', true] ).each(&amp;amp;amp;amp;amp;:notify!)<br /><br />
end<br /><br />
end<br /><br />

To trigger the worker process to perform its task at 12:01 a.m. every day, my background.yml reads:

<br /><br />
:backgroundrb:<br /><br />
:ip: 0.0.0.0<br /><br />
:port: 11006<br /><br />
:schedules:<br /><br />
:billing_worker:<br /><br />
:do_nightly_updates:<br /><br />
# Trigger this action at 12:01 a.m. every day<br /><br />
:trigger_args: 0 1 0 * * * *<br /><br />

Re the BackgrounDRb cron trigger, here’s a snippet from the plugin docs :

Note that the initial field in the BackgrounDRb cron trigger specifies seconds, not minutes as with Unix-cron. The fields (which can be an asterisk, meaning all valid patterns) are:

<br /><br />
sec[0,59] min[0,59], hour[0,23], day[1,31], month[1,12], weekday[0,6], year</p><br />
<p># The syntax pretty much follows Unix-cron. The following will trigger on the first hour and the thirtieth minute every day:<br /><br />
0 30 1 * * * *</p><br />
<p># The following will trigger the specified method every 10 seconds:<br /><br />
*/10 * * * * * *</p><br />
<p># The following will trigger the specified method every 1 hour:<br /><br />
0 0 * * * * *<br /><br />

(Aside) If you’re developing on a Mac like I am, you may want to use

</p><br />
<p>:ip: 0.0.0.0</p><br />
<p># instead of<br /><br />
:ip: localhost<br /><br />

for things to run smoothly

Run it!
Fire up two Terminal windows, one to start and stop the BackgrounDRb server, and the other one to monitor the log file:

<br /><br />
script/backgroundrb start<br /><br />

Everything is hunky-dory right now, I just need to do two further tasks:

1. Write specs for my worker model - I can’t seem to mock my model correctly, I keep getting undefined constants errors when running autotest.

2. Have a BackgrounDRb process upload images to S3 in the background using Paperclip.

Posted in Tutorials. Tagged with .

Customizing validation errors

From my code snippets repo, found this snippet from here - it’s another way of adding your own error messages (or overriding the default ones Rails throws back).

class User < ActiveRecord::Base
attr_accessor :password_confirmation, :mail_confirmation

before_validation_on_create 'self.class.validates_uniqueness_of :name, :message=>“#{self.name} is not available”‘
before_validation_on_create ’self.class.validates_uniqueness_of :mail, :message=>”#{self.mail} is already used by some other user”‘

def validate_on_create
errors.add_on_blank %w( name password mail )
errors.add_on_boundary_breaking(’name’, 5..12) if errors.on(’name’).nil?
errors.add_on_boundary_breaking(’password’, 6..12) if errors.on(’password’).nil?
errors.add(’mail_confirmation’, “and mail do not match.”) unless mail_confirmation == mail
errors.add(’password_confirmation’, “and Password do not match.”) unless password_confirmation == password
end

end

Posted in Tutorials. Tagged with .