Skip to content

Commit

Permalink
Remove Mobility::Interface
Browse files Browse the repository at this point in the history
  • Loading branch information
shioyama committed May 22, 2018
1 parent ffbe3ae commit 6f99431
Show file tree
Hide file tree
Showing 11 changed files with 206 additions and 243 deletions.
59 changes: 5 additions & 54 deletions lib/mobility.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ module Mobility
require "mobility/backend_resetter"
require "mobility/configuration"
require "mobility/fallbacks"
require "mobility/interface"
require "mobility/loaded"
require "mobility/plugins"
require "mobility/translates"
Expand Down Expand Up @@ -82,8 +81,9 @@ class << self
def extended(model_class)
return if model_class.respond_to? :mobility_accessor

model_class.include(InstanceMethods)
model_class.extend(ClassMethods)
model_class.extend Translates
#TODO: Remove in v1.0
model_class.include InstanceMethods

if translates = Mobility.config.accessor_method
model_class.singleton_class.send(:alias_method, translates, :mobility_accessor)
Expand Down Expand Up @@ -176,6 +176,7 @@ def default_fallbacks(*args)
config.public_send(:default_fallbacks, *args)
end

# TODO: Make private in v1.0
def new_fallbacks(*args)
config.public_send(:new_fallbacks, *args)
end
Expand Down Expand Up @@ -243,56 +244,25 @@ def set_locale(locale)
end
end

class BackendsCache < Hash
def initialize(model)
mobility = model.class.mobility

super() do |hash, name|
if backend = mobility.backends[name]
backend.new(model, name.to_s).tap { |instance| hash[name] = instance }
elsif String === name
# Support fetching with string keys
self[name.to_sym]
else
raise KeyError, "No backend for: #{name}."
end
end
end
end
private_constant :BackendsCache

# TODO: Remove entire module in v1.0
module InstanceMethods
# Return hash of cached backend instances for this model.
# @return BackendsCache
def mobility_backends
@mobility_backends ||= BackendsCache.new(self)
end

# Fetch backend for an attribute
# @deprecated Use mobility_backends[:<attribute>] instead.
# @param [String] attribute Attribute
# TODO: Remove in v1.0
def mobility_backend_for(attribute)
warn %{
WARNING: mobility_backend_for is deprecated and will be removed in the next
version of Mobility. Use <post>.<attribute>_backend instead.}
mobility_backends[attribute.to_sym]
end

def initialize_dup(other)
@mobility_backends = nil
super
end

# TODO: Remove in v1.0
def mobility
warn %{
WARNING: <post>.mobility is deprecated and will be removed in the next
version of Mobility. To get backends, use <post>.<attribute>_backend instead.}
@mobility ||= Adapter.new(self)
end

# TODO: Remove in v1.0
class Adapter < Struct.new(:model)
def backend_for(attribute)
model.mobility_backends[attribute.to_sym]
Expand All @@ -301,25 +271,6 @@ def backend_for(attribute)
private_constant :Adapter
end

module ClassMethods
include Translates

def mobility
@mobility ||= Interface.new
end

def translated_attribute_names
mobility.translated_attribute_names
end

def inherited(subclass)
subclass.instance_variable_set(:@mobility, mobility.dup)
super
end
end
private_constant :ClassMethods, :InstanceMethods

class BackendRequired < ArgumentError; end
class InvalidLocale < I18n::InvalidLocale; end
class NotImplementedError < StandardError; end
end
2 changes: 1 addition & 1 deletion lib/mobility/active_record/uniqueness_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class UniquenessValidator < ::ActiveRecord::Validations::UniquenessValidator
def validate_each(record, attribute, value)
klass = record.class

