module Uuids
module Base
# @api hide
# Adds getter and setter for the model attribute referred by uuid.
#
# @example
# BelongsTo.add City, :state, State
# city = City.new
# city.respond_to? :state # => true
# city.respond_to? :state= # => true
class BelongsTo < Struct.new(:klass, :name, :model)
# @api hide
# @!attribute klass
# The model to add the attribute to.
# @return [ActiveRecord::Base] The model class.
# @api hide
# @!attribute name
# The name of the attribute to be added.
# @return [String, Symbol] The name.
# @api hide
# @!attribute model
# The model to be referred by uuid.
# @return [ActiveRecord::Base] The model class to be referred to.
# @api hide
# @!attribute [r] class_name
# The name of the {#klass} constant.
# @return [String] The {#klass} name.
def class_name
@class_name ||= klass.name
end
# @api hide
# @!attribute [r] model_name
# The name of the {#model} constant.
# @return [String] The {#model} name.
def model_name
@model_name ||= model.name
end
# @api hide
# @!attribute [r] model_name
# The name of the {#klass} db table column to store reference to the
# attribute by uuid.
# @return [String] The name of the column.
def column_name
@column_name ||= "#{ name }_uuid"
end
# @api hide
# Constructs the object and adds the attribute.
# @example (see Uuids::Base::BelongsTo)
# @params [ActiveRecord::Base] klass The model to add attribute to.
# Should have a corresponding column for reference by uuid.
# @params [String, Symbol] name The name of the attribute to be added.
# @params [ActiveRecord::Base] model The model to be referred by uuid.
# Should include the Uuids::Base module.
# @raise (see Uuids::Base::BelongsTo#add)
def self.add(*args)
new(*args).add
end
# @api hide
# Adds the attribute's getter and setter.
# @raise [TypeError] if the {#model} doesn't include
# the Uuids::Base module.
# @raise [NotImplementedError] if the {#klass} doesn't have the
# correspondint {#column_name} column.
def add
check_model
check_column
add_getter
add_setter
end
private
def check_model
return if model.ancestors.include? Uuids::Base
fail TypeError
.new "#{ model_name } class doesn't include the Uuids::Base module"
end
def check_column
return if klass.attribute_names.include? column_name
fail NotImplementedError
.new "#{ class_name }##{ column_name } attribute isn't implemented"
end
def add_getter
klass.class_eval getter
end
def add_setter
klass.class_eval setter
end
def getter
"def #{ name };
uuid = read_attribute :#{ column_name };
return @#{ name } = nil unless uuid;
if Array(@#{ name }.try(:uuids)).map(&:value).include? uuid;
@#{ name };
else;
@#{ name } = #{ model_name }.by_uuid(uuid).includes(:uuids).first;
end;
end"
end
def setter
"def #{ name }=(value);
fail TypeError unless value.nil? || value.is_a?(#{ model_name });
write_attribute :#{ column_name }, value.try(:uuid);
#{ name };
end"
end
end
end
end