Rails の require_dependency とは何か?
Rails Guide を見てみますが、いまいちピンとこないのでソースを見てみました。 http://railsguides.jp/autoloading_and_reloading_constants.html#require-dependency
先にまとめ
require_dependency とは、production環境では require
し
development環境では Kernel.load
する。
Kernel.load
は呼ばれるたびにロードされるので、開発時には効率が良い。
通常は、autoload でことが足りそうな気がしますが、
何かautload_pathsに含まれないものをrequireする際には使うと良いでしょう。
ソース見てみるよー
require_dependency
activesupport-5.0.0.1/lib/active_support/dependencies.rb
def require_dependency(file_name, message = "No such file to load -- %s") file_name = file_name.to_path if file_name.respond_to?(:to_path) unless file_name.is_a?(String) raise ArgumentError, "the file name must either be a String or implement #to_path -- you passed #{file_name.inspect}" end Dependencies.depend_on(file_name, message) end
depend_on を呼んでます。
depend_on
def depend_on(file_name, message = "No such file to load -- %s.rb") path = search_for_file(file_name) require_or_load(path || file_name) rescue LoadError => load_error if file_name = load_error.message[/ -- (.*?)(\.rb)?$/, 1] load_error.message.replace(message % file_name) load_error.copy_blame!(load_error) end raise end
require_or_load を呼んでいます。
別件ですが、なんかdefaultのmessageが呼び出し元と若干違いますね。
少し違和感があります(後でもう少し見てみるかも)。
require_or_load 前半
少し長いので分割。
def require_or_load(file_name, const_path = nil) file_name = $` if file_name =~ /\.rb\z/ expanded = File.expand_path(file_name) return if loaded.include?(expanded) Dependencies.load_interlock do # Maybe it got loaded while we were waiting for our lock: return if loaded.include?(expanded) # Record that we've seen this file *before* loading it to avoid an # infinite loop with mutual dependencies. loaded << expanded loading << expanded
ActiveSupport::Concurrency::ShareLock を使って安全にロックを掛けつつ
loaded
に読み込み済みのリスト(Set)を保持しています。
こんな感じ。
#<Set: {"/Users/rochefort/work/rails5_example/app/controllers/articles_controller", "/Users/rochefort/work/rails5_example/app/controllers/application_controller", "/Users/rochefort/work/rails5_example/app/helpers/application_helper", "/Users/rochefort/work/rails5_example/app/helpers/tags_helper", "/Users/rochefort/work/rails5_example/app/helpers/user_sessions_helper", "/Users/rochefort/work/rails5_example/app/helpers/users_helper", "/Users/rochefort/work/rails5_example/app/helpers/articles_helper", "/Users/rochefort/work/rails5_example/app/models/article", "/Users/rochefort/work/rails5_example/app/models/application_record", "/Users/rochefort/work/rails5_example/app/uploaders/image_uploader"}>
require_or_load 後半
begin if load? # Enable warnings if this file has not been loaded before and # warnings_on_first_load is set. load_args = ["#{file_name}.rb"] load_args << const_path unless const_path.nil? if !warnings_on_first_load or history.include?(expanded) result = load_file(*load_args) else enable_warnings { result = load_file(*load_args) } end else result = require file_name end
load? がtrueの場合は、load_file を呼んでいますが、falseの場合は require しているだけのようです。
load_file
まずは load_file を見てみると Kernel.load しています。
def load_file(path, const_paths = loadable_constants_for_path(path)) const_paths = [const_paths].compact unless const_paths.is_a? Array parent_paths = const_paths.collect { |const_path| const_path[/.*(?=::)/] || ::Object } result = nil newly_defined_paths = new_constants_in(*parent_paths) do result = Kernel.load path end autoloaded_constants.concat newly_defined_paths unless load_once_path?(path) autoloaded_constants.uniq! result end
load?
続いてload?
def load? mechanism == :load end
mechanism という値を参照しているだけです。
mechanism
こいつはどこから来るのか?
mattr_accessor :mechanism self.mechanism = ENV['NO_RELOAD'] ? :require : :load
一見、環境変数で出しわけているだけのようですが
おそらくconfigのファイルで出しわけてそうなので、config/production.rb を見てみます。
しかし、直接 mechanism を設定している箇所はありません。
ですが、以下が怪しそうです。
config.cache_classes = true
railties
railties-5.0.0.1/lib/rails/application/bootstrap.rb
module Rails class Application module Bootstrap # Sets the dependency loading mechanism. initializer :initialize_dependency_mechanism, group: :all do ActiveSupport::Dependencies.mechanism = config.cache_classes ? :require : :load end
ありました!
cach_classes の値を見て require / load を設定していました。
RailsのSQLiteで正規表現を使う
Postgres や MySQL では、書き方は違えど正規表現を使うことができます。 しかし、RailsのDevelopment(SQLite)で実行すると、エラーとなります。
SQLite3::SQLException: no such function: REGEXP
SQLiteでのREGEXP
こちらを見てみると SQLite3におけるREGEXP演算子 - めらんこーど地階
The REGEXP operator is a special syntax for the regexp() user function. No regexp() user function is defined by default and so use of the REGEXP operator will normally result in an error message. If an application-defined SQL function named "regexp" is added at run-time, then the "X REGEXP Y" operator will be implemented as a call to "regexp(Y,X)".
SQLite Query Language: expression を見てみると
確かに、application-defined SQL function で regexp を追加しろと記載があります。
Rails ではどうすれば良いか?
ググるとこちらのgemがありました。
Rails5でとりあえず動作するようにgemspecを修正したPRを出したら速攻で取り込んでもらえたので
今はGemfileに突っ込んだら使える状態です。
AaronLasseigne/sqlite3_ar_regexp
(以下のケースでは REGEXP 使う必要はないのですが、)
とりあえず動作するかどうかを見るために手元で動かしてみました。
names = ["ジェイミー・ヴァーディ", "中村憲剛"] Person.where("name REGEXP :names", names: names.join("|"))
以下のSQLが発行されます。 REGEXP が使用されているのが分かります。
SELECT "people".* FROM "people" WHERE (name REGEXP 'ジェイミー・ヴァーディ|中村憲剛')
どうやってるの?
sqlite3_ar_regexp/extension.rb at master · AaronLasseigne/sqlite3_ar_regexp
を見ると以下を利用しています。
connection.create_function('regexp', 2) do |func, pattern, expression|
てっきり SQLite側に仕込む必要があるかと思ったのですが、各種driver側でユーザ定義関数を作成できるメソッドがそれぞれ用意されているようでした。
sqlite3 だと以下。
Method: SQLite3::Database#create_function — Documentation for luislavena/sqlite3-ruby (master)
まとめ
とりあえず使ってみたい人向け。速度などは未検証。
Acts-as-taggable-on を拡張する
タグの管理にmbleigh/acts-as-taggable-on を使っています。 非常に使いやすくて重宝しているのですが、一部のタグのみ他のテーブルと関連づけしたかったので 拡張してみました。
やりたいこと
schema.rb
acts-as-taggable-on を使うと以下のようなテーブルが作成されます。
create_table "tags", force: :cascade do |t| t.string "name" t.integer "taggings_count", default: 0 t.index ["name"], name: "index_tags_on_name", unique: true end
データとしては以下のような感じです。
id=1,2 はカテゴリーですが、id=3 は人物です。
別途、人物を管理するpeopleテーブルを保有しているとすると、
このテーブルとのreferenceを付与するだけで、「通常のタグ一覧」と「人物一覧」が簡単に取り出せます。
id | name | taggings_count |
---|---|---|
1 | アート | 10 |
2 | 歴史 | 20 |
3 | ジェイミー・ヴァーディ | 3 |
referenceを追加すると以下のようなイメージです。
id | name | taggings_count | person_id |
---|---|---|---|
1 | アート | 10 | null |
2 | 歴史 | 20 | null |
3 | ジェイミー・ヴァーディ | 3 | 100 |
やり方
$ rails g migration AddPersonRefToTags person:references # migration file class AddPersonRefToTags < ActiveRecord::Migration[5.0] def change add_reference :tags, :person, foreign_key: true end end
config/initializers/acts_as_taggable_on.rb
ActsAsTaggableOn::Tag.class_eval do belongs_to :person end
model
point は class_name で ActsAsTaggableOn::Tag を指定すること。
class Person < ApplicationRecord has_one :tag, class_name: "ActsAsTaggableOn::Tag" end
データ抽出
記事にタグ付けされたタグの一覧と、人物タグの一覧を取り出します。(Articleにタグ付けをできるようにするのは、acts_as_taggable_on の基本機能なのでここでは割愛)
pointは、acts_as_taggable_on利用時にtags_on というメソッドが提供されるのですが、この引数に condisions が渡せるので以下のようにすれば良いです。
acts_as_taggable_on 親切設計ですね!
@tags = Article.tags_on(:tags, conditions: { person_id: nil }) @people = Article.tags_on(:tags, conditions: "person_id is not null")
あとは、has_one / belongs_to を設定しているので、各モデルのオブジェクトから辿ることができます。