Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Added support for automatic initialization using :initialize_with.

Changed the public method name to simply be #find_param.
Refactored to remove dependence on class inheritable attributes.
Improved the documentation.
  • Loading branch information...
commit 085b4bc66aea7723a7880b9d2fc33eaa8a19a28a 1 parent 5d01367
Tyler Hunt authored
View
60 README
@@ -1,16 +1,54 @@
-This is a silly plugin to let you take a shortcut. Here's the blog post example:
+Find-Param
+==========
-class Post < ActiveRecord::Base
- define_find_param :slug
-end
+This is a simple plugin to let you easily define a parameter to use for
+#to_param, and also define a finder to access that same parameter.
-This will set the to_param to be post.slug and add the following method to the Post model:
+This plugin is most useful for situations where you want to override the usage
+of ID as the default parameter value. Using a slug creates URLs that can be
+friendlier for humans and bots alike.
-def self.find_by_param(*args)
- find_by_slug(*args)
-end
-You can also pass :raise_on_not_found => true to have it raise ActiveRecord::RecordNotFound
-if the result set is empty.
+Getting Started
+---------------
-Like I said, it's silly, but I find myself using this pattern a lot.
+Here we define the find param to be a slug:
+
+ class Post < ActiveRecord::Base
+ find_param :slug
+ end
+
+This will set the #to_param to be #slug and add a finder to the model called
+#find_by_param that can be used in your controllers to fetch records using
+params[:id].
+
+ class PostController
+ before_filter :find_post, :only => %(show edit update destroy)
+
+ def find_post
+ find_by_param(params[:id])
+ end
+ end
+
+
+Options
+-------
+
+To have your find param automatically populated on record creation, use the
+:initialize_with option.
+
+ class Post < ActiveRecord::Base
+ find_param :slug, :initialize_with => :title
+ end
+
+This will use the title property to populate the slug. By default this value is
+lowercased and all whitespace and special characters are replaced by hyphens.
+To specify your own formatted for the find param, there a :using option that
+accepts a proc.
+
+ class Post < ActiveRecord::Base
+ find_param :slug, :initialize_with => :title, :using => Proc.new { |value| value.upcase }
+ end
+
+You can also pass :raise_on_not_found => true to have it raise an
+ActiveRecord::RecordNotFound erorr when the result set is empty.
View
3  init.rb
@@ -1,2 +1,3 @@
require 'find_by_param'
-ActiveRecord::Base.send(:extend, FindByParam::ClassMethods)
+
+ActiveRecord::Base.send(:extend, FindByParam::ClassMethods)
View
73 lib/find_by_param.rb
@@ -1,43 +1,52 @@
module FindByParam
-
- ##
# Catch-all error for any issue arrising within the FindByParam plugin.
- #
class Error < RuntimeError; end
-
- ##
- # Raised when the param requested is not in the model's table definition.
- # For example:
- #
- # class WillRaiseError < ActiveRecord::Base
- # define_find_param :undefined_column
- # end
+
+ # Raised when the param requested is not in the model's table definition:
#
+ # class WillRaiseError < ActiveRecord::Base
+ # define_find_param :undefined_column
+ # end
class ColumnNotFoundError < Error; end
-
+
module ClassMethods
- def define_find_param(param, options={})
- param = param.to_s
- options[:raise_on_not_found] ||= false
- if column_names.include?(param)
- write_inheritable_attribute :find_parameter, param
- write_inheritable_attribute :find_param_options, options
- bl = lambda do |args|
- results = send("find_by_#{read_inheritable_attribute(:find_parameter)}", *args)
- raise ActiveRecord::RecordNotFound if options[:raise_on_not_found] && (results.nil? or (results.is_a?(Array) && results.size == 0))
- return results
+ # Defines a finder (#find_by_param) using the specified parameter name.
+ # Also, defines #to_param to use the same parameter.
+ #
+ # Options:
+ # * <tt>:raise_on_not_found</tt>: cases ActiveRecord::RecordNotFound to be raised when no record is found
+ # * <tt>:initialize_with</tt>: the name of parameter to use to initialize the find parameter when a new record is created
+ # * <tt>:using</tt>: a proc used to manipulated the initialization parameter before setting the find parameter
+ def find_param(param, options={})
+ raise_on_not_found = options.delete(:raise_on_not_found)
+ initialize_with = options.delete(:initialize_with)
+ raise ColumnNotFoundError unless column_names.include?(param.to_s)
+
+ self.class.send(:define_method, :find_by_param) do |args|
+ returning send("find_by_#{param}", *args) do |results|
+ raise ActiveRecord::RecordNotFound if raise_on_not_found && (results.nil? || (results.is_a?(Array) && results.size == 0))
end
- self.class.send(:define_method, 'find_by_param', &bl)
- else
- raise ColumnNotFoundError
end
- self.send(:include, FindByParam::InstanceMethods)
+
+ self.send(:alias_method, :to_param, param)
+
+ initialize_parameter(param, initialize_with, options) if initialize_with
end
- end
-
- module InstanceMethods
- def to_param
- self.send(self.class.read_inheritable_attribute(:find_parameter))
+
+ def initialize_parameter(param, source, options={})
+ raise ColumnNotFoundError unless column_names.include?(source.to_s)
+ using = options.delete(:using)
+
+ self.send(:define_method, :set_param) do
+ value = self.send(source)
+ value = using.respond_to?(:call) ? using.call(value) : value.downcase.gsub(/[^\w]+/, '-')
+
+ self.send("#{param}=", value)
+ end
+ self.send(:private, :set_param)
+
+ self.send(:before_create, :set_param)
end
+ private :initialize_parameter
end
-end
+end
View
63 test/find_by_param_test.rb
@@ -2,48 +2,75 @@
class FindByParamTest < Test::Unit::TestCase
def setup
- BlogPost.create(:slug => 'adam-west', :title => 'Adam West')
- BlogPost.create(:slug => 'burt-ward', :title => 'Burt Ward')
- BlogPost.send(:define_find_param, 'slug')
+ Post.create(:slug => 'adam-west', :title => 'Adam West')
+ Post.create(:slug => 'burt-ward', :title => 'Burt Ward')
+ Post.find_param(:slug)
end
def teardown
- BlogPost.delete_all
+ Post.delete_all
end
def test_plugin_loaded_correctly
- assert_kind_of FindByParam::ClassMethods, BlogPost
- assert BlogPost.respond_to?(:find_by_param)
+ assert_kind_of FindByParam::ClassMethods, Post
+ end
+
+ def test_find_by_param_was_defined
+ assert Post.respond_to?(:find_by_param)
+ end
+
+ def test_find_by_param_is_defined_in_subclasses
+ assert Blog.respond_to?(:find_by_param)
end
def test_returns_valid_data
- bp = BlogPost.find(:first, :conditions => 'slug = "adam-west"')
- assert_equal BlogPost.find_by_param('adam-west'), bp
+ post = Post.find_by_param('adam-west')
+ assert_equal Post.find_by_slug('adam-west'), post
end
- def test_can_define_find_parameter
- BlogPost.send('define_find_param', 'title')
- bp = BlogPost.find(:first, :conditions => {:slug => 'adam-west'})
- assert_equal BlogPost.find_by_param('Adam West'), bp
+ def test_can_define_find_parameter_with_symbol
+ Post.find_param(:title)
+ post = Post.find_by_param('Adam West')
+ assert_equal Post.find_by_title('Adam West'), post
end
+ def test_can_define_find_parameter_with_string
+ Post.find_param('title')
+ post = Post.find_by_param('Adam West')
+ assert_equal Post.find_by_title('Adam West'), post
+ end
+
def test_correctly_goes_to_param
- bp = BlogPost.find(:first, :conditions => {:slug => 'adam-west'})
- assert_equal bp.to_param, 'adam-west'
+ post = Post.find(:first, :conditions => { :slug => 'adam-west' })
+ assert_equal 'adam-west', post.to_param
end
def test_raises_on_not_found_if_specified
- BlogPost.send(:define_find_param, 'slug', :raise_on_not_found => true)
+ Post.find_param('slug', :raise_on_not_found => true)
assert_raises ActiveRecord::RecordNotFound do
- BlogPost.find_by_param('in ur tests, failing')
+ Post.find_by_param('non-existent-slug')
end
end
def test_raises_column_not_found_error_when_given_undefined_column
assert_raise(FindByParam::ColumnNotFoundError) do
- BlogPost.send(:define_find_param, 'bad_column_name')
+ Post.find_param(:bad_column_name)
end
+ end
+
+ def test_column_not_found_error_is_a_find_by_param_error
assert_kind_of FindByParam::Error, FindByParam::ColumnNotFoundError.new
end
-end
+ def test_initializes_param_on_create
+ Post.find_param(:slug, :initialize_with => :title)
+ blog_post = Post.create(:title => 'A Test Post')
+ assert_equal 'a-test-post', blog_post.slug
+ end
+
+ def test_initializes_param_on_create_using_a_custom_initializer
+ Post.find_param(:slug, :initialize_with => :title, :using => Proc.new { |value| value.upcase.gsub(/\s+/, '_') })
+ blog_post = Post.create(:title => 'A Test Post')
+ assert_equal 'A_TEST_POST', blog_post.slug
+ end
+end
View
13 test/test_helper.rb
@@ -1,4 +1,4 @@
-$LOAD_PATH.unshift 'lib/'
+$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
require 'rubygems'
require 'multi_rails_init'
@@ -15,17 +15,16 @@
RAILS_ROOT = '.' unless defined? RAILS_ROOT
RAILS_ENV = 'test' unless defined? RAILS_ENV
-
ActiveRecord::Base.send(:extend, FindByParam::ClassMethods)
-ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :dbfile => ":memory:")
+ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :dbfile => ':memory:')
ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Schema.define(:version => 1) do
- create_table :blog_posts do |t|
- t.column :slug, :string
+ create_table :posts do |t|
t.column :title, :string
+ t.column :slug, :string
end
end
-class BlogPost < ActiveRecord::Base
-end
+class Post < ActiveRecord::Base ; end
+class Blog < Post ; end
Please sign in to comment.
Something went wrong with that request. Please try again.