if (([*options[:scope]] + [attribute]).map(&:to_s) & klass.translated_attribute_names).present?
if (([*options[:scope]] + [attribute]).map(&:to_s) & klass.mobility_attributes).present?
warn %{
WARNING: The Mobility uniqueness validator for translated attributes does not
support case-insensitive validation. This option will be ignored for: #{attribute}
Expand Down
75 changes: 70 additions & 5 deletions lib/mobility/attributes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -134,13 +134,13 @@ def initialize(*attribute_names, method: :accessor, backend: Mobility.default_ba
@method = method
@options = Mobility.default_options.to_h.merge(backend_options)
@names = attribute_names.map(&:to_s).freeze
raise Mobility::BackendRequired, "Backend option required if Mobility.config.default_backend is not set." if backend.nil?
raise BackendRequired, "Backend option required if Mobility.config.default_backend is not set." if backend.nil?
@backend_name = backend
end

# Setup backend class, include modules into model class, add this
# attributes module to shared {Mobility::Interface} and setup model with
# backend setup block (see {Mobility::Backend::Setup#setup_model}).
# Setup backend class, include modules into model class, include/extend
# shared modules and setup model with backend setup block (see
# {Mobility::Backend::Setup#setup_model}).
# @param klass [Class] Class of model
def included(klass)
@model_class = @options[:model_class] = klass
Expand All @@ -157,7 +157,9 @@ def included(klass)
define_writer(name) if %i[accessor writer].include?(method)
end

model_class.mobility << self
klass.include InstanceMethods
klass.extend ClassMethods

backend_class.setup_model(model_class, names)
end

Expand Down Expand Up @@ -238,5 +240,68 @@ def get_class_from_key(parent_class, key)
klass_name = key.to_s.gsub(/(^|_)(.)/){|x| x[-1..-1].upcase}
parent_class.const_get(klass_name)
end

module InstanceMethods
# Return a new backend for an attribute name.
# @return [Hash] Hash of attribute names and backend instances
def mobility_backends
@mobility_backends ||= Hash.new do |hash, name|
next hash[name.to_sym] if String === name
hash[name] = self.class.mobility_backend_class(name).new(self, name.to_s)
end
end

def initialize_dup(other)
@mobility_backends = nil
super
end
end

module ClassMethods
# Return all {Mobility::Attribute} module instances from among ancestors
# of this model.
# @return [Array<Mobility::Attributes>] Attribute modules
def mobility_modules
ancestors.select { |mod| Attributes === mod }
end

# Return translated attribute names on this model.
# @return [Array<String>] Attribute names
def mobility_attributes
mobility_modules.map(&:names).flatten
end

# @!method translated_attribute_names
# @return (see #mobility_attributes)
alias translated_attribute_names mobility_attributes

# Return backend class for a given attribute name.
# @param [Symbol,String] Name of attribute
# @return [Class] Backend class
def mobility_backend_class(name)
@backends ||= BackendsCache.new(self)
@backends[name.to_sym]
end

class BackendsCache < Hash
def initialize(klass)
# Preload backend mapping
klass.mobility_modules.each do |mod|
mod.names.each { |name| self[name.to_sym] = mod.backend_class }
end

super() do |hash, name|
if mod = klass.mobility_modules.find { |m| m.names.include? name.to_s }
hash[name] = mod.backend_class
else
raise KeyError, "No backend for: #{name}."
end
end
end
end
private_constant :BackendsCache
end
end

class BackendRequired < ArgumentError; end
end
2 changes: 1 addition & 1 deletion lib/mobility/backends/active_record/table.rb
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ def require_outer_join?(opts, invert)
touch: true

before_save do
required_attributes = self.class.translated_attribute_names & translation_class.attribute_names
required_attributes = self.class.mobility_attributes & translation_class.attribute_names
send(association_name).destroy_empty_translations(required_attributes)
end

Expand Down
53 changes: 0 additions & 53 deletions lib/mobility/interface.rb
Original file line number Diff line number Diff line change
@@ -1,53 +0,0 @@
module Mobility
=begin
Class to access Mobility across backends. In particular, keeps a record of
which {Attributes} modules have been included on the model class.
=end
class Interface
# @return [Array<Attributes>]
attr_reader :modules

# Map from attribute name to backend class
# @return [Hash]
attr_reader :backends

# @param [Class] model_class Model class
def initialize
@modules = []
@backends = Hash.new do |_, key|
if String === key
warn "You're accessing a backend using a String key. Try using a Symbol instead."
end
raise KeyError, "no backend found with name: \"#{key}\""
end
end

# @return [Array<String>] Translated attributes defined on model
def translated_attribute_names
modules.map(&:names).flatten
end

# Appends backend module to +modules+ array for later reference.
# @param [Attributes] backend_module Attributes module
def << backend_module
modules << backend_module
backend_module.names.each { |name| backends[name.to_sym] = backend_module.backend_class }
end

# Fetches attribute from backend class. The +[]+ method must be implemented
# by backend class. For ActiveRecord, this returns an Arel node.
# @param [Symbol] name Attribute name
# @param [Symbol] locale Locale
def [](name, locale = Mobility.locale)
backends[name][name, locale]
end

def initialize_dup(other)
@modules = other.modules.dup
@backends = other.backends.dup
super
end
end
end
16 changes: 12 additions & 4 deletions lib/mobility/plugins/active_record/query.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ def where(opts = :chain, *rest)
opts == :chain ? WhereChain.new(spawn) : super
end

# Return backend node for attribute name.
# @param [Symbol,String] name Name of attribute
# @param [Symbol] locale Locale
# @return [Arel::Node] Arel node for this attribute in given locale
def backend_node(name, locale = Mobility.locale)
@klass.mobility_backend_class(name)[name, locale]
end

class WhereChain < ::ActiveRecord::QueryMethods::WhereChain
def not(opts, *rest)
QueryBuilder.build(@scope, opts, invert: true) do |untranslated_opts|
Expand All @@ -57,7 +65,7 @@ def build(scope, where_opts, invert: false)
locale = Mobility.locale
opts = where_opts.with_indifferent_access

maps = build_maps!(scope.mobility, opts, locale, invert: invert)
maps = build_maps!(scope, opts, locale, invert: invert)
return yield if maps.empty?

base = opts.empty? ? scope : yield(opts)
Expand All @@ -66,14 +74,14 @@ def build(scope, where_opts, invert: false)

private

def build_maps!(interface, opts, locale, invert:)
def build_maps!(scope, opts, locale, invert:)
keys = opts.keys.map(&:to_s)
interface.modules.select { |mod| mod.options[:query] }.map { |mod|
scope.mobility_modules.map { |mod|
next if (mod_keys = mod.names & keys).empty?

mod_opts = opts.slice(*mod_keys)
predicates = mod_keys.map do |key|
build_predicate(interface[key.to_sym], opts.delete(key), invert: invert)
build_predicate(scope.backend_node(key), opts.delete(key), invert: invert)
end

->(rel) do
Expand Down
Loading

0 comments on commit 6f99431

Please sign in to comment.