codeclimate/minidoc

View on GitHub
lib/minidoc.rb

Summary

Maintainability
B
4 hrs
Test Coverage
A
95%
require "mongo"
require "virtus"
require "active_model"
require "active_support/core_ext"

class Minidoc
  require "minidoc/associations"
  require "minidoc/connection"
  require "minidoc/counters"
  require "minidoc/finders"
  require "minidoc/grid"
  require "minidoc/read_only"
  require "minidoc/record_invalid"
  require "minidoc/duplicate_key"
  require "minidoc/timestamps"
  require "minidoc/validations"
  require "minidoc/value"
  require "minidoc/version"

  include Connection
  include Finders
  include Validations
  include Virtus.model
  extend ActiveModel::Naming
  include ActiveModel::Conversion
  include ActiveModel::Validations

  attribute :_id, BSON::ObjectId
  alias_attribute :id, :_id

  def self.delete_all
    collection.remove({})
  end

  def self.create(attrs = {})
    new(attrs).tap(&:save)
  end

  def self.create!(*args)
    new(*args).tap(&:save!)
  end

  def self.delete(id)
    collection.delete_one(_id: BSON::ObjectId(id.to_s))
  end

  def self.set(id, attributes)
    id = BSON::ObjectId(id.to_s)
    update_one(id, "$set" => attributes)
  end

  def self.unset(id, *keys)
    id = BSON::ObjectId(id.to_s)

    unsets = {}
    keys.each do |key|
      unsets[key] = 1
    end

    update_one(id, "$unset" => unsets)
  end

  def self.update_one(id, updates)
    collection.update_one({ "_id" => id }, updates)
  end

  def self.atomic_set(query, attributes)
    result = collection.update_one(query, "$set" => attributes)
    result.ok? && result.n == 1
  end

  def self.value_class
    @value_class ||= Class.new(self) do
      attribute_set.each do |attr|
        private "#{attr.name}="
      end

      private :attributes=
    end
  end

  # Rescue a duplicate key exception in the given block. Returns the result of
  # the block, or +false+ if the exception was raised.
  def self.rescue_duplicate_key_errors
    yield
  rescue Minidoc::DuplicateKey
    false
  rescue Mongo::Error::OperationFailure => ex
    if Minidoc::DuplicateKey.duplicate_key_exception(ex)
      false
    else
      raise
    end
  end

  def initialize(attrs = {})
    if attrs["_id"].nil? && attrs[:_id].nil?
      attrs[:_id] = BSON::ObjectId.new
    end

    @new_record = true
    @destroyed = false

    super(attrs)
  end

  def ==(other)
    other.is_a?(self.class) && self.id && self.id == other.id
  end

  def new_record?
    @new_record
  end

  def destroyed?
    @destroyed
  end

  def persisted?
    !(new_record? || destroyed?)
  end

  def delete
    self.class.delete(id)
  end

  def destroy
    delete
    @destroyed = true
  end

  def reload
    new_object = self.class.find(self.id) or raise DocumentNotFoundError

    self.class.attribute_set.each do |attr|
      self[attr.name] = new_object[attr.name]
    end

    self
  end

  def save
    valid? ? create_or_update : false
  end

  def save!
    valid? ? create_or_update : raise(RecordInvalid.new(self))
  end

  def set(attributes)
    self.class.set(id, attributes)

    attributes.each do |name, value|
      self[name] = value
    end
  end

  def unset(*keys)
    self.class.unset(id, *keys)

    keys.each do |key|
      self[key] = nil
    end
  end

  def atomic_set(query, attributes)
    query[:_id] = id

    if self.class.atomic_set(query, attributes)
      attributes.each do |name, value|
        self[name] = value
      end

      true
    end
  end

  def as_value
    self.class.value_class.new(attributes)
  end

  def to_key
    [id.to_s]
  end

  def to_param
    id.to_s
  end

private

  def create_or_update
    new_record? ? create : update
    @new_record = false
    true
  rescue Mongo::Error::OperationFailure => exception
    if (duplicate_key_exception = Minidoc::DuplicateKey.duplicate_key_exception(exception))
      raise duplicate_key_exception
    else
      raise
    end
  end

  def create
    self.class.collection.insert_one(attributes)
  end

  def update
    self.class.collection.update_one({ _id: id }, { "$set" => attributes.except(:_id) })
  end

  if ActiveSupport.respond_to?(:run_load_hooks)
    ActiveSupport.run_load_hooks(:mongo)
  end
end