开发者

Fetching parents efficiently in rails

开发者 https://www.devze.com 2023-04-02 09:56 出处:网络
I\'m writing an image gallery. An image in the gallery belongs to a project. Each project can have a parent project.

I'm writing an image gallery.

An image in the gallery belongs to a project. Each project can have a parent project.

An example would be project1 > project2 > project3 > image1

To allow the admins to use FTP i wanted to have 'pretty' paths to the files.

Thumbnails and previews are stored in ROOT/previews/<image-id> and ROOT/thumbnails/<image-id> while images are stored in ROOT/images/<project-name>/<project-name>/.../<image-name>

The admin can then simple upload image to the correct folder and the application would discover them and handle everything.

This of course causes the problem of having to fetch all parents when getting the path to an image. Currently this means first fetching the image, then it's project, then that projects parent and so on.

If it was always one ancestor (nesting level) the problem would be easy, it's the 0 or more that's the problem.

I don't expect the projects to be very deeply nested but I'm still concerned as having many queries per request is bad.

Is there any way I could avoid this? Maybe fetch all in one?

Maybe I should skip having FTP uploads all together and only allow drag and drop from the UI?

Maybe I'm worrying too much, I'm only constraining the queries by an indexed field


My schema.rb currently looks like this:

ActiveRecord::Schema.define(:version => 20110827230616) do

  create_table "images", :force => true do |t|
    t.string   "name"
    t.string   "filename"
    t.text     "info"
    t.integer  "project_id"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

  add_index "images", ["project_id", "filename"], :name => "index_images_on_project_id_and_filename", :unique => true

  create_table "projects", :force => true do |t|
    t.string   "name"
    t.text     "info"
    t.string   "dates"
    t.integer  "parent_id"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

  add_index "projects", ["parent_id", "name"], :name => "index_projects_on_parent_id_and_name", :unique => true

end

image.rb

class Image < ActiveRecord::Base
  belongs_to :project

  # Validation
  validates_presence_of :project_id, :filename

  attr_readonly :filename

  def path
    if not self.project_id or not self.project
      raise "Called path on an image without a project"
    end
    return File.join(self.project.path, self.filename)
  end

  def thumbnail_path
    raise "Called thumbnail_path on an image without an id" unless self.id
    return File.join(Rails.application.config.thumbnail_directory, self.id.to_s)
  end

  def preview_path
    raise "Called preview_path on an image without an id" unless self.id
    return File.join(Rails.application.config.preview_directory, self.id.to_s)
  end

  def exists?
    if not File.file? self.path
      return false
    end

    return true
  end
end

project.rb

class Project < ActiveRecord::Base
  belongs_to :parent, :class_name => 'Project'
  has_many   :children, :class_name => 'Project', 
             :dependent => :destroy, :foreign_key => 'parent_id'
  has_many   :images, :dependent => :destroy

  def safe_name
    return self.name.downcase.gsub(/\s/, '-').gsub(/[^a-z0-9-]/, '_')
  end

  def path
    tmp = self
    parents = []
    while tmp
      parents.insert(0, tmp.safe_name)
      tmp = tmp.parent
    end

    return File.join(Rails.application.config.image_directory, parents)
  end
end

Example request can look like this:

Started GET "/images/1" for 127.0.0.1 at Sat Sep 03 10:23:30 -0700 2011
  Processing by ImagesController#show as HTML
  Parameters: {"id"=>"1"}
  Image Load (0.2ms)  SELECT "images".* FROM "images" WHERE "images"."id" = ? LIMIT 1  [["id", "1"]]
  Project Load (0.2ms)  SELECT "projects".* FROM "projects" WHERE "projects"."id" = 2 LIMIT 1
  Project Load (0.2ms)  SELECT "projects".* FROM "pro开发者_Python百科jects" WHERE "projects"."id" = 1 LIMIT 1
  Project Load (0.1ms)  SELECT "projects".* FROM "projects" WHERE "projects"."id" IS NULL LIMIT 1


You could use a nested set model for storing the projects. Using it you could simply get the project, then get all the parents of the project in one query.

Wikipedia about nested sets:
http://en.wikipedia.org/wiki/Nested_set_model

Rails plugin for nested sets:
https://github.com/collectiveidea/awesome_nested_set

0

精彩评论

暂无评论...
验证码 换一张
取 消