Одной из худших вещей, которые могут случиться с приложением Rails, является то, что SQL-запросы становятся огромным сложным условным беспорядком. Я сталкивался с действиями контроллера, которые строят строки запроса, используя метод «цепочки условий», например:
sql = "active= 1"
if condition
sql += "and important=1"
end
if second_condition
sql += "and important=1"
end
Article.where(sql)
По мере усложнения приложений извлечение необходимой информации может стать проблемой, если вы погрузитесь в использование запросов SQL. Это быстро становится обременительным, когда вы хотите выполнить разные запросы для отчетов и т. Д. По мере того, как команды становятся больше, все больше сотрудников требует более широкого доступа к данным приложения. Поскольку требуемые данные становятся более сложными, вы можете быстро получить очень запутанное действие контроллера и модель, дополненную специальными методами.
В этом уроке я надеюсь продемонстрировать, как связать области видимости с помощью метода send
Как часть цели, я хотел бы сохранить удобство прицелов.
Если вы задаетесь вопросом, что такое области действия, или даже размышляете над тем, что, черт возьми, представляет собой метод send
Они довольно просты.
Область Active Record — это Proc, который вы создаете внутри модели, используемой как вызов метода:
class Article < ActiveRecord::Base
enum status: [ :draft, :pending_review,:flagged, :published]
scope :drafts, -> { (where("`status` = ? ", 0)) } # 0 is :draft in the enum
end
drafts = Article.drafts
Метод send
Вы можете думать о send
drafts = Article.send("drafts")
Если это не имеет смысла, не волнуйтесь. Я собираюсь коснуться этого еще раз, чтобы вы получили лучшее представление. Давайте создадим миниатюрное приложение, чтобы узнать больше об этих двух понятиях.
Для начала мы собираемся создать простое приложение для блога:
$ rails new blog
Перейдите в этот каталог и сгенерируйте быстрый блог с множеством атрибутов:
$ rails generate scaffold Article title:string description:text status:integer author:string website:string meta_title:string meta_description:text
Перенос базы данных:
$ rake db:migrate
С нашей самой базовой платформой нам просто нужно заполнить ее данными. Для этого я настоятельно рекомендую камень Faker . В вашем Gemfile добавьте следующее:
gem 'faker'
Идите вперед и bundle install
Теперь в вашем файле db / seeds.rb добавьте следующее для генерации набора данных:
10.times do
Article.create(:title => Faker::Lorem.word, :description => Faker::Lorem.sentence, :status => 0)
Article.create(:title => Faker::Lorem.word, :description => Faker::Lorem.sentence, :status => 1)
Article.create(:title => Faker::Lorem.word, :description => Faker::Lorem.sentence, :status => 2)
Article.create(:title => Faker::Lorem.word, :description => Faker::Lorem.sentence, :status => 3)
end
10.times do
Article.create(:title => Faker::Lorem.word, :description => Faker::Lorem.sentence, :status => 0,:website => Faker::Internet.domain_name)
Article.create(:title => Faker::Lorem.word, :description => Faker::Lorem.sentence, :status => 1,:author => Faker::Name.first_name)
Article.create(:title => Faker::Lorem.word, :description => Faker::Lorem.sentence, :status => 2,:meta_title => Faker::Lorem.word)
Article.create(:title => Faker::Lorem.word, :description => Faker::Lorem.sentence, :status => 3,:meta_description => Faker::Lorem.sentence)
end
В терминале нам нужно ввести следующее, чтобы заполнить нашу базу данных разработки:
$ rake db:seed
Запустите сервер ( rails s
/articles
Имея некоторые данные, мы можем начать использовать области для извлечения соответствующих данных из нашего приложения. В нашем файле app / models / article.rb добавьте enum
class Article < ActiveRecord::Base
enum status: [ :draft, :pending_review,:flagged, :published]
scope :with_author, -> {(where("`author` IS NOT NULL ")) }
scope :with_website, -> {(where("`website` IS NOT NULL ")) }
scope :with_meta_title, -> {(where("`meta_title` IS NOT NULL ")) }
scope :with_meta_description, -> {(where("`meta_description` IS NOT NULL")) }
end
Прежде чем мы создадим пользовательскую часть приложения для создания отчетов, я всегда хочу убедиться, что все работает с консоли Rails. Вернитесь к терминалу, запустите консоль Rails и протестируйте одну из новых областей:
$ rails c
> Article.draft
=> #<ActiveRecord::Relation [#<Article id: 1, title: "labore", description: "Tempora debitis nihil illum vel vero suscipit cupi...", status: "draft", created_at: "2016-09-04 12:15:39", updated_at: "2016-09-04 12:15:39", author: nil, website: nil, meta_title: nil, meta_description: nil>]
Отличительной особенностью областей является то, что мы можем связать их вместе, чтобы создать более крупный оператор SQL:
> Article.published.with_meta_title
=> #<ActiveRecord::Relation [#<Article id: 1, title: "labore", description: "Tempora debitis nihil illum vel vero suscipit cupi...", status: "draft", created_at: "2016-09-04 12:15:39", updated_at: "2016-09-04 12:15:39", author: nil, website: nil, meta_title: "A meta Title to remember", meta_description: nil>]
Примечание: перечисление
status
Article.published
Надеюсь, вы начинаете видеть силу границ. Поскольку области видимости точно такие же, как методы, мы можем воспользоваться невероятно мощным методом send
> Article.send("draft")
=> #<ActiveRecord::Relation [#<Article id: 1, title: "labore", description: "Tempora debitis nihil illum vel vero suscipit cupi...", status: "draft", created_at: "2016-09-04 12:15:39", updated_at: "2016-09-04 12:15:39", author: nil, website: nil, meta_title: nil, meta_description: nil>]
Однако объединение методов в цепочку с использованием метода send
Нам нужно создать другой метод для нашей модели, чтобы иметь возможность динамически связывать методы. В app / models / article.rb добавьте следующее:
def self.send_chain(methods)
methods.inject(self, :send)
end
Этот метод принимает массив методов и вызовов, send
`send_chain позволяет нам динамически вызывать столько областей, сколько мы хотим. Например:
> Article.send_chain(["with_author", "pending_review"])
=> #<ActiveRecord::Relation [#<Article id: 82, title: "quia", description: "Adipisci nisi tempora culpa atque vel quo.", status: "pending_review", created_at: "2016-09-04 12:16:37", updated_at: "2016-09-04 12:16:37",. . .]
Позвольте мне теперь продемонстрировать, как мы можем использовать это в наших представлениях и нашем контроллере. В верхней части app / views / article / index.html.erb вставьте следующее:
<%= form_tag("/articles", method: "get") do %>
With Author<%= check_box_tag "article[methods][]", "with_author" %>
Pending Review<%= check_box_tag "article[methods][]", "pending_review" %>
Draft<%= check_box_tag "article[methods][]", "draft" %>
Flagged<%= check_box_tag "article[methods][]", "flagged" %>
Published<%= check_box_tag "article[methods][]", "published" %>
With Website<%= check_box_tag "article[methods][]", "with_website" %>
With Meta Title<%= check_box_tag "article[methods][]", "with_meta_title" %>
With Meta Description<%= check_box_tag "article[methods][]", "with_meta_description" %>
<%= submit_tag("Search") %>
<% end %>
Теперь о настоящей магии. В app / controllers / article_controller.rb измените действие index
def index
if params[:article]
methods = params[:article][:methods]
@articles = Article.send_chain(methods)
else
@articles = Article.all
end
end
Действие создаст огромный SQL-запрос, просто отметив флажки. Если вы отметите все флажки, вы увидите следующее в журналах разработки Rails:
Processing by ArticlesController#index as HTML
Parameters: {"utf8"=>"✓", "article"=>{"methods"=>["with_author", "pending_review", "draft", "flagged", "published", "with_website", "with_meta_title", "with_meta_description"]}, "commit"=>"Search"}
Rendering articles/index.html.erb within layouts/application
Article Load (1.0ms) SELECT "articles".* FROM "articles" WHERE (`author` IS NOT NULL ) AND (`status` = 1 ) AND (`status` = 0 ) AND (`status` = 2 ) AND (`status` = 3 ) AND (`website` IS NOT NULL ) AND (`meta_title` IS NOT NULL ) AND (`meta_description` IS NOT NULL)
Rendered articles/index.html.erb within layouts/application (60.8ms)
Вывод
Многие приложения Rails, с которыми я сталкивался в дикой природе, использовали огромные действия контроллера, которые пытались определить, что возвращать. Я даже видел шаблоны, где люди строят строки для передачи в Rails, where
sql = "active =1"
if condition
sql += "and important=1"
end
... tons of other conditions ...
Article.where(sql)
С помощью областей мы просто соединяем методы вместе, чтобы создать SQL-запрос на лету:
scope :active, -> {(where("`active` = 1")) }
scope :important, -> {(where("`important` = 1")) }
Затем мы можем использовать эти области для создания одного и того же SQL-запроса простым и понятным способом:
if condition
query = Article.active.important
end
В нашем примере блогового приложения объединение метода send
Это связано с тем, что бизнес-логика внедряется в модель, а контроллер используется для определения того, какая информация отображается. Обычно с помощью метода send
Добавление способа объединить методы в цепочку ( send_chain
$ methods = ["with_author", "pending_review", "draft", "flagged", "published", "with_website", "with_meta_title", "with_meta_description"]
$ Article.send_chain(methods)
Надеюсь, я дал хороший пример для объединения областей и метода send
Если у вас есть какие-либо отзывы, я хотел бы услышать в комментариях ниже.