clowne-rb/clowne

Multiple Databases

yelvert opened this issue · 1 comments

First off, very nice looking gem, I actually just started work on something very similar, except my use case involves cloning records from a separate, but (mostly) identical database. I'm currently using a separate ApplicationRecord base class, for handling the alternate models to clone from, that looks like this.

module GuestHouse
  class ApplicationRecord < ::ActiveRecord::Base
    self.abstract_class = true
    establish_connection "guest_house_#{Rails.env}".to_sym

  end
end

So, if I have:

class Widget < ApplicationRecord
end

class GuestHouse::Widget < GuestHouse::ApplicationRecord
end

Then Widgets can be cloned from GuestHouse::Widgets

Could this gem handle that use case out of the box, or would it need some additional development, and if so, could you point me to a starting place?

Hi @yelvert!

First off, very nice looking gem

Thank you ;)

..cloning records from a separate database ...could this gem handle that use case out of the box?

An interesting case and we didn't think about this when we developed Сlowne. We always use the source class as the base class for cloning and it can't be overridden, but we have a magic init_as declaration.
So, please, look this proof of concept (especially AsAlien module):

At first, define models and schemas

require 'clowne'
require 'activerecord'
require 'sqlite3'

class ApplicationRecord < ::ActiveRecord::Base
  self.abstract_class = true
  establish_connection(adapter: 'sqlite3', database: 'file:memdb2?mode=memory')
end
class Widget < ApplicationRecord; end

module GuestHouse
  class ApplicationRecord < ::ActiveRecord::Base
    self.abstract_class = true
    establish_connection(adapter: 'sqlite3', database: 'file:memdb1?mode=memory')
  end

  class Widget < ApplicationRecord; end
end

ActiveRecord::Schema.define do
  self.verbose = false

  ApplicationRecord.connection.create_table :widgets, force: true do |t|
    t.string :name, null: false
    t.string :country, null: false
  end

  GuestHouse::ApplicationRecord.connection.create_table :widgets, force: true do |t|
    t.string :name, null: false
    t.string :country, null: false
  end
end

Then implement cloners

module AsAlien
  def self.included(base)
    base.class_eval do
      init_as do |source, params|
        target_class =
          if target_scope = params[:target_scope]
            target_scope.const_get(source.class.name.demodulize)
          else
            source.class
          end

        target_class.new(source.attributes)
      end
    end
  end
end

class WidgetCloner < Clowne::Cloner
  include AsAlien

  finalize do |_source, record|
    record.name = "[cloned] " + record.name
  end
end

class GuestHouse::WidgetCloner < Clowne::Cloner
  include AsAlien

  finalize do |_source, record|
    record.name = "[guest cloned] " + record.name
  end
end

And call it

w1 = Widget.create(name: 'News', country: 'ru')
w2 = GuestHouse::Widget.create(name: 'Weather', country: 'us')

# WidgetCloner:
WidgetCloner.call(w1)
=> #<Widget:0x00007fbb349c1f20 id: 1, name: "[cloned] News", country: "ru">

WidgetCloner.call(w2)
=> #<GuestHouse::Widget:0x00007fbb332c5c40 id: 1, name: "[cloned] Weather", country: "us">

WidgetCloner.call(w2, target_scope: Object)
=> #<Widget:0x00007fbb3209cd98 id: 1, name: "[cloned] Weather", country: "us">

# GuestHouse::WidgetCloner:
GuestHouse::WidgetCloner.call(w1)
=> #<Widget:0x00007fbb349311f0 id: 1, name: "[guest cloned] News", country: "ru">

GuestHouse::WidgetCloner.call(w2)
=> #<GuestHouse::Widget:0x00007fbb348f3b70 id: 1, name: "[guest cloned] Weather", country: "us">

GuestHouse::WidgetCloner.call(w1, target_scope: GuestHouse)
=> #<GuestHouse::Widget:0x00007fbb348b9ec0 id: 1, name: "[guest cloned] News", country: "ru">

Since we can proxy the parameters through all associations, this will allow you to clone objects under one scope (DB).
I hope this will be useful for you!