lib/hydra_attribute/hydra_value.rb
module HydraAttribute
class HydraValue
# This error is raised when +:hydra_attribute_id+ key isn't passed to initialize.
# This key is important for determination the type of attribute which this model represents.
class HydraAttributeIdIsMissedError < ArgumentError
def initialize(msg = 'Key :hydra_attribute_id is missed')
super
end
end
# This error is raised when <tt>HydraAttribute::HydraValue</tt> model is saved
# but <tt>entity model</tt> isn't persisted
class EntityModelIsNotPersistedError < RuntimeError
def initialize(msg = 'HydraValue model cannot be saved is entity model is not persisted')
super
end
end
include ::HydraAttribute::Model::IdentityMap
include ActiveModel::AttributeMethods
include ActiveModel::Dirty
attr_reader :entity, :value
define_attribute_method :value
# Initialize hydra value object
#
# @param [ActiveRecord::Base] entity link to entity model
# @param [Hash] attributes contain values of table row
# @option attributes [Symbol] :id
# @option attributes [Symbol] :hydra_attribute_id this field is required
# @option attributes [Symbol] :value
def initialize(entity, attributes = {})
raise HydraAttributeIdIsMissedError unless attributes.has_key?(:hydra_attribute_id)
@entity = entity
@attributes = attributes
if attributes.has_key?(:value)
@value = column.type_cast(attributes[:value])
else
@value = column.type_cast(column.default)
attributes[:value] = column.default
end
end
class << self
# Holds <tt>Arel::Table</tt> objects grouped by entity table and backend type of attribute
#
# @return [Hash]
def arel_tables
@arel_tables ||= Hash.new do |entity_tables, entity_table|
entity_tables[entity_table] = Hash.new do |backend_types, backend_type|
backend_types[backend_type] = Arel::Table.new("hydra_#{backend_type}_#{entity_table}", ::ActiveRecord::Base)
end
end
end
# Returns database adapter
#
# @return [ActiveRecord::ConnectionAdapters::AbstractAdapter]
def connection
::ActiveRecord::Base.connection
end
# Returns virtual value column
#
# @param [Fixnum] hydra_attribute_id
# @return [ActiveRecord::ConnectionAdapters::Column]
def column(hydra_attribute_id)
nested_identity_map(:column).cache(hydra_attribute_id.to_i) do
hydra_attribute = ::HydraAttribute::HydraAttribute.find(hydra_attribute_id)
::ActiveRecord::ConnectionAdapters::Column.new(hydra_attribute.name, hydra_attribute.default_value, hydra_attribute.backend_type)
end
end
# Delete all values for current entity
#
# @param [HydraAttribute::HydraEntity] entity
# @return [NilClass]
def delete_entity_values(entity)
hydra_attributes = ::HydraAttribute::HydraAttribute.all_by_entity_type(entity.class.model_name)
hydra_attributes = hydra_attributes.group_by(&:backend_type)
hydra_attributes.each do |backend_type, attributes|
table = arel_tables[entity.class.table_name][backend_type]
where = table['hydra_attribute_id'].in(attributes.map(&:id)).and(table['entity_id'].eq(entity.id))
arel = table.from(table)
connection.delete(arel.where(where).compile_delete, 'SQL')
end
end
end
# Returns virtual value column
#
# @return [ActiveRecord::ConnectionAdapters::Column]
def column
self.class.column(@attributes[:hydra_attribute_id])
end
# Returns model ID
#
# @return [Fixnum]
def id
@attributes[:id]
end
# Sets new type casted attribute value
#
# @param [Object] new_value
# @return [NilClass]
def value=(new_value)
value_will_change! unless value == new_value
@attributes[:value] = new_value
@value = column.type_cast(new_value)
end
# Returns not type cased value
#
# @return [Object]
def value_before_type_cast
@attributes[:value]
end
# Checks if value not blank and not zero for number types
#
# @return [TrueClass, FalseClass]
def value?
return false unless value
if column.number?
!value.zero?
else
value.present?
end
end
# Returns hydra attribute model which contains meta information about attribute
#
# @return [HydraAttribute::HydraAttribute]
def hydra_attribute
@hydra_attribute ||= ::HydraAttribute::HydraAttribute.find(@attributes[:hydra_attribute_id])
end
# Checks if model is persisted
#
# @return [TrueClass, FalseClass]
def persisted?
@attributes[:id].present?
end
# Saves model
# Performs +insert+ or +update+ sql query
# Method doesn't perform sql query if model isn't modified
#
# @return [TrueClass, FalseClass]
def save
raise EntityModelIsNotPersistedError unless entity.persisted?
if persisted?
return false unless changed?
update
else
create
end
@previously_changed = changes
@changed_attributes.clear
true
end
private
# Creates arel insert manager
#
# @return [Arel::InsertManager]
def arel_insert
table = self.class.arel_tables[entity.class.table_name][hydra_attribute.backend_type]
fields = {}
fields[table[:entity_id]] = entity.id
fields[table[:hydra_attribute_id]] = hydra_attribute.id
fields[table[:value]] = value
fields[table[:created_at]] = Time.now
fields[table[:updated_at]] = Time.now
table.compile_insert(fields)
end
# Creates arel update manager
#
# @return [Arel::UpdateManager]
def arel_update
table = self.class.arel_tables[entity.class.table_name][hydra_attribute.backend_type]
arel = table.from(table)
arel.where(table[:id].eq(id)).compile_update(table[:value] => value, table[:updated_at] => Time.now)
end
# Performs sql insert query
#
# @return [Integer] primary key
def create
@attributes[:id] = self.class.connection.insert(arel_insert, 'SQL')
end
# Performs sql update query
#
# @return [NilClass]
def update
self.class.connection.update(arel_update, 'SQL')
end
end
end