How to make my model use my cache when saving?

I am using Rails 5. I have the following model

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 

With some tricky debugging, I realized that the "save" method makes this request execute ...

  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' 

This happens every time the above method is called. this is not optimal because I installed my DistanceUnit model to store the cache. Below is the code. How can I get the save method to automatically use the cache and not execute this request every time?

 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 
+6
source share
2 answers

Rails 5 makes the belongs_to association mandatory by default after this change . This means that a related record must be present in the database when it is saved or validated. There are several possible solutions to your problem.

1) Set distance_unit manually from the cache before saving the MyObject instance to prevent it from being retrieved from the database:

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

2) Or cancel this behavior:

You can pass optional: true to the belongs_to association, which will remove this validation check:

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

Honestly, I think there was a bad design

For a distance calculation, suggest using a different approach.

Props:

  • no cache required
  • SOLID
  • no own written logic in models

Code for distance calculation logic:

 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}" 

You can run it with: $ ruby path_to_file.rb

And the result should be as follows:

 "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" 
+2
source

Source: https://habr.com/ru/post/1015034/


All Articles