Download - Rails 3 Beginner to Builder 2011 Week 7
July, 2011
Beginner to BuilderWeek 7
Sunday, July 31, 11
@Schneems
Rails - Week 7• SQL Crash Course
• where & find
• method_missing
• scopes and class methods
• Pagination
• full text search
• Rake vs. Script/Runner
Sunday, July 31, 11
• Structured Query Language
• Used to manage data in RDBMS (relational database management system)
• Active Record hides our SQL
@Schneems
SQL
User.where(:username => "foo").to_sql
=> SELECT "users".*
FROM "users" WHERE
"users"."username" = 'foo'
Sunday, July 31, 11
• find by uniqe id, or ids
• find_by_#{:column_name}
@Schneems
Find
User.find(1)
User.find(1,2,3)
User.find_by_username('schneems')
Sunday, July 31, 11
@Schneems
Method Missing
User.foo_foo
NoMethodError: undefined method `foo_foo' for #<Class:
0x105187be8>
Sunday, July 31, 11
@Schneems
Method Missingclass User
def self.method_missing(method_name, *args, &block)
puts "You just called #{method_name}"
end
end
User.foo_foo
>> "You just called foo_foo"
Sunday, July 31, 11
@Schneems
Method Missingclass User < ActiveRecord::Base
def self.method_missing(method_name, *args, &blk)
if method_name.to_s =~ /^foo_foo_find_by_.*/
column = method_name.to_s.gsub("foo_foo_find_by_", "")
User.where(column.to_sym => args.first)
else
super
end
end
end
User.foo_foo_find_by_username("schneems")
Sunday, July 31, 11
• Active Record’s Query API
@Schneems
Active Record
find
where (:conditions)
includes (:include)
group
order
limit
offset
joins
having
http://m.onkey.org/active-record-query-interface
Sunday, July 31, 11
@Schneems
Order & Offset# Order# defaults to ascending
User.order(:created_at).first
#<User id: 1, ...
User.order('created_at DESC').first
#<User id: 55, ...
# Offset
User.order(:created_at).offset(1).first
#<User id: 2, ...
User.order(:created_at).offset(2).first
#<User id: 3, ...
Sunday, July 31, 11
@Schneems
Limit & Count
# Limit
User.limit(3).all
[ #<User ... >, #<User ... >, #<User ... > ]
# Count
User.count
=> 55
Sunday, July 31, 11
• Group
@Schneems
Group
# Group
User.group(:hometown).count(:hometown)
#<OrderedHash {
"Austin, Texas"=>28,
"Alexandria, MN"=>1,
"Warsaw, Poland"=>1,
# ...
}
Sunday, July 31, 11
@Schneems
Joins
# Joins
BlogPost.joins(:user).where(:user_id => 2).first
=> #<BlogPost id: 13023, name: The best of times>
Sunday, July 31, 11
@Schneems
Include
# Include pre-fetches data
User.where(:id => 3).includes(:posts => :comments)
user = User.find(3)
post = user.posts.all.first
post.comments
Sunday, July 31, 11
@Schneems
Having
# having
User.group(:hometown).having("COUNT(hometown) > 22").count
#<OrderedHash
{"Austin, Texas"=>28,
"Austin, TX"=>102,
"San Francisco, CA"=>92,
"Dallas, TX"=>56}>
Sunday, July 31, 11
• can take raw sql statement
@Schneems
Where
User.where("username = foo").to_sql
=> "SELECT \"users\".* FROM \"users\"
WHERE (username = foo)"
Sunday, July 31, 11
• Like
@Schneems
Where
# 'Like' does a search using % as wildcard
User.where("username like '%foo%'")
# will match '...zfoo', 'fooz...', etc.
Sunday, July 31, 11
• Null
@Schneems
Where
# 'NULL' checks blank entries
User.where("username IS NULL")
# 'NOT' negates query
User.where("username IS NOT NULL")
Sunday, July 31, 11
• Equality Operators
@Schneems
Where
# '<>' is SQL not equal
User.where("username <> 'foo'")
# "greater than"
User.where("popularity > 5")
# "less than"
User.where("popularity < 5")
Sunday, July 31, 11
• SQL Functions
@Schneems
Where
# DATE() is a SQL function
User.where("DATE(created_at) > DATE('#{5.days.ago}')")
# Other Functions:
# AVG, SUM, COUNT, MIN, MAX
Sunday, July 31, 11
• (?) passes data into SQL safely
@Schneems
Where
User.where("DATE(created_at) > DATE(?)", 5.days.ago)
Sunday, July 31, 11
• SQL injection attacks
@Schneems
SQL Security
name = "'' or 1 DROP TABLE USERS"
# Bad
User.where("name = #{name}")
# Good
User.where("name = ?", name)
User.where(:name => name)
Sunday, July 31, 11
• Lazy Loading
@Schneems
ActiveRecord
# stores Query, doesn’t hit the DB
cars = Car.where(:colour => 'black')
# .each Fires "select * from cars where ..."
cars.each {|c| puts c.name }
# Now We can Chain
Item.limit(10).order('created_at DESC')
Sunday, July 31, 11
• Re-use your queries
@Schneems
Scopes
class Product < ActiveRecord::Base
scope :discontinued, where(:discontinued => true)
scope :cheap, where("price < 5")
end
Product.discontinued.first
Product.discontinued.cheap.first
Sunday, July 31, 11
• Re-use your queries
@Schneems
Scopes
class Product < ActiveRecord::Base
scope :cheaper_than,
lambda { |price| where("price < ?", price) }
end
Product.cheaper_than(5).first
Sunday, July 31, 11
• Re-use your queries
• Better than scopes
@Schneems
Class Methods
class Product < ActiveRecord::Base
def self.cheaper_than(price)
where("price < ?", price)
end
end
Product.cheaper_than(5).first
Product.cheaper_than(5).order.limit(5).first
Sunday, July 31, 11
@Schneems
Rake# /lib/tasks/users.rake
namespace :users do
desc "Create dummy users"
task :populate => :environment do
5.times do
User.build(:name => Faker.first_name)
end
end
end
# execute with
> rake users:populate
Sunday, July 31, 11
• Can be used independently from Rails
@Schneems
Rake
# /lib/tasks/foo.rake
namespace :foo do
desc "foo bars"
task :bar do
# ...
end
end
Sunday, July 31, 11
@Schneems
Script/Runner
# /lib/user_populate.rb
5.times do
User.build(:name => Faker.first_name)
end
# execute with
> script/runner /lib/user_populate.rb
Sunday, July 31, 11
• Versatile
• Useful outside of rails
• favored over script/runner
@Schneems
Rake Wins
Sunday, July 31, 11
• Split up large data into chunks
• will_paginate gem
@Schneems
Pagination
post = BlogPost.paginate :page => params[:page], :per_page => 20
post.total_count # => 200
post.count # => 20
Sunday, July 31, 11
@Schneems
Full Text Search
# SQL Like
BlogPost.where("title like '%?%", params[:title])
# slow
# doesn't deal with
# punctuation
# pluralization
# etc.
Sunday, July 31, 11
• Open Source Search Server
• Based on Lucene
• Fast
• Feature rich
• Easily Accessed ruby API wrappers
@Schneems
SOLR
Sunday, July 31, 11
@Schneems
Sunspot GEMclass BlogPost < ActiveRecord::Base
searchable do
text :title, :body
text :comments do
comments.map { |comment| comment.body }
end
time :published_at
string :sort_title do
title.downcase.gsub(/^(an?|the)/, '')
end
end
end
Sunday, July 31, 11
• Paginated
@Schneems
Sunspot GEM
BlogPost.search do
fulltext 'best pizza'
with(:published_at).less_than Time.now
order_by :published_at, :desc
paginate :page => 2, :per_page => 15
facet :category_ids, :author_id
end
Sunday, July 31, 11
• Open Source
• Full text search server
@Schneems
Sphinx
Sunday, July 31, 11
@Schneems
Thinking Sphinx GEMclass Article < ActiveRecord::Base
define_index do
# fields
indexes subject, :sortable => true
indexes content
indexes author.name, :as => :author, :sortable => true
# attributes
has author_id, created_at, updated_at
end
end
Sunday, July 31, 11
@Schneems
Thinking Sphinx GEM
Article.search "topical issue"
Article.search "something",
:order => :created_at,
:sort_mode => :desc
Sunday, July 31, 11
• Flying Sphinx add-on in heroku
@Schneems
Sphinx
Sunday, July 31, 11
• Index Tank
@Schneems
3rd Parties/Black Box# Connect to Service
api = IndexTank::Client.new "<YOUR API URL HERE>"
index = api.indexes "<YOUR INDEX NAME>"
# add documents
docid = "<YOUR DOCUMENT ID>"
text = "<THE TEXTUAL CONTENT>"
index.document(docid).add({ :text => text })
# search documents
results = index.search "foo"
Sunday, July 31, 11
@Schneems
Questions?
http://guides.rubyonrails.orghttp://stackoverflow.com
http://peepcode.com
Sunday, July 31, 11