I've seen many Railers' codes tempt to shove many logic in View and Controller. Allow me to add these important emphases:
Skinny Controller Fat Model which is one of The Elegant Rails Way principles.
And Testing.
Here I have products_controller.rb file inside app/controllers directory that contains this:
class ProductsController < ApplicationController
def search
@products = Product.search(params)
end
end
I put the logic inside the Model. Here is my product.rb file inside app/models directory:
class Product < ActiveRecord::Base
scope :nm, lambda { |n| where "name LIKE ?", '%' + n + '%' }
scope :criteria, lambda { |n, type| nm(n).where type_id: type }
def self.search(opts={})
return all if opts.empty?
return nm(opts[:search_keywords]) unless opts[:search_keywords].nil?
return criteria(opts[:name], opts[:type]) if opts[:product].try(:[], :type).empty?
end
end
You could also try to experiment Dynamic Scope Construction to improve Product Model code above. I haven't tried it successfully AND efficiently here, let me know if anyone can make it better, anyway.
And Testing. You can also test your app using RSpec, Cucumber, Ruby Selenium, Watir, or any other Test for Ruby Environment, but here I'll only show you how to do it using Ruby Unit Test. Here is my product_test.rb inside test/unit directory:
require 'test_helper'
require 'product'
class ProductTest < ActiveSupport::TestCase
fixtures :products
def test_nm
assert_equal Product.nm("Pick").first.name, products(:pickaxe).name
end
def test_criteria
assert_equal Product.criteria("Bach", 2).first.name, products(:bach).name
end
def test_search
assert_equal Product.search(:search_keywords => "Pick").first.name,
products(:pickaxe).name
assert_equal Product.search(:search_keywords => "Pick", :type => 1).first.name,
products(:pickaxe).name
assert_equal Product.search.size, Product.count
end
end
And its related fixture. Here is my products.yml file inside test/fixtures directory:
pickaxe:
name: Pickaxe 3
type_id: 1
bach:
name: Bach
type_id: 2
lotr:
name: LOTR
type_id: 3
And for controller testing, here is my products_controller_test.rb file inside test/functional directory:
require 'test_helper'
require "products_controller"
class ProductsControllerTest < ActionController::TestCase
fixtures :products
test "should get search" do
get :search, :search_keywords => 'Pick', :product => { :type => '1' }
assert_response :success
assert_not_nil assigns(:products)
end
end
Let's go test them. I use:
. ruby -v
ruby 1.9.2p0 (2010-08-18) [i386-darwin9.8.0]
. rails -v
Rails 3.0.9
Here is my test's result:
. rake test
(in /Users/arie/se/tester)
NOTICE: CREATE TABLE will create implicit sequence "products_id_seq" for serial column "products.id"
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "products_pkey" for table "products"
Loaded suite /opt/experiment/ruby/lib/ruby/1.9.1/rake/rake_test_loader
Started
...
Finished in 0.237899 seconds.
3 tests, 5 assertions, 0 failures, 0 errors, 0 skips
Test run options: --seed 54809
Loaded suite /opt/experiment/ruby/lib/ruby/1.9.1/rake/rake_test_loader
Started
.
Finished in 0.330431 seconds.
1 tests, 2 assertions, 0 failures, 0 errors, 0 skips
Test run options: --seed 25681
Alright! Worked like a charm! 0 errors for both unit (for model) and functional (for controller) tests. I also tested it from my browser and it worked.
You can also download my copy files here.
Links:
- Testing Reference
- Beginning with Cucumber RailsCasts
- Active Record Query Interface 3.0
- The Skinny on Scopes (Formerly named_scope)