Глобально повторно использовать запросы ActiveRecord в разных моделях? - PullRequest
0 голосов
/ 15 сентября 2010

Не уверен, как сформулировать вопрос кратко:).

Я сказал 20 сообщений на странице, и каждое сообщение имеет 3-5 тегов. Если я покажу все теги на боковой панели (Tag.all.each...), то есть ли способ позвонить post.tags, чтобы не запрашивать базу данных, а просто использовать теги, найденные из Tag.all?

class Post < ActiveRecord::Base

end

class Tag < ActiveRecord::Base

end

Вот как это можно использовать:

# posts_controller.rb
posts = Post.all

# posts/index.html.haml
- posts.each do |post|
  render :partial => "posts/item", :locals => {:post => post}

# posts/_item.html.haml
- post.tags.each do |tag|
  %li
    %a{:href => "/tags/#{tag.name}"}= tag.name.titleize

Я уже знаю о следующих оптимизациях:

  1. Рендеринг паролей с использованием :collection
  2. Стремительная загрузка с Post.first(:include => [:tags])

Мне интересно, если я использую Tag.all в своем шаблоне shared/_sidebar.haml, есть ли возможность повторно использовать результат этого запроса в вызовах post.tags?

1 Ответ

1 голос
/ 15 сентября 2010

Вы можете использовать метод tag_ids в экземпляре Post.

В вашем контроллере создайте хэш тега.Еще лучше кэшировать тэг хеша.

Добавьте это к вашему application_controller.rb.

def all_tags
  @all_tags ||=Rails.cache.fetch('Tag.all', :expire_in => 15.minutes)){ Tag.all }
  # without caching
  #@all_tags ||= Tag.all
end


def all_tags_hash
  @all_tags_hash ||= all_tags.inject({}){|hash, tag| hash[tag.id]=tag;hash}
end

def all_tags_by_ids ids
  ids ||= []
  ids = ids.split(",").map{|str| str.to_i} if ids.is_a?(string)
  all_tags_hash.values_at(*ids)
end
helper_method :all_tags, :all_tags_hash, :all_tags_by_id

Теперь ваше частичное можно переписать как

# posts/_item.html.haml
- all_tags_by_ids(post.tag_ids).each do |tag|
  %li
    %a{:href => "/tags/#{tag.name}"}= tag.name.titleize

Мое решение кешируетTag моделей за 15 минут.Убедитесь, что вы добавили наблюдателя / фильтр в модель тега, чтобы сделать недействительным / обновить кэш во время создания / обновления / удаления.

В вашем config\environment.rb

config.active_record.observers = :tag_observer

Добавьте файл tag_observer.rb в каталог app\models.

class TagObserver < ActiveRecord::Observer

  def after_save(tag)
    update_tags_cache(tag)
  end

  def after_destroy(tag)
    update_tags_cache(tag, false)
  end

  def update_tags_cache(tag, update=true)
    tags = Rails.cache.fetch('Tag.all') || []
    tags.delete_if{|t| t.id == tag.id}
    tags << tag if update
    Rails.cache.write('Tag.all', tags, :expire_in => 15.minutes)
  end
end

Примечание. То же решение будет работать безтакже кеш.

Это решение все еще требует от вас запроса к таблице tags для идентификаторов Tag.Дальнейшую оптимизацию можно выполнить, сохранив идентификаторы тегов в виде строки с разделителями-запятыми в модели Post (не считая ее сохранение в таблице post_tags).

class Post < ActiveRecord::Base
  has_many :post_tags
  has_many :tags, :through => :post_tags

  # add a new string column called tag_ids_str to the `posts` table.
end

class PostTag < ActiveRecord::Base
  belongs_to :post
  belongs_to :tag
  after_save :update_tag_ids_str
  after_destroy :update_tag_ids_str

  def update_tag_ids_str    
    post.tag_ids_str = post.tag_ids.join(",")
    post.save
  end
end

class Tag < ActiveRecord::Base
  has_many :post_tags
  has_many :posts, :through => :post_tags
end

Теперь ваш фрагмент можно переписать как

# posts/_item.html.haml
- all_tags_by_ids(post.tag_ids_str).each do |tag|
  %li
    %a{:href => "/tags/#{tag.name}"}= tag.name.titleize
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...