Posted at

railsで動的にscopeを設定したい


初めに

Railsでmodelにスコープを書くことがあるのですがスコープの数が多くなりすぎることがあります。

それで動的にスコープが定義出来たらと思って自分で試してみました。


具体例

具体的な例として記事に言語の設定があるとします。

モデルはこんな感じ

erdplus-diagram.png

そんでこのlangはinteger型でenumを使います

なのでmodels/article.rbには


article.rb

class DataCollect < ApplicationRecord

enum lang: {ja: 0, en: 1, ar: 2, de: 3,el: 4, eo: 5, es: 6, fr: 7}
end

こんな感じに設定します

これでarticleを作成するときにlangを設定してあげれはその記事の言語がわかるようになります。


scopeを作成

scopeを作成するときに例えば

Article.ja.all

で日本語の記事がとれるようにするためにはmodels/article.rbに

scope :ja, -> {where(lang: :ja)}

とすれば実装できます。

しかし今回の例のように七個も言語があると

models/article.rbは

scope :ja, -> {where(lang: :ja)}

scope :en, -> {where(lang: :en)}
scope :ar, -> {where(lang: :ar)}
scope :de, -> {where(lang: :de)}
scope :el, -> {where(lang: :el)}
scope :eo, -> {where(lang: :eo)}
scope :es, -> {where(lang: :es)}

と書かなくてはいけなくて結構邪魔になります。

もっと増えたらもっと邪魔になるわけでこれをもっと短く書きたい!


class_evalを使ってみた

とりあえずclass_evalを使ってArticleのモデルにscopeを定義してみます。

使い方はこんな感じになりました。

Article.class_eval { scope ja, -> {where(lang: ja)}

これでscopeの設定ができるので後は配列を作ってループしたら出来上がり。

Lang = [ja, en, ar, de, el, eo, es, fr]

Lang.each do |l|
Article.classs_eval { scope l, -> {where(lang: l)}
end

これでOK


もう少しいじってみる

もう少しいじってみることにして言語のファイルがconfig/localesにあるのですがそのファイルを見てscopeを定義してみたいと思います。

どういうことかというと

例えばconfig/localesに

en.yml

ja.yml
en.yml

というファイルがあったらそれを見てen,ja,enというスコープを作るということです。

config/localesのファイル名を取得するために

Dir.chdir("/mnt/c/users/owner/desktop/app_name/config/locales")

lang = Dir.glob("*").map {|l| l.match(/../)[0] }

こうすればconfig/localesのファイル名が取得できます。

これでconfig/localesにある言語化ファイルがある言語のスコープを作成できました

完成全体像はこちらです。


class Article < ApplicationRecord
enum lang: {ja: 0, en: 1, ar: 2, de: 3,el: 4, eo: 5, es: 6, fr: 7}
end

Dir.chdir("/mnt/c/users/owner/desktop/latest_news/config/locales")
lang = Dir.glob("*").map {|l| l.match(/../)[0] }

lang.each do |l|
Article.class_eval { scope l, -> {where(lang: l)}}
end

逆にわかりにくいですね(笑)

おわり

メリークリスマス:snowman: