class AddTagsToNotes < ActiveRecord::Migration
def change
add_column :notes, :tags, :string, array: true
end
end
SELECT notes.*
FROM notes
WHERE 'useful' = ANY(notes.tags)
SELECT notes.*
FROM notes
WHERE notes.tags @> '{useful}'
class AddTagsToNotes < ActiveRecord::Migration
def change
add_index :notes, :tags, using: :gin
end
end
First Run - 370 ms
Nested Loop -> Bitmap Heap Scan on post_tags Recheck Cond: (tag_id = 1) -> Bitmap Index Scan on index_post_tags_on_tag_id Index Cond: (tag_id = 1) -> Index Scan using posts_pkey on posts Index Cond: (id = post_tags.post_id) Total runtime: 370.139 ms
Subsequent calls - 13 ms
Nested Loop -> Bitmap Heap Scan on post_tags Recheck Cond: (tag_id = 2) -> Bitmap Index Scan on index_post_tags_on_tag_id Index Cond: (tag_id = 2) -> Index Scan using posts_pkey on posts Index Cond: (id = post_tags.post_id) Total runtime: 13.511 ms
First run: 503 ms
Aggregate -> Seq Scan on posts Filter: ('Tag #1'::text = ANY ((array_tags)::text[])) Rows Removed by Filter: 98989 Total runtime: 503.517 ms
Subsequent runs: 500 ms
Aggregate -> Seq Scan on posts Filter: ('Tag #1'::text = ANY ((array_tags)::text[])) Rows Removed by Filter: 98989 Total runtime: 499.618 ms
First run - Pre index: 536 ms
Aggregate -> Seq Scan on posts Filter: (array_tags @> '{"Tag #1"}'::character varying[]) Rows Removed by Filter: 98989 Total runtime: 536.183 msFirst run - Post index: 8ms
Aggregate -> Bitmap Heap Scan on posts Recheck Cond: (gin_tags @> '{"Tag #1"}'::character varying[]) -> Bitmap Index Scan on index_posts_on_gin_tags Index Cond: (gin_tags @> '{"Tag #1"}'::character varying[]) Total runtime: 8.056 ms
Method | Run 1 | Run 2 |
---|---|---|
has_many | 370 ms | 13 ms |
Array: ANY | 503 ms | 500 ms |
Array: @< (no index) | 499 ms | 500 ms |
Array: @< (indexed) | 8 ms | 5 ms |
class CreateMembers < ActiveRecord::Migration
def change
add_column :posts, :similar_post_ids, :integer, array: true
end
end
Post#similar_posts
which will retrieve our item's by their ids stored in Post#similar_post_ids
Post#similar_posts=
method that will take the array of items passed to it and store their ids in Post#similar_post_ids
class Post < ActiveRecord::Base
def similar_posts
self.class.where(id: similar_posts_ids)
end
def similar_posts=(children)
self.similar_posts_ids = children.map(&:id)
end
def similar_posts_ids=(ids)
write_attribute :similar_posts_ids, ids.reject{ |id| id.to_i <= 0 }
end
end
post = Post.find(1)
post.similar_posts # => [Post, Post, Post]
post.similar_posts = [Post, Post] # Updates the similar_post_ids attribute
# the following won't work
post.similar_posts << Post # We aren't observing this array,
# so it won't update the ids attribute
# we can work around it with the following
post.similar_posts += [Post] # calls the setter on the array
enable_extension
which will allow you to enable an extension via a migrationenable_extension 'hstore'
will enable hstore in your database
class CreateMembers < ActiveRecord::Migration
def change
enable_extension 'hstore' # Let's enable Hstore
create_table :members do |t|
t.string :username
t.string :type
t.hstore :data # We'll store all our attributes in a column
# called data
t.timestamps
end
end
end
module HstoreAccessor
def hstore_accessor(hstore_attribute, *keys)
Array(keys).flatten.each do |key|
define_method("#{key}=") do |value|
send("#{hstore_attribute}=", (send(hstore_attribute) || {}).merge(key.to_s => value))
send("#{hstore_attribute}_will_change!")
end
define_method(key) do
send(hstore_attribute) && send(hstore_attribute)[key.to_s]
end
end
end
end
end
ActiveRecord::Base.send(:extend, HstoreAccessor)
class User < Member
hstore_accessor :data, :email, :age
end
class User < Member
hstore_accessor :data, :email, :age
validates :email, :age, presence: true
end
User.new.email #=> nil
User.new.valid? #=> false
class TwitterUser < Member
hstore_accessor :data, :twitter_handle
end
twitter_user = TwitterUser.new twitter_handle: '_danmcclain', username: 'dan'
twitter_user.twitter_handle #=> "_danmcclain"
twitter_user.username #=> "dan"
twitter_user.email #=> NoMethodError
SELECT rank() OVER(ORDER BY points DESC), username, points
FROM scores
WHERE game = 'Barking Bostons'
┌──────┬──────────┬────────┐ │ rank │ username │ points │ ├──────┼──────────┼────────┤ │ 1 │ Brian │ 300 │ │ 2 │ Dan │ 200 │ │ 3 │ Dan │ 100 │ │ 4 │ Doug │ 30 │ └──────┴──────────┴────────┘
SELECT rank() OVER (ORDER BY scores.points DESC), username, points
FROM scores
WHERE game = 'Barking Bostons' AND username = 'Dan'
┌──────┬──────────┬────────┐ │ rank │ username │ points │ ├──────┼──────────┼────────┤ │ 1 │ Dan │ 200 │ │ 2 │ Dan │ 100 │ └──────┴──────────┴────────┘
WITH top_scores_for_game AS (
SELECT rank() OVER (ORDER BY points DESC), username, points
FROM scores
WHERE scores.game = 'Barking Bostons'
)
SELECT top_scores_for_game.*
FROM top_scores_for_game
WITH
statement
WITH top_scores_for_game AS (
SELECT rank() OVER (ORDER BY points DESC), username, points
FROM scores
WHERE scores.game = 'Barking Bostons'
)
SELECT top_scores_for_game.*
FROM top_scores_for_game
WHERE top_scores_for_game.username = 'Dan'
┌──────┬──────────┬────────┐ │ rank │ username │ points │ ├──────┼──────────┼────────┤ │ 2 │ Dan │ 200 │ │ 3 │ Dan │ 100 │ └──────┴──────────┴────────┘
query = <<-SQL
WITH top_scores_for_game AS (
SELECT rank() OVER (ORDER BY points DESC), username, points
FROM scores
WHERE scores.game = 'Barking Bostons'
)
SELECT top_scores_for_game.*
FROM top_scores_for_game
WHERE top_scores_for_game.username = 'Dan'
SQL
Score.find_by_sql query
=> [#<Score ... >, #<Score ... >]
Score.ranked(points: :desc).where(game: 'Barking Bostons')
Score.from_cte('top_scores_for_game',
Score.ranked(points: :desc).where(game: 'Barking Bostons')).where(username: 'Dan')