Skip to main content

Mobomo webinars-now on demand! | learn more.

Rails’s named_scopes are great, and the ability to chain named_scopes together are even better. I am sure many of us have done something like this before:

   User.active.live_in('Virginia') 

That would give you the active users who live in the state of Virginia. You would define something like these in your User model:

   named_scope :active, lambda { |status| { :conditions => { :status => status } } }   named_scope :live_in lambda { |state| { :conditions => { :state => state } } } 

One recent client project has a model where the database table has 30-something columns. Doing these named_scopes became tedious and repetitive. That makes it not fun anymore.

So Dave Pranata and I wrote the searchable_attributes Rails plugin so we could stop worrying about explicitly defining these named_scopes once and for all.

To use it, you just install it like any other Rails plugin:

 script/plugin install git://github.com/rayvinly/searchable_attributes.git 

The plugin will automatically define a bunch of named_scopes for you if you include this in your model:

 class User < ActiveRecord::Base   searchable_attributes end 

You can then search on your models with something like this:

   def index     @users = User.with_first_name_equal('Gilbert').with_last_name_equal_if_not_null('Arenas').with_address_like('Main Street').with_age_greater_than(25).with_state_equal(['VA', 'MD', 'DC']).with_position_end_with('Guard').with_salary_within_inclusive((100..10000)).with_injured_equal(true).with_join_date_greater_than_or_equal_to(5.years.ago)   end 

Take a look in the plugin’s lib/searchable_attributes.rb for a list of automatically defined named_scopes.

Categories
Author

There is a piece of code that shows up more than 80% of the controllers that I write, and it goes a little something like this:

class UsersController < ApplicationController   def user     @user ||= User.find(params[:id])   end   helper_method :user end

A simple memoization method to allow me to easily grab the parameter-referred user in all of my actions. If I’m using nested routes, that means I can write two, maybe three of these methods into a controller. I’m basically using slight variations on the same code 20 different times in an application. Since we live in a world that loves to be DRY, I thought, “I can do better.”

Fetches: Memoizing Your Parameter Record Retrieval

Fetches is a simple extension to ActionController that lets you simply define those kinds of fetch methods on a one-line command. For the example above, I can rewrite it like so:

class UsersController < ApplicationController   fetches :user end

That’s pretty useful! Not only can I call the “user” method from the controller, but it’s automatically helperized so that I can use the same call in my views. Of course, there are times when more advanced fetching is called for, say using a method other than find or storing to a different variable name. Let’s take a look at a slightly more complex example:

# assuming a route like /users/:user_id/articles/:id class ArticlesController < ApplicationController   fetches :user, :as => :author, :from => :user_id, :using => :find_by_login   fetches :article end

Now if I were to call “author” in any of my controller actions, it would be equivalent to User.find_by_login(params[:user_id]). Similarly, calling “article” is equivalent to Article.find(params[:id]). The “from” option can also take a Proc in case your fetching is not simply a parameter key:

class UsersController < ApplicationController   fetches :user, :from => Proc.new{ |c| c.params[:user_id] || c.params[:id] } end

The main advantages to fetches are brevity, clarity and DRYness. I’ve found that this method covers every use case for parameter-based fetching that I’ve needed, and as such provides a much simpler, more readable, and shorter way to fetch models for use in your controller and views.

Installation

Fetches is available as a gem as well as in traditional plugin format. To install
as a gem, add this to your environment.rb:

config.gem 'mbleigh-fetches', :source => 'http://gems.github.com', :lib => "fetches"

To install it as a traditional plugin:

script/plugin install git://github.com/mbleigh/fetches.git

Resources

The source is available on GitHub, the Acts As Community project is there for general discussion, and the Lighthouse is there for bugs and feature suggestions.

UPDATE: A commenter requested that the plugin be able to handle creation of new records in addition to fetching existing records. I have added in the :initialize option to do just this. Examples:

fetches :user, :initialize => true # initialize from params[:user] fetches :user, :initialize => :author # initialize from params[:author] fetches :user, :initialize => Proc.new{ |c| {:login => c.params[:login], :email => c.params[:email]} }
Categories
Author

Let's face it, iPhone interfaces are awesome, but they can only cater to a fraction of the mobile market. What are you to do if you want to satisfy the rest of the mobile world? Mobile Fu helps to make this job much easier by automatically detecting mobile devices that access your Rails application. People can access your site from a Palm, Blackberry, iPhone, Nokia, etc. and it will automatically adjust the format of the request from :html to :mobile.

