lib/lookup_by/cache.rb in lookup_by-0.9.1 vs lib/lookup_by/cache.rb in lookup_by-0.10.0

- old
+ new

@@ -7,24 +7,27 @@ @klass = klass @primary_key = klass.primary_key @primary_key_type = klass.columns_hash[@primary_key].type @field = options[:field].to_sym @cache = {} + @reverse = {} @order = options[:order] || @field @read = options[:find_or_create] || options[:find] @write = options[:find_or_create] @allow_blank = options[:allow_blank] || false @normalize = options[:normalize] @raise_on_miss = options[:raise] || false @testing = false @enabled = true @safe = options[:safe] || concurrent? + @mutex = Mutex.new if @safe @stats = { db: Hash.new(0), cache: Hash.new(0) } raise ArgumentError, %Q(unknown attribute "#{@field}" for <#{klass}>) unless klass.column_names.include?(@field.to_s) + # Order matters here, some instance variables depend on prior assignments. case options[:cache] when true @type = :all @read ||= false @@ -33,10 +36,11 @@ raise ArgumentError, "`#{@klass}.lookup_by :#{@field}` options[:find] must be true when caching N" if @read == false @type = :lru @limit = options[:cache] @cache = @safe ? Caching::SafeLRU.new(@limit) : Caching::LRU.new(@limit) + @reverse = @safe ? Caching::SafeLRU.new(@limit) : Caching::LRU.new(@limit) @read = true @write ||= false @testing = true if Rails.env.test? && @write else @read = true @@ -51,35 +55,39 @@ return unless @type == :all clear ::ActiveRecord::Base.connection.send :log, "", "#{@klass.name} Load Cache All" do - @klass.order(@order).each do |i| - @cache[i.id] = i + @klass.order(@order).each do |object| + cache_write(object) end end end def clear @cache.clear end def create(*args, &block) created = @klass.create(*args, &block) - @cache[created.id] = created if created && cache? + + cache_write(created) if cache? + created end def create!(*args, &block) created = @klass.create!(*args, &block) - @cache[created.id] = created if cache? + + cache_write(created) if cache? + created end def seed(*values) @klass.transaction(requires_new: true) do - values.each { |value| create!(@field => value) } + values.map { |value| @klass.where(@field => value).first_or_create! } end end def fetch(value) increment :cache, :get @@ -87,11 +95,11 @@ value = normalize(value) if @normalize && !primary_key?(value) found = cache_read(value) if cache? found ||= db_read(value) if @read || !@enabled - @cache[found.id] = found if found && cache? + cache_write(found) if cache? found ||= db_write(value) if @write if @raise_on_miss && found.nil? raise LookupBy::RecordNotFound, "No #{@klass.name} lookup record found for value: #{value.inspect}" @@ -130,17 +138,13 @@ clear end private + # RAILS_ENV=test will not use the SafeLRU def concurrent? - case Rails::VERSION::MAJOR - when 4 then Rails.configuration.cache_classes && Rails.configuration.eager_load - when 3 then Rails.configuration.allow_concurrency - else - true - end + Rails.configuration.cache_classes && Rails.configuration.eager_load end def primary_key?(value) case @primary_key_type when :integer @@ -152,38 +156,73 @@ def normalize(value) @klass.new(@field => value).send(@field) end - def cache_read(value) - if primary_key?(value) - found = @cache[value] - else - found = @cache.values.detect { |o| o.send(@field) == value } + + if Rails.env.production? + def cache_read(value) + if primary_key?(value) + @cache[value] + else + @reverse[value] + end end + else + def cache_read(value) + found = if primary_key?(value) + @cache[value] + else + @reverse[value] + end - increment :cache, found ? :hit : :miss + increment :cache, found ? :hit : :miss - found + found + end end - def db_read(value) - increment :db, :get + if Rails.env.production? + def db_read(value) + @klass.where(column_for(value) => value).first + end + else + def db_read(value) + increment :db, :get - found = @klass.where(column_for(value) => value).first + found = @klass.where(column_for(value) => value).first - increment :db, found ? :hit : :miss + increment :db, found ? :hit : :miss - found + found + end end + if @safe + def cache_write(object) + return unless object + + @mutex.synchronize do + @cache[object.id] = object + @reverse[object.send(@field)] = object + end + end + else + def cache_write(object) + return unless object + + @cache[object.id] = object + @reverse[object.send(@field)] = object + end + end + def db_write(value) column = column_for(value) return if column == @primary_key @klass.transaction(requires_new: true) do - @klass.create!(column => value) + @klass.create(column => value) end rescue ActiveRecord::RecordNotUnique db_read(value) end