保存时如何让我的模型使用我的缓存?

时间:2017-02-16 16:03:11

标签: ruby-on-rails caching model save ruby-on-rails-5

我正在使用Rails 5.我有以下模型

class MyObject < ActiveRecord::Base
    ...
  belongs_to :distance_unit

    ...
  def save_with_location
    transaction do
      address = LocationHelper.get_address(location) 
      if !self.address.nil? && !address.nil?
        self.address.update_attributes(address.attributes.except("id", "created_at", "updated_at")) 
      elsif !address.nil?
        address.race = self
        address.save
      end

      # Save the object
      save
    end 
  end

通过一些狡猾的调试,我发现“save”方法会导致执行此查询......

  DistanceUnit Load (0.3ms)  SELECT  "distance_units".* FROM "distance_units" WHERE "distance_units"."id" = $1 LIMIT $2  [["id", 2], ["LIMIT", 1]]
  ↳ app/models/my_object.rb:54:in `block in save_with_location'

每次调用上述方法时都会发生这种情况。这不是最佳的,因为我设置了我的DistanceUnit模型以获得缓存。下面是它的代码。如何让我的“保存”方法自动使用缓存而不是每次都执行此查询?

class DistanceUnit < ActiveRecord::Base

  def self.cached_find_by_id(id)
    Rails.cache.fetch("distanceunit-#{id}") do
      puts "looking for id: #{id}" 
      find_by_id(id)
    end
  end

  def self.cached_find_by_abbrev(abbrev)
    Rails.cache.fetch("distanceunit-#{abbrev}") do
      find_by_abbrev(abbrev)
    end
  end

  def self.cached_all()
    Rails.cache.fetch("distanceunit-all") do
      all
    end
  end

end

2 个答案:

答案 0 :(得分:3)

Rails 5在this change之后默认需要belongs_to关联。这意味着相关记录必须在保存时存在于数据库中,否则验证将失败。有几种方法可以解决您的问题

1)在保存distance_unit实例之前从缓存中手动设置MyObject以防止从数据库中提取它:

  def save_with_location
      # ...

      # Save the object
      self.distance_unit = DistanceUnit.cached_find_by_id(self.distance_unit_id)
      save
    end 
  end

2)或选择退出此行为:

您可以将optional: true传递给可以删除此验证检查的belongs_to关联:

class MyObject < ApplicationRecord
  # ...
  belongs_to :distance_unit, optional: true
  # ...
end

答案 1 :(得分:2)

老实说,我觉得设计不好

对于距离计算,建议使用另一种方法

道具:

  • 不需要缓存
  • SOLID
  • 模型
  • 中没有自编写逻辑

距离计算逻辑的代码如下:

require 'active_record'

ActiveRecord::Base.logger = Logger.new STDOUT

class Address < ActiveRecord::Base
  establish_connection adapter: 'sqlite3', database: 'foobar.db'
  connection.create_table table_name, force: true do |t|
    t.string  :name
    t.decimal :latitude,  precision: 15, scale: 13
    t.decimal :longitude, precision: 15, scale: 13
    t.references :addressable, polymorphic: true, index: true
  end

  belongs_to :addressable, polymorphic: true
end

class Shop < ActiveRecord::Base
  establish_connection adapter: 'sqlite3', database: 'foobar.db'
  connection.create_table table_name, force: true do |t|
    t.string  :name
  end

  has_one :address, as: :addressable
end

class House < ActiveRecord::Base
  establish_connection adapter: 'sqlite3', database: 'foobar.db'
  connection.create_table table_name, force: true do |t|
    t.string  :name
  end

  has_one :address, as: :addressable
end

class Measure
  def initialize address_from, address_to
    @address_from = address_from
    @address_to   = address_to
  end

  def distance
    # some calculation logic
    lat_delta = @address_from.latitude  - @address_to.latitude
    lon_delta = @address_from.longitude - @address_to.longitude
    lat_delta.abs + lon_delta.abs
  end

  def units
    'meters'
  end
end

shop_address  = Address.new name: 'Middleberge FL 32068', latitude: 30.11, longitude: 32.11
house_address = Address.new name: 'Tucson AZ 85705-7598', latitude: 40.12, longitude: 42.12
shop  = Shop.create!  name: 'Food cort'
house = House.create! name: 'My home'
shop.update!  address: shop_address
house.update! address: house_address
p 'Shop:',  shop
p 'House:', house
measure = Measure.new shop_address, house_address
p "Distance between #{shop.name} (#{shop.address.name}) and #{house.name} (#{house.address.name}): #{measure.distance.to_s} #{measure.units}"

您可以使用:$ ruby path_to_file.rb

运行它

结果应该是:

"Shop:"
#<Shop id: 1, name: "Food cort">
"House:"
#<House id: 1, name: "My home">
"Distance between Food cort (Middleberge FL 32068) and My home (Tucson AZ 85705-7598): 20.02 meters"