Learn Mobile Fu

First off, just install the Mobile Fu plugin into your Rails application.

script/plugin install git://github.com/brendanlim/mobile-fu.git

Start by adding this one line to your ApplicationController.

class ApplicationController < ActionController::Base    has_mobile_fu  end

Once this is in place, any request that comes from a mobile device will be be set as :mobile format. It is up to you to determine how you want to handle these requests by creating the .mobile.erb versions of your views that are to be requested. Also, I recommend that you setup a before_filter that will redirect to a specific page depending on whether or not it is a mobile request. How can you check this?

is_mobile_device? # => Returns true or false depending on the device

You can also determine which format is currently set in by calling the method below.

in_mobile_view? # => Returns true or false depending on current req. format

If you want the ability to allow a user to switch between ˜mobile' and ˜standard' format (:html), you can just adjust the mobile_view session variable in a custom controller action.

session[:mobile_view] # => Set to true if request format is :mobile and false                             if set to :html

 

What About Custom Mobile Styling?

Different devices need different styling. Don't worry though, we've got this baked in to Mobile Fu (thanks to Intridea's own Michael Bleigh, who created Browserized Styles for letting me modify his code). If you are including a css or sass file via stylesheet_link_tag, all you have to do is add _device to the name of one of your files to override your styling for a certain device. The stylesheet that is loaded is dependant on which device is making the request.

Supported stylesheet override device extensions at the moment are: blackberry, iphone, mobileexplorer, nokia, palm

e.g., Accessing a page from a Blackberry.          Ends up loading mobile.css, and mobile_blackberry.css if the file exists.

 

Feature Requests

You can check out Mobile Fu's very own project page at Acts As Community. If you have any problems or would like me to add a certain feature, please create a ticket at http://blim.lighthouseapp.com/projects/14490-mobile-fu/. Also, feel free to fork Mobile Fu and make any enhancements you please from its GitHub location at: http://github.com/brendanlim/mobile-fu/tree/master

Categories
Author

Last week the Uberkit kicked off with some helpers to make your menu-building much easier. This week we’re following it up with UberForms, a Form Builder that DRYs up your repetitive form stresses. Let’s see how it works!

Building an Uber-er Form

UberForms automatically generate all of the standard boilerplate HTML that goes around your forms. By wrapping everything up in an easily style-able package, it becomes a much easier business to make new forms as well as re-use form styling across projects. With the form markup taken care of, you can focus on the more important aspects of your UI building and keep your views deadly clean.

While UberForms are available as a standard form builder (Uberkit::Forms::Builder), you may find it more useful in its helper form (automatically available when the UberKit plugin is loaded:

<% uberform_for :user do |f| %>   <%= f.text_field :login %>   <%= f.password_field :password %>   <%= f.submit "Submit"%> <% end %>

This will automatically be translated into some nice, CSS-ready HTML:

<form method="post" class="uberform" action="/users">   <div class="field_row">     <label for="user_login">Login:</label>     <input type="text" size="30" name="user[login]" id="user_login" class="text_field"/>     <br/>   </div>   <div class="field_row">     <label for="user_password">Password:</label>     <input type="password" size="30" name="user[password]" id="user_password" class="password_field"/>     <br/>   </div>   <button type="submit">Submit</button> </form>

You can also change the label, add a description or help text to a field by adding the relevant options:

<%= f.text_field :login, :label => "Username",                           :help => "Only a-z and underscores.",                           :description => "The name you will use to log in." %>

Becomes…

<div class="field_row">   <label for="user_login">Username:</label>   <input type="text" size="30" name="user[login]" label="Username" id="user_login" help="Only a-z and underscores." description="The name you will use to log in." class="text_field"/>   <span class="help">Only a-z and underscores.</span>   <span class="description">The name you will use to log in.</span>   <br/> </div>

Finally, you can create custom HTML inside an UberForm field by passing a block:

<% f.custom :label => "State", :for => "user_state" do |f| %>   <%= state_select :user, :state %> <% end %>

Becomes…

<div class="field_row">   <label for="user_state">State:</label>   <div class="pseudo_field">     <select id="user_state">...</select>   </div>    <br/> </div>

Easy, right? That’s all there is to it, now you can be UberForming to your heart’s content

Installation

To install the UberKit (which includes more than just forms) you can do so either as a gem or a traditional plugin. As a gem, just add this to your environment.rb:

config.gem 'mbleigh-uberkit', :lib => 'uberkit', :source => 'http://gems.github.com'

As a traditional Rails plugin:

script/plugin install git://github.com/mbleigh/uberkit.git

The Future of the UberKit

These two pieces are pretty helpful, but there’s more coming for the UberKit. Stay tuned for more updates, including more hooks and ways to customize the UberKit to fit your needs as a developer.

Categories
Author

So many of components we build into our web applications have a grain of an extractable element, a standardization waiting to happen. Starting today, I am putting together a “Standard UI Kit” for all of the tools that help me build interfaces faster. Together, they are called the UberKit. This week, the first segment is coming: UberMenus.

UberMenu: Abstract Menu Generation

Most people who build interfaces will build their menus with the same structure over and over. I finally took the time to abstract this out into a single helper that can pretty much serve all of my navigational needs. Here’s how you use it in a view:

<% ubermenu do |m| %>   <% m.action 'Home', '/' %>   <% m.action 'Users', users_path %>   <% m.action 'Log Out', logout_path, :class => "special" %> <% end %>

Becomes this HTML (assuming you’re at the document root):

<ul>   <li class="first current first_current"><a href="/">Home</a></li>   <li><a href="/users">Users</a></li>   <li class="special last"><a href="/logout">Log Out</a></li> </ul>

The current class will automatically be set on whichever page responds to the built-in Rails helper current_page? and the action syntax behaves just like a link_to. If a given action has multiple classes, they will also be joined with underscores as an additional class for browsers that do not support multiple class declarations. But in addition to easily creating simple menus, you can also easily generate multi-level navigation menus:

<% ubermenu 'nav' do |m| %>   <% m.action 'Home', home_path %>   <% m.submenu 'Services', services_path do |s| %>     <% s.action 'Service A', service_path('a') %>     <% s.action 'Service B', service_path('b') %>   <% end %> <% end %>

Which will become this HTML:

<ul id='nav'>   <li class='first current first_current'><a href="/">Home</a></li>   <li class='last'><a href="/services">Services</a>     <ul>       <li><a href="/services/a">Service A</a></li>       <li><a href="/services/b">Service B</a></li>     </ul>   </li> </ul>

Installation

UberKit is available both as a gem and a traditional plugin. For the gem version, add this to your environment.rb:

config.gem 'mbleigh-uberkit', :source => "http://gems.github.com/", :lib => "uberkit"

Or as a traditional plugin:

script/plugin install git://github.com/mbleigh/uberkit.git

Future of the UberKit

While UberMenu is a useful tool, the UberKit will continue to grow over time, so stay tuned for additions (next on the slate: UberForm). It may also grow to include some common styles and javascripts that can be used in conjunction with the helpers to provide an even easier track to a full-fledged UI.

Resources

As always, the source is on GitHub and there is an Acts As Community Profile available as well. If you have any problems with it or would like to request new features, enter them on the Lighthouse project.

Categories
Author

Almost every Rails developer has written an app with more than one user role, but when is_admin? isn’t enough, where do you go? RankFu aims to solve this by giving you a rich toolset for roles. Now it’s free to allow users to have more than one role, so you can have modifiers such as ‘Trusted’ and ‘New’ trivially. It’s also easy to have sets of roles, with administrators outranking moderators. Sets are optional though, so you’re free not to put Telephone Sanitizers in one due to their obvious lack of importance.

Throughout this piece, I’ll be talking about the ubiquitous User model, but you can use RankFu with any model you wish to.

Rankin’ Fu

When you install RankFu, you can add several new methods to your models:

 User#has_role?  User#has_role User#"#{role}?" User#"#{role}_exactly?"	#This forces an exact comparison, useful if you want to test for a role which has a superset (e.g. moderators and  administrators) User#"make_#{role}           User#"remove_#{role}"  User#rank				#Returns a string listing all roles, modifiers first eg: "Trusted Administrator"  User#roles				#Returns an array of role names. 

There is also some sugar for disabling users, to make your code more readable. These examples assume the existence of role with :disabled as its key:

 User#disable_user  User#enable_user User#enabled?                        

Building on this, you can easily clean up your views. I DRY’d up many instances login check logic.

 def logged_in_as_mod? def logged_in_as?(user)   def logged_in_as_friend_of?(user)   def logged_in_as_or_as_friend_of?(user)    

There might have been a few others, but I don’t want to give any more ammunition to those who accuse me of pedantry.

InstallationFu

The easiest way to install is as a standard Rails plugin:

script/plugin install git://github.com/mjt/rank_fu.git

After installing this plugin, you’ll want to start by performing some admin:

 script/generate rank_fu user script/generate rank_fu roles 		#create migration for roles  

After that, you’ll probably want to add roles to your user model, so just add this line:

knows_rank_fu

Then you need to create some roles. I suggest using the excellent Seed Fu plugin so you can do something like this:

 Role.destroy_all roles = [ {:id => 1, :key => "root",      :name=> "Superuser", :value => 2**22, :set => 1}, 	      {:id => 2, :key => "admin",     :name=> "Administrator", :value => 2**21, :set => 1}, 	      {:id => 3, :key => "moderator", :name=> "Editor", :value => 2**20, :set => 1},      	      {:id => 5, :key => "member",    :name=> "Member", :value => 2**10, :is_default => true}]  roles.each do |role|                             Role.create role end 

What’s going on here?

Not much.

Internally, bitwise arithmetic is used to store each model’s role-state. Once a model knows RankFu, you can assign and remove roles freely, knowing that RankFu is a good citizen and uses update_attribute internally.

You may also find that you can reduce your use of STI by separating users by roles.

Please feel free to leave any feedback in the comments.

Categories
Author

RSpec is a great tool that has come to replace Test::Unit for many Rails developers. Autotest makes it go even faster, and has become an indispensable part of my development environment. However, it has always been somewhat-to-extremely difficult to use RSpec when developing Rails plugins. In this post I will walk through step-by-step how to get RSpec and Autotest working with your plugin.

This plugin is assuming that you are running Rails >= 2.1 and have already installed RSpec and RSpec::Rails as plugins in your Rails project like so:

script/plugin install git://github.com/dchelimsky/rspec.git script/plugin install git://github.com/dchelimsky/rspec-rails.git

And also gotten RSpec up and running by calling script/generate rspec.

Generate It

Luckily, I wasn’t the first person who ever wanted to create a plugin that was tested with RSpec. The Rspec Plugin Generator will do most of the heavy lifting for us when we start out. Just install it like so:

script/plugin install git://github.com/pat-maddox/rspec-plugin-generator.git

And you’re ready to get started. I’m assuming here that this is a brand new plugin, if it’s already in development you may need to run this in a fresh directory and then copy/paste files as needed to glue it together. Let’s say I’m writing a plugin called new_fu. I can generate an RSpec-ready plugin simply by calling:

script/generate rspec_plugin new_fu

This will generate the standard plugin structure as well as some extra files:

create  vendor/plugins/new_fu/spec create  vendor/plugins/new_fu/spec/spec_helper.rb create  vendor/plugins/new_fu/spec/new_fu_spec.rb

You can take a look at these to see how they work, but pretty simply they hook your plugin up so that it can be run with rake spec:plugins. Let’s add a simple example to our new_fu_spec.rb file:

require File.dirname(__FILE__) + '/spec_helper'  describe "NewFu" do   it "should have a pending spec" end

Now if you run rake spec:plugins you should see one pending spec. Congratulations, your plugin is now running on RSpec!

Autotest Like a Champ

Ok, so now we’re up and running with RSpec on our plugin. That’s great, but if you have several plugins in the same Rails app that all have specs, it starts to get messy when you run that rake spec:plugins. Not to mention how long it takes between runs! We need to get an autotest setup like we have for our main Rails app!

I struggled with getting this to work for a long time, so thanks to this post on Rails Symphonies for finally pointing me in the right direction. First we need to create an autotest/discover.rb file in our plugin’s lib directory. In that file, put this code:

$:.push(File.join(File.dirname(__FILE__), %w[.. .. rspec]))       Autotest.add_discovery do     "rspec"  end

This gets us almost exactly where we want to be. However, the first time I ran it I had two problems: some specs that I had written were strangely failing, and it wasn’t in color or following the rest of my spec.opts preferences from my main app!

To remedy this, we need a spec.opts in the spec directory of the plugin. You can either copy and paste it in from your Rails app (my recommendation if you are publishing your plugin) or you can just create a softlink back to it:

ln -s ../../../../spec/spec.opts spec.opts

That’s it! Now if you run autotest you should be running all of the specs for your plugin just as you would if you were running them for your app. Note that this doesn’t hook in to your app’s autotest, which may be desirable or undesirable to your specific needs.

Categories
Author

An extremely common practice for Rails applications is to provide keyed
access through subdomains (i.e. http://someaccount.awesomeapp.com/). However,
there has never been a real unified convention for handling this functionality.
DHH’s Account Location
works for some circumstances but is more tailored for a Basecamp domain model
(i.e. the app is on a separate domain from all other functionality, so you
can always expect a subdomain) than the more common usage of one domain only.

SubdomainFu aims to provide a simple, generic toolset for dealing with subdomains
in Rails applications. Rather than tie the functionality to something specific
like an account, SubdomainFu simply provides a foundation upon which any
subdomain-keyed system can easily be built.

Usage Fu

SubdomainFu works by riding on top of the URL Rewriting engine provided with
Rails. This way you can use it anywhere you normally generate URLs: through
url_for, in named routes, and in resources-based routes. There’s a small
amount of configuration that is needed to get you running (though the defaults
should work for most).

To set it up, you can modify any of these settings (the defaults are shown):

# in environment.rb    # These are the sizes of the domain (i.e. 0 for localhost, 1 for something.com) # for each of your environments SubdomainFu.tld_sizes = { :development => 0,                           :test => 0,                           :production => 1 }  # These are the subdomains that will be equivalent to no subdomain SubdomainFu.mirrors = ["www"]  # This is the "preferred mirror" if you would rather show this subdomain # in the URL than no subdomain at all. SubdomainFu.preferred_mirror = "www"

Now when you’re in your application, you will have access to two useful
features: a current_subdomain method and the URL Rewriting helpers.
The current_subdomain method will give you the current subdomain or
return nil if there is no subdomain or the current subdomain is a mirror:

# http://some_subdomain.myapp.com/ current_subdomain # => "some_subdomain"  # http://www.myapp.com/ or http://myapp.com/ current_subdomain # => nil  # http://some.subdomain.myapp.com current_subdomain # => "some.subdomain"

The URL rewriting features of SubdomainFu come through a :subdomain option
passed to any URL generating method. Here are some examples (in these examples,
the current page is considered to be ‘http://intridea.com/’):

url_for(:controller => "my_controller",    :action => "my_action",    :subdomain => "awesome") # => http://awesome.intridea.com/my_controller/my_action    users_url(:subdomain => false)  # => http://intridea.com/users  # The full URL will be generated if the subdomain is not the same as the # current subdomain, regardless of whether _path or _url is used. users_path(:subdomain => "fun") # => http://fun.intridea.com/users users_path(:subdomain => false) # => /users

While this is just a simple set of tools, it can allow the easy creation
of powerful subdomain-using tools. Note that the easiest way to locally
test multiple subdomains on your app is to edit /etc/hosts and add
subdomains like so:

127.0.0.1	localhost subdomain1.localhost subdomain2.localhost www.localhost

Adding an entry for each subdomain you want to use locally. Then you need
to flush your local DNS cache to make sure your changes are picked up:

sudo dscacheutil -flushcache

Installation

SubdomainFu is available both as a traditional plugin and as a GemPlugin
for Rails 2.1 and later. For a traditional plugin, install like so:

script/plugin install git://github.com/mbleigh/subdomain-fu.git

For a GemPlugin, add this dependency to your environment.rb:

config.gem 'mbleigh-subdomain-fu', :source => "http://gems.github.com/", :lib => "subdomain-fu"

Implementing A Simple Account Key System

Let’s take this functionality and implement a simple account-key system based
off of the subdomain. We’ll start with some controller code (assuming that
we have an Account model with a ‘subdomain’ field):

class ApplicationController < ActionController::Base   protected      # Will either fetch the current account or return nil if none is found   def current_account     @account ||= Account.find_by_subdomain(current_subdomain)   end   # Make this method visible to views as well   helper_method :current_account      # This is a before_filter we'll use in other controllers   def account_required     unless current_account       flash[:error] = "Could not find the account '#{current_subdomain}'"       redirect_to :controller => "site", :action => "home", :subdomain => false     end   end end

That’s really all we need for a basic setup, now let’s say we have a
ProjectsController that you must specify an account to access:

class ProjectsController < ApplicationController   # Redirect users away if no subdomain is specified   before_filter :account_required end

There’s lots more you can do with the plugin, but this is a simple use case
that everyone can relate to.

Resources and Plans

A feature that I hoped would make it to the first release of SubdomainFu
but is now a planned feature is subdomain-aware routing so that you can
add conditional subdomain routes to your routes.rb file. Keep an eye
out for more on that in the future.

In the meantime, the project will live at its home on Acts As Community for intermittent
updates, is available on GitHub as always, and bugs/feature requests may
be passed on through the Lighthouse.

Categories
Author

The new Gem Dependencies of Rails 2.1 give developers an easier-than-ever ability to keep track of and maintain the various library dependencies inherent with any project. However, a much-overlooked additional feature of the Gem Dependencies is the ability to package traditional Rails plugins as a gem and have them hooked in properly. This article is designed as an introduction to how to write and use plugins as gems in Rails projects.

The Basics

The basic method by which this is achievable is that any plugin included through a config.gem command will automatically have the gem-packed file rails/init.rb run upon Rails’s initialization. All it takes is a little bit of effort, and any Rails plugin can be packaged as a gem and easily depended upon through gem dependencies.

You may be wondering why this is a “big deal.” Plugins are already dead simple to install in Rails (and you can even script/plugin install straight from Git now!), why do we need GemPlugins? It’s simple, really: RubyGems are a rock-solid established way of easily distributing versioned reusable bits of code. Using gems for plugins allows for a greater standardization of the way in which plugins are maintained and distributed, as well as a simple path for version-locking to ensure compatibility with legacy code etc.

Another reason that GemPlugins are important is that they provide a level of abstraction from Rails: by releasing a gem rails/init.rb you could also use the same exact code to release a Merb plugin or any other framework that supports gemified add-ons. I think you will begin to see a number of cross-framework plugins be developed as Rails gets some company and shares alike.

Using a GemPlugin

First, let’s go through the process required to use an existing gem plugin. I’m going to be using my Acts As Taggable On plugin as an example throughout because I just recently went through the process of making it available as a gem.

First, you will need to include the dependency in your environment.rb file. I’m assuming here that most plugins are going to be hosted on GitHub, but the same should be true for any gem source.

# in environment.rb    config.gem "mbleigh-acts-as-taggable-on", :source => "http://gems.github.com", :lib => "acts-as-taggable-on"

This is the standard usage of gem dependencies, and for more info on this you can see Ryan Daigle’s post or watch the RailsCast on the subject. Now assuming that you don’t already have the gem in question installed, it’s simple to grab it:

rake gems:install

This will automatically install any gem dependencies in your project, and will tell you what’s happening the same as if you had run gem install from the command line.

That’s it! Once you have successfully installed the necessary gem, you can simply start up your Rails server and the plugin will be loaded and initialized as though it were living in your vendor/plugins directory.

Now that you know how to use a GemPlugin, I’ll show you how you can take an existing plugin and gemify it quickly and painlessly.

Making a GemPlugin

Let’s say I have a plugin called awesome_fu that lives on GitHub at mbleigh/awesome-fu. I’ve already released this plugin, it works great, and now I want to make it compatible with GemPlugins.

First, let’s create a gemspec called awesome-fu.gemspec in line with the requirements for the GitHub Gem Repository. In order to make the file list, I usually find it’s easiest to “find **” in the plugin directory, then copy it into TextMate, make the modifications I need for manifest (using a regular expression to quote each of the files), and saving it in the spec. If you have only a few files in your plugin, it may be easier just to add them by hand.

Next we need to add rails/init.rb. This is a little bit troublesome, because we still want our plugin to work if installed through the traditional method, so we also need init.rb to run the same code (this is automatically fine in edge Rails). What I did for my plugin is copy all of my init.rb code into rails/init.rb and then change init.rb to the following:

require File.dirname(__FILE__) + "/rails/init"

Now they both run the same code without any kind of replication, great! This means that now I have set up my plugin to work equally as a GemPlugin or a traditional plugin with just a couple minutes of work.

All that’s left to do is switch on the RubyGem setting for my GitHub project, update the README, and push! Now anyone will be able to easily require the plugin as a gem dependency and you will get all of the accolades associated with releasing your plugin the “new and hip” way.

Caveat Coder

The one problem with GemPlugins that I have run into is that if you unpack your gems using “rake gems:unpack” the rails/init.rb file is not run on initialization. This is a known issue that is supposed (?) to be resolved but I have still experienced this problem in my experiments. Hopefully this issue will be fully resolved in edge Rails soon and the glorious future of GemPlugins can begin.

Categories
Author

Acts As Taggable On (original post here), the tagging plugin with custom tag contexts, has gathered up some great new features over the past weeks thanks to the efforts of the community as well as fellow Intrideans Pradeep Elankumaran and Brendan Lim. I just wanted to take this opportunity to go over some of what’s new and interesting in the world of acts_as_taggable_on.

Community Fixes

First, Peter Cooper was kind enough to submit a patch that allows acts_as_taggable_on to work with Rails 2.1’s named_scope when using find_options_for_tag_counts.

Secondly, the much requested support for Single Table Inheritance is finally in! It was just a matter of using a class inheritable attribute instead of a class instance variable, and big thanks to slainer68 for hunting that down and taking the time to submit a patch.

If there’s anything you’ve hacked on to Acts As Taggable On, I urge you to submit a patch to the Lighthouse Project. I try to get new patches integrated into the codebase as quickly as possible, so please do submit anything!

During the Community Code Drive at RailsConf two great features were added: taggers and related objects.

Taggers

Tags can now have ownership, allowing for such things as User-tracked tags and more. This was a requested feature and something that I’d been looking forward to myself. Here’s the usage:

class User < ActiveRecord::Base   acts_as_tagger end  class Photo < ActiveRecord::Base   acts_as_taggable_on :locations end  @some_user.tag(@some_photo, :with => "paris, normandy", :on => :locations) @some_user.owned_taggings @some_user.owned_tags @some_photo.locations_from(@some_user)

Find Related

Another request (and another great idea) is the ability to find related objects by similar tags. This is now available through the @object.find_related_on_tags syntax:

@bobby = User.find_by_name("Bobby") @bobby.skill_list # => ["jogging", "diving"]  @frankie = User.find_by_name("Frankie") @frankie.skill_list # => ["hacking"]  @tom = User.find_by_name("Tom") @tom.skill_list # => ["hacking", "jogging", "diving"]  @tom.find_related_on_skills # => [<User name="Bobby">,<User name="Frankie">] @bobby.find_related_on_skills # => [<User name="Tom">]  @frankie.find_related_on_skills # => [<User name="Tom">] 

Gemified!

Acts As Taggable On now works as a GemPlugin in Rails. This is a new way (as of Rails 2.1) of distributing plugins as gems and having them still automatically link up and do their magic. To use it as a gem, add it to your config/environment.rb like so:

config.gem "mbleigh-acts-as-taggable-on", :source => "http://gems.github.com", :lib => "acts-as-taggable-on"

Now you should be able to get the latest version of the plugin just by running rake gems:install. However, this hasn’t been working for me so the alternative is just to install the gem directly:

gem install mbleigh-acts-as-taggable-on --source http://gems.github.com/

Now when you run your Rails app, even though it’s not in vendor/plugins it should be running! To make sure, look for this line on startup:

** acts_as_taggable_on: initialized properly

There are still a couple of issues outstanding in Rails regarding GemPlugins (if you unpack it, it will not run the initialization properly for some reason), but I wanted to give everyone the latest and greatest way to install the plugin possible. It will still work fine using the conventional methods as well.

Community and Future

I’ve been really happy with the response and support of the community, and I would like to do everything possible to cultivate future participation. To that end, I have created an Acts As Community Project for acts_as_taggable_on that will hopefully provide some casual communication about the project. Feel free to post on the wall or in the forums, and look out for additions soon.

Finally, the area of the plugin that still needs some work is tag caching. This is not a particular area of my expertise, so I’m hoping that someone from the community will write up some specs that flesh out the caching functionality in new and interesting ways.

Thanks for all of the patches, and I hope you continue to enjoy using Acts As Taggable On!

UPDATE (6/10/08): The improvements keep on rolling! After writing the post, I went off on a tangent and decided to make the plugin work both traditionally and as a gem. See more details above in the “Gemified” section.

Categories
Author
Subscribe to Plugins