Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove Mobility::Interface #229

Merged
merged 1 commit into from
May 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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