Techioz Blog

gem化されたPadrinoアプリモデルに外部からアクセスする方法(コントローラー内ではなく、スタンドアロンスクリプトなど)

概要

私は次のような(続編)モデルを指定するGusyと呼ばれるPadrinoアプリを持っています

# gusy/models/seminar.rb
class Seminar < Sequel::Model
  # hopefully irrelevant stuff defined here
end

2 番目の gem または bin/ 内のスクリプトからこのモデルにアクセスしたいと考えています。

さて、例えば2 番目の gem「gusy_fill」から Gusy が必要です。 Gemfile は、Gusy Git リポジトリへのパスを設定するために作成されます。バンドルコンソールを使用して対話的に探索すると、Gusy 名前空間を正常に表示できます (たとえば、アプリのバージョン Gusy::VERSION を出力します)。

マップされたモデルにアクセスするにはどうすればよいですか?また、データベース接続はどこでどのように構成すればよいですか? Padrino:: または Gusy:: モジュールには関連するものは何もありません。

IRB セッションは次のようになります。

require 'gusy'
Gusy::Seminar.create(:name => 'from gusy_fill' # => NameError: uninitialized constant Gusy::Seminar

Gusy をマウントする 2 つ目の Padrino アプリを作成せずにこれを実現したいと考えています (そのために、生成された gusy/README.md にポインターが含まれています)。

最初に述べたように、同じアプリ内でやりたいことを実行する場合、まったく同じ問題が発生します。実際には、padrino コンソールを呼び出すときのような設定で、データベースと通信する小さなスクリプトを gusy/bin に記述します。

解決策

ご迷惑をおかけして申し訳ございません。でも、あなたがそれを取り上げてくれたのは良かったです。なぜなら、私はしばらくこのテーマについて自分の考えをまとめようとしていて、これが私をこのテーマに追い込むきっかけになったからです :)。 Padrino にあるものを使ってそれを行う方法を説明するリポジトリを用意しました。

README (後で貼り付けます) では、その背後にある理由を説明し、実装方法について考えるためのいくつかの質問を提示します。それについてのあなたの意見をぜひ聞きたいです:)。

このリポジトリは答えを意図しています 「スタンドアロン」(bin/) スクリプトで Padrino モデルとデータベースにアクセスするにはどうすればよいですか?そして gem 化された Padrino アプリ モデルに、そのアプリを必要とする他の gem からアクセスする方法。

つまり、同様の性質の問題が 2 つあり、どちらも gemified アプリで定義されたモデルに関連しています。

まず、gemified-app があります。それは、Gem化されたPadrinoアプリです。模型も入ってます SomeModel と呼ばれるこのモデルには、property というフィールドが 1 つあります。

次に、access-gemified-app-without-padrino があります。 gem化されたアプリをロードするRubyスクリプト モデルにアクセスします。

最後に、gemified-app をロードして使用するだけの通常の Padrino アプリである別のアプリがあります。 そのモデル (SomeModel)。

Padrino g project gemified-app –orm Sequence –gem –tiny を使用してアプリを作成すると、 以下の gemspec:

# -*- encoding: utf-8 -*-
require File.expand_path('../lib/gemified-app/version', __FILE__)

Gem::Specification.new do |gem|
  gem.authors       = ["Darío Javier Cravero"]
  gem.email         = ["[email protected]"]
  gem.description   = %q{Padrino gemified app example}
  gem.summary       = %q{Padrino gemified app example}
  gem.homepage      = ""

  gem.files         = `git ls-files`.split($\)
  gem.executables   = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
  gem.test_files    = gem.files.grep(%r{^(test|spec|features)/})
  gem.name          = "gemified-app"
  gem.require_paths = ["lib", "app"]
  gem.version       = GemifiedApp::VERSION

  gem.add_dependency 'padrino-core'
end

重要なポイントは gem.require_paths = [“lib”, “app”] と gem.add_dependency ‘padrino-core’ です。

gem.require_paths = [“lib”, “app”] は、models/some_model.rb が利用できない理由を説明しています。 gemを別の場所にロードします。それは単純には追加されません:(。

gem.add_dependency ‘padrino-core’ は、後で何かが不足する可能性があることを示唆しています。何が起こるのですか ORM やレンダラーなどの依存関係はありますか?それらをロードする必要がありますか?それは問題だと思う あなたが達成したいことについては、ほとんどの場合、「はい」と言えます。

gem化されたアプリの依存関係は引き続き Gemfile にリストされており、 現在のスコープであり、gemified-app gem を必要とする gem には含まれていません。

これを機能させるには、次の 2 つのことを行う必要があります。

「models」を gem.require_paths = [“lib”, “app”] に追加すると、次のようになります。 gem.require_paths = [“lib”, “app”, “models”]。 これにより、gemified-app/models ディレクトリ内のすべてのものが確実に 宝石。

これをテストしやすくするために、bundler を使用し、access-gemified-app-without-padrino を使用します。 テスト スクリプトには、次のような Gemfile を追加します。

source 'https://rubygems.org'

gem 'gemified-app', path: '../gemified-app'
gem 'pry'

新しいアプリで、REPL バンドル exec pry に移動し、「gemified-app」を要求してみます。 次に、SomeModel.all を試してください。それは失敗します。なぜ? 「some_model」が必要なかったためです。

それでもそうしてもうまくいきません。なぜ?モデルの依存関係がないため、 つまり、Sequeller と sqlite3 (直接の依存関係ではなく、接続を通じて行われます) がロードされます。

ここでは 2 つの選択肢があります。Gemfile に手動でロードするか、次のように定義します。 gemified-app.gemspec への依存関係。 すでにモデルを含めているので、後者の方が良い選択だと思います。 依存関係がそれに伴うことを期待しています。これは次のようになります。

# gemified-app/gemified-app.gemspec

  # ...

  gem.add_dependency 'padrino-core'
  gem.add_dependency 'padrino-helpers'
  gem.add_dependency 'slim'
  gem.add_dependency 'sqlite3'
  gem.add_dependency 'sequel'
  gem.add_development_dependency 'rake'

  # ...


# gemified-app/Gemfile
source 'https://rubygems.org'

# Distribute your app as a gem
gemspec

必要なすべての gem を明示的に含める必要があります。これは面倒に思えるかもしれませんが、 公平に言えば、アプリが何を必要としているのかをより深く理解できるようになります。最終的にはそうするでしょう バンドラーと Gemfile さえ必要ないことに注意してください :)。

