Использование нескольких баз данных в Rails 3: как?

Пишу сайт, который должен будет работать с со сложной системой, имеющей свою, не рельсовую, структуру БД. Если говорить более конкретно — это сайт для обслуживания инфраструктуры серверов WOW на форке MaNGOS. Сложностей тут прилично: начиная с того, что в таблицах MaNGOS-a не всегда выполняются принятые в Rails и ActiveRecord соглашения, и заканчивая тем, что баз, с которыми нужно работать много: 1 общая realmd база c таблицами account итп, и по 3 (characters_N, mangos_N, scriptdev2_N) для каждого реалма (игрового мира). Сейчас крутится 2 реалма, поэтому всего баз, с которыми нужно работать 1 + 2*3 = 7. Ну ещё для самого сайта заведена отдельная база.







Вот последнюю проблему то (про несколько баз) я и не знаю как решать. Хотелось бы остаться на ActiveRecord, потому что к нему привык уже, поэтому DataMapper и альтернативы рассматриваются в последнюю очередь.

Структуры баз и таблиц в базах characters_N, mangos_N и scriptdev2_N похожи, но в некоторых местах различаются. Например в таблице characters_1.characters 29 столбцов, а в characters_2.characters их 30, добавился один столбец, но все такие изменения несущественны и могут быть опущены в модели.



И вот, вопрос, как удобнее организовать работу с такими базами… Задача, наверное, весьма нетривиальна для Rails программистов.

Кроме того хочется сделать модульную систему, что-бы можно было потом легко расширять количество миров и соответственно добавлять в систему characters_N+1, mangos_N+1 и scriptdev2_N+1 таблицы. Плюс хотелось бы иметь возможность создать модель, может быть без базы, но что-бы она имела все эти базы миров, и можно было бы делать что-то вроде World.each { |w| puts w.characters.online }, где online — это метод модели в таблице characters_N.characters.



Я пока-что мусолю следующую идею: сделать для каждой базы в модели отдельный неймспейс (соответственно отдельную директорию: app\models\wow\server_N\ например), и копировать туда все модели, потом в каждой менять неймспейс и establish_connection к нужной базе делать…



Напрмер так как то:



<pre>
class Wow::Base &lt; ActiveRecord::Base
  # ... тут что-то посвященное всем моделям из того
  def server_name
    # тут по неймспейсу определим номер нужной нам группы серверов
    1 # например выдается единица 
  end

  def database_name database, name; &quot;#{database.to_s}_#{id}&quot; end

end

class Wow::Server1::Base &lt; Wow::Base
end


class Wow::Server1::Character &lt; Wow::Server1::Base
  # тут соединяемся как-то так
  establish_connection database_name(:characters, server_name)
end
</pre><br/>




Тогда в контроллере можно было-бы использовать их так: @chars = Wow::Server1::Character.where(:online => 1).all

Но тогда возникает проблема модульности, да и вообще, плохо это, когда константы (в неймспейсе, а именно Server1) в таком участвуют. Может можно сделать как-нибудь вроде Wow::Server[1]::Character.all или SERVERS[1]::Characters.all или Wow::Characters[1].all или Wow::Characters.set_server(1).all… Кстати последнее я, возможно, даже знаю как реализовать… Только вот не уверен, что ассоциации (has_many, etc) будут работать :) Но всё ровно: что же ещё можно придумать?



Может можно попробовать применять метод расширения модели (гугль по «Rails model extensions (mincemeat models revisited)» ).



Вообщем насколько логично это всё выглядит и у кого какие есть ещё идеи? Помогите, а то уже голова всё не вмещает…
  • Вопрос задан
  • 3285 просмотров
Пригласить эксперта
Ответы на вопрос 2
@MOZGIII Автор вопроса
Я нашёл решение! Ура!

Главный сахар
app/models/wow/base.rb
class Wow::Base < ActiveRecord::Base
  self.abstract_class = true
  
  class << self
  
    def [](database_id)
      raise "DB name was not set! Use set_db_name to set it in yor model!" unless db_name
      self.abstract_class = true
      class_name = "#{database_id.to_s.classify}_#{self.name}"
      
      return Object.const_get(class_name) if Object.const_defined?(class_name)
      
      db_info = connection_info(database_id)
      raise ArgumentError, "There is no database with such ID!" unless db_info
      
      klass = Object.const_set class_name, Class.new(self)
      klass.establish_connection(db_info)
      klass
    end
    
    def set_db_name name; @@db_name = name; end
    def db_name; @@db_name; end
    
    private
    
    def connection_info database_id
      # эту не поясняю т.к. длинно, должна возвращать то, что принимает establish_connection
      WOW_CONFIG[database_id][:databases][db_name] 
    end
    
  end
end


Потом для всех баз создаём по файлу, в моём случае:
app/models/wow/characters_db.rb
app/models/wow/mangos_db.rb
app/models/wow/realmd_db.rb
app/models/wow/scriptdev2_db.rb

С примерно таким содержимым (пример для characters_db.rb, заменять подчёркнутое):

app/models/wow/characters_db.rb
class Wow::CharactersDb < Wow::Base
self.abstract_class = true
set_db_name :characters
end


Вот и всё!!! Ну, почти… :) Теперь немного изменим все файлы моделей, наследуем их все от нужных нам таблиц:

app/models/characters.rb
class Character < Wow::CharactersDb
set_primary_key :guid
belongs_to :account, :foreign_key => :account

scope :online, where(:online => 1)
scope :offline, where(:online => 0)

scope :alliance, where(:race => [1,3,4,7,11])
scope :horde, where(:race => [2,5,6,8,10])
end


И для отсальных моделей производим аналогичные замены.
Использовать после всего этого так:
Character[:main].count
Account[:legacy].all
… и т.п.
Это толком не протестировано на работу миграций, ассоциаций и прочего, но уже ночь, поду вообщем спать… :) Всем кто задумался над проблемой спасибо.
PS: что-то мне думается что с ассоциациями будет всё в порядке… Хотя может в одном месте поменять кое-что надо (там где "...}_#{...")…
Ответ написан
DanielWolf
@DanielWolf
раскопал исходники старого проекта там, так —

app/models/film.rb
class Film < ActiveRecord::Base
establish_connection "feed"
set_table_name "film"

has_many :presets, :class_name => "Preset", :foreign_key => "subject_id"
end


config/database.yml
feed:
adapter: mysql2
encoding: utf8
reconnect: false
database: feed
pool: 5
username: {someusername}
.........

production:
..........
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы