lib/active_repository/base.rb
require 'active_repository/associations'
require 'active_repository/uniqueness'
require 'active_repository/write_support'
require 'sql_query_executor'
require 'active_repository/finders'
require 'active_repository/writers'
require 'active_repository/adapters/persistence_adapter'
require 'active_repository/result_set'
module ActiveRepository
# Base class for ActiveRepository gem.
# Extends it in order to use it.
#
# == Options
#
# There are 2 class attributes to help configure your ActiveRepository class:
#
# * +class_model+: Use it to specify the class that is responsible for the
# persistence of the objects. Default is self, so it is always saving in
# memory by default.
#
# * +save_in_memory+: Used to ignore the class_model attribute, you can use
# it in your test suite, this way all your tests will be saved in memory.
# Default is set to true so it saves in memory by default.
#
#
# == Examples
#
# Using ActiveHash to persist objects in memory:
#
# class SaveInMemoryTest < ActiveRepository::Base
# end
#
# Using ActiveRecord/Mongoid to persist objects:
#
# class SaveInORMOrODMTest < ActiveRepository::Base
# SaveInORMOrODMTest.persistence_class = ORMOrODMModelClass
# SaveInORMOrODMTest.save_in_memory = false
# end
#
# Author:: Caio Torres (mailto:efreesen@gmail.com)
# License:: GPL
class Base < ActiveHash::Base
extend ActiveModel::Callbacks
extend ActiveRepository::Finders
extend ActiveRepository::Writers
include ActiveModel::Validations
include ActiveModel::Validations::Callbacks
include ActiveRepository::Associations
include ActiveRepository::Writers::InstanceMethods
class_attribute :model_class, :before_save_methods, :after_save_methods, :instance_writer => false
class_attribute :before_create_methods, :after_create_methods, :instance_writer => false
class_attribute :save_in_memory, :postfix, :instance_writer => true
after_validation :set_timestamps
# Returns all persisted objects
def self.all
(repository? ? super : PersistenceAdapter.all(self).map { |object| serialize!(object.attributes) })
end
def self.before_save(*methods, options)
add_callbacks(__method__, methods, options)
end
def self.after_save(*methods, options)
add_callbacks(__method__, methods, options)
end
# Constantize class name
def self.constantize
self.to_s.constantize
end
# Deletes all persisted objects
def self.delete_all
repository? ? super : PersistenceAdapter.delete_all(self)
end
# Checks the existence of a persisted object with the specified id
def self.exists?(id)
repository? ? find_by(id: id).present? : PersistenceAdapter.exists?(self, id)
end
def self.persistence_class
return self if save_in_memory? || (postfix.nil? && self.model_class.nil?)
return "#{self}#{postfix.classify}".constantize if postfix.present?
self.model_class.to_s.constantize
end
def self.repository?
self == persistence_class
end
# Returns the Class responsible for persisting the objects
def self.get_model_class
puts '[deprecation warning] This method is going to be deprecated, use "persistence_class" instead.'
persistence_class
end
# Searches all objects that matches #field_name field with the #args value(s)
def self.find_by(args)
raise ArgumentError("Argument must be a Hash") unless args.is_a?(Hash)
objects = where(args)
objects.first
end
# Searches all objects that matches #field_name field with the #args value(s)
def self.find_by!(args)
object = find_by(args)
raise ActiveHash::RecordNotFound unless object
object
end
# Converts Persisted object(s) to it's ActiveRepository counterpart
def self.serialize!(other)
case other.class.to_s
when "Hash", "ActiveSupport::HashWithIndifferentAccess" then self.new.serialize!(other)
when "Array" then other.map { |o| serialize!(o.attributes) }
when "Moped::BSON::Document", "BSON::Document" then self.new.serialize!(other)
else self.new.serialize!(other.attributes)
end
end
# Returns an array with the field names of the Class
def self.serialized_attributes
field_names.map &:to_s
end
def self.persistence_class=(value)
self.model_class = value
end
# Sets the class attribute model_class, responsible to persist the ActiveRepository objects
def self.set_model_class(value)
puts '[deprecation warning] This method is going to be deprecated, use "persistence_class=" instead.'
persistence_class = value
end
def self.save_in_memory?
self.save_in_memory == nil ? true : self.save_in_memory
end
# Sets the class attribute save_in_memory, set it to true to ignore model_class attribute
# and persist objects in memory
def self.set_save_in_memory(value)
puts '[deprecation warning] This method is going to be deprecated, use "save_in_memory=" instead.'
self.save_in_memory = value
end
# Searches persisted objects that matches the criterias in the parameters.
# Can be used in ActiveRecord/Mongoid way or in SQL like way.
#
# Example:
#
# * RelatedClass.where(:name => "Peter")
# * RelatedClass.where("name = 'Peter'")
def self.where(*args)
raise ArgumentError.new("must pass at least one argument") if args.empty?
result_set = ActiveRepository::ResultSet.new(self)
result_set.where(args)
# if repository?
# args = args.first if args.respond_to?(:size) && args.size == 1
# query_executor = SqlQueryExecutor::Base.new(all)
# query_executor.where(args)
# else
# objects = PersistenceAdapter.where(self, sanitize_args(args)).map do |object|
# self.serialize!(object.attributes)
# end
# objects
# end
end
def persistence_class
self.class.persistence_class
end
def get_model_class
puts '[deprecation warning] This method is going to be deprecated, use "persistence_class" instead.'
self.class.persistence_class
end
# Persists the object using the class defined on the model_class attribute, if none defined it
# is saved in memory.
def persist
if self.valid?
save_in_memory? ? save : self.convert.present?
end
end
# Gathers the persisted object from database and updates self with it's attributes.
def reload
object = self.id.present? ?
persistence_class.where(id: self.id).first_or_initialize :
self
serialize! object.attributes
end
def save(force=false)
execute_callbacks(before_save_methods)
result = true
if self.class == persistence_class
object = persistence_class.where(id: self.id).first_or_initialize
result = if force || self.id.nil?
self.id = nil if self.id.nil?
super
elsif self.valid?
object.attributes = self.attributes.select{ |key, value| self.class.serialized_attributes.include?(key.to_s) }
object.save(true)
end
else
result = self.persist
end
execute_callbacks(after_save_methods)
# (after_save_methods || []).each { |method| self.send(method) }
result
end
# Updates attributes from self with the attributes from the parameters
def serialize!(attributes)
unless attributes.nil?
attributes.each do |key, value|
key = "id" if key == "_id"
self.send("#{key}=", (value.dup rescue value))
end
end
self.dup
end
protected
# Find related object on the database and updates it with attributes in self, if it didn't
# find it on database it creates a new one.
def convert(attribute="id")
klass = persistence_class
object = klass.where(attribute.to_sym => self.send(attribute)).first
object ||= persistence_class.new
attributes = self.attributes.select{ |key, value| self.class.serialized_attributes.include?(key.to_s) }
attributes.delete(:id)
object.attributes = attributes
object.save
self.id = object.id
object
end
private
def self.add_callbacks(kind, methods, options)
methods = methods.map { |method| {method: method, options: options} }
current_callbacks = (self.send("#{kind}_methods") || [])
self.send("#{kind}_methods=", (current_callbacks + methods)).flatten
end
private_class_method :set_callback
def execute_callbacks(callbacks)
Array(callbacks).each do |callback|
method = callback[:method]
options = callback[:options]
if_option = !!options[:if].try(:call, self)
else_option = options[:else].try(:call, self)
if if_option || !(else_option.nil? ? true : else_option)
self.send(method)
end
end
end
# Updates created_at and updated_at
def set_timestamps
if self.errors.empty?
self.created_at = DateTime.now.utc if self.respond_to?(:created_at=) && self.created_at.nil?
self.updated_at = DateTime.now.utc if self.respond_to?(:updated_at=)
end
end
end
end