それでは、REPL を起動して、require ‘gemified-app’ と require ‘some_model’ を入力してください。 次に、SomeModel.all を試してください。そして… 失敗します:(。なぜでしょうか? Sequel::Base が定義されていないからです。ここで次のように疑問に思うかもしれません。 gemified-app.gemspec に入れた続編への参照はどうなったのでしょうか?まあ、それは次のとおりです。 リファレンスなので、Gem は必要ありません。 Padrino では次のことを使用しているため、これは起こりません。

require 'rubygems' unless defined?(Gem)
require 'bundler/setup'
Bundler.require(:default, RACK_ENV)

config/boot.rb にあると、必要な gem のみが Gemfile にロードされます。

そこで質問は…手動でロードすべきでしょうか?もしそうなら、どこでしょうか?

まあ、これは gem そのものなので、そうするのに最適な場所は lib/gemified-app.rb だと思います。 必要なすべての gem をロードすると、このファイルは次のようになります。

require 'padrino-core'
require 'padrino-helpers'
require 'slim'
require 'sqlite3'
require 'sequel'

module GemifiedApp
  extend Padrino::Module
  gem! "gemified-app"
end

さて、準備は完了です…REPL に戻り、要件を満たしてください

require 'gemified-app'
require 'some_model'

SomeModel.all を試してください。そして… それは失敗します :(。また! :/ なぜですか? なぜなら、 データベース。 Padrino は、config/database.rb を通じてこれをロードしていました。

別の疑問が生じます… config/database.rb も gem に含めるべきでしょうか? 私の見方では、そうすべきではありません。私の見方では、データベース接続はすべてのアプリに関係するものです アクセスするための特定の認証情報などが含まれる可能性があるため、ローカルで定義する必要があります。 サンプルの access-gemified-app-without-padrino/do-somethin.rb スクリプトは次のようになります。

require 'gemified-app'

Sequel::Model.plugin(:schema)
Sequel::Model.raise_on_save_failure = false # Do not throw exceptions on failure
Sequel::Model.db = Sequel.connect("sqlite:///" + File.expand_path('../../gemified-app/db/gemified_app_development.db', __FILE__), :loggers => [logger])

require 'some_model'

SomeModel.all.each do |model|
  puts %Q[#{model.id}: #{model.property}]
end

はい、接続コードは Padrino アプリとほぼ同じであり、そのデータベースを再利用しています。 この例では。

それは少しの道のりでした:)しかし、私たちはついにそれを達成しました。一部の動作については、リポジトリ内のサンプル アプリを参照してください。 例。

あなたを知りませんが、私はそれがまったく好きではありません。そのようなことをしなければならないということは、私が 使用したいものと衝突しないように、モデルの名前を慎重に選択する必要があります 将来。 モジュールがそれに対する答えだと思いますが、それが現状です。を参照してください。 これについて詳しくは結論を。

モデルレイヤーを独自の gem に分離し、(gem化されているかどうかにかかわらず) Padrino アプリからそれを要求します。 モデルのテストを分離し、作成することもできるので、これがおそらく最もクリーンかもしれません。 状況に応じて異なるモデルが存在し、その下で同じデータベースが使用される場合もあれば、使用されない場合もあります。

すべての接続の詳細をカプセル化することもできます。

私たちは、Gemified アプリに対する Padrino のアプローチを見直す必要があると思います。

ハード依存関係には Gemfile の代わりに gemspec を使用する必要がありますか?

モデルに名前空間を付ける必要がありますか (これに関して過去にいくつかの問題があったことは知っています)。

ユーザーに gem で明示的な require を行うよう教えるべきか、それとも依存関係を検査するよう教えるべきでしょうか。 彼らのためにそれらを必要としますか?

ユーザーに依存関係をロードする方法を教え、それについてもっと責任を持つべきでしょうか?最後に その日、彼らがジェム化されたアプリのルートを選択した場合、彼らは明らかにRubyにはるかに熟練しており、 この種のことに注意する必要があります。

考えは? :)