Статьи

СУХОЙ код вашего Rails с ActiveSupport :: Проблемы

фен значок

Если вы какое-то время создавали приложения на Rails, вы, вероятно, заметили папку с названием concerns . Эта папка создается внутри каталогов app/controllers и app/models при каждом создании нового приложения Rails. Я думал, что это было бесполезно до недавнего времени, когда мы должны были использовать это на работе.

В этом коротком уроке я хочу показать вам, как использовать силу concerns .

Мы собираемся создать мини-приложение для твитов в Rails, я назову его Twik . В приложении у нас будет два TwitsController . Один контроллер будет пространством имен под admin (так что его действия доступны только администраторам), а другой будет в обычном пространстве имен Rails. Мы поделимся функциональностью, используя ActiveSuppourt::Concern чтобы убедиться, что мы ActiveSuppourt::Concern принцип СУХОЙ

Посмотрев, как это работает для контроллеров, я покажу вам, как использовать ActiveSupport::Concern в моделях.

Давайте начнем.

Настройка приложения

Создайте приложение на Rails:

 rails new twik -T 

Добавьте следующие gems в свой Gemfile :

 ... gem 'devise' gem 'bootstrap-sass' 

Теперь в bundle install ваши драгоценные камни.

Запустите команду для установки Devise:

 rails g devise:install 

Теперь давайте создадим нашу модель Admin :

 rails g devise Admin 

Переименуйте app / assets / stylesheets / application.css в app / assets / stylesheets / application.scss и вставьте следующее:

 #app/assets/stylesheets/application.scss @import "bootstrap-sprockets"; @import "bootstrap"; 

Перейдите в app / assets / javascripts / application.js и добавьте строку над последним require :

 ... //= require bootstrap-sprockets (ADD THIS) //= require_tree . 

Создайте файл app / views / layouts / _navigation.html.erb и вставьте следующее:

 #app/views/layouts/_navigation.html.erb <nav class="navbar navbar-default"> <div class="container-fluid"> <!-- Brand and toggle get grouped for better mobile display --> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-collapse" aria-expanded="false"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <% if admin_signed_in? %> <%= link_to "Twik", admin_twits_path, class: "navbar-brand" %> <% else %> <%= link_to "Twik", root_path, class: "navbar-brand" %> <% end %> </div> <div class="collapse navbar-collapse" id="navbar-collapse"> <ul class="nav navbar-nav navbar-right"> <li><%= link_to 'Home', root_path %></li> <% if admin_signed_in? %> <li><%= link_to 'My Account', edit_admin_registration_path %></li> <li><%= link_to 'Logout', destroy_admin_session_path, :method => :delete %></li> <% else %> <li><%= link_to 'Login', new_admin_session_path %></li> <% end %> </ul> </div> </div> </nav> 

Теперь отредактируйте app / views / layouts / application.html.erb, чтобы он выглядел так:

 #app/views/layouts/application.html.erb <!DOCTYPE html> <html> <head> <title>Twik</title> <%= csrf_meta_tags %> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> </head> <body> <%= render "layouts/navigation" %> <div class="container-fluid"> <%= yield %> </div> </body> </html> 

Контроллеры и проблемы Twits.

Мы хотим написать как можно меньше кода, чтобы мы не повторяли ничего. Чтобы достичь этого, мы будем использовать проблемы, чтобы разделить действия в обоих контроллерах. Смущенный? Сделайте следующее:

  • Создайте свою модель Twit: rails g model Twit tweet:text .
  • Создайте свой TwitsController: rails g controller TwitsController .
  • Перейдите в app / controllers / Концерны и создайте файл twitable.rb , вставив следующее:
  #app/controllers/concerns/twitable.rb module Twitable extend ActiveSupport::Concern included do before_action :set_twit, only: [:show, :edit, :destroy, :update] end def index @twits = Twit.all end def new @twit = Twit.new end def show end def create @twit = Twit.new(twit_params) if @twit.save flash[:notice] = "Successfully created twit." redirect_to @twit else flash[:alert] = "Error creating twit." render :new end end private def twit_params params.require(:twit).permit(:tweet) end def set_twit @twit = Twit.find(params[:id]) end end 

Теперь давайте рассмотрим код выше.

Использование extend ActiveSupport::Concern сообщает Rails, что мы создаем проблему. Код внутри included блока будет выполняться везде, где включен модуль. Это лучше всего для включения сторонних функций. В этом случае мы получим ошибку, если before_action записано вне included блока. На этом этапе мы можем включить наш модуль Twitable в контроллеры, которым необходимо такое поведение.

После этого наш TwitsController очень TwitsController :

 #app/controllers/twits_controllers.rb class TwitsController < ApplicationController include Twitable end 

Нам нужен каталог администратора для размещения контроллера для администраторов:

 mkdir app/controllers/admin touch app/controllers/admin/twits_controllers.rb 

Теперь вставьте этот код в файл, который вы только что создали:

 #app/controllers/admin/twits_controllers.rb class Admin::TwitsController < ApplicationController include Twitable def edit end def update if @twit.update_attributes(twit_params) flash[:notice] = "Successfully updated twit." redirect_to admin_twit_path else flash[:alert] = "Error creating twit." render :edit end end def destroy if @twit.destroy flash[:notice] = "Successfully deleted twit." redirect_to twits_path else flash[:alert] = "Error deleting twit." end end end 

Разве это не круто? Нам не нужно повторять наш код. Теперь давайте создадим представления, чтобы мы могли проверить, действительно ли все это работает:

 mkdir -p app/views/admin/twits touch app/views/admin/twits/index.html.erb touch app/views/admin/twits/new.html.erb touch app/views/admin/twits/show.html.erb touch app/views/admin/twits/edit.html.erb touch app/views/twits/new.html.erb touch app/views/twits/show.html.erb touch app/views/twits/index.html.erb 

Вставьте приведенный ниже код в соответствующие файлы.
Admin Twit Редактировать страницу

 #app/views/admin/twits/edit.html.erb <div class="container-fluid"> <div class="row"> <div class="col-sm-offset-4 col-sm-4 col-xs-12"> <%= form_for @twit, :url => {:controller => "twits", :action => "update" } do |f| %> <div class="form-group"> <%= f.label :tweet %> <%= f.text_field :tweet, class: "form-control" %> </div> <div class="form-group"> <%= f.submit "Update", class: "btn btn-primary" %> <%= link_to "Cancel", :back, class: "btn btn-default" %> </div> <% end %> </div> </div> </div> 

Индексная страница администратора Twit

 #app/views/admin/twits/index.html.erb <div class="container-fluid"> <p id="notice"><%= notice %></p> <h1>Listing Twits</h1> <div class="row"> <div class="col-sm-12 col-xs-12"> <%= link_to "New Tweet", new_admin_twit_path, class: "btn btn-primary pull-right" %> </div> </div> <div class="row"> <div class="col-sm-12 col-xs-12"> <div class="table-responsive"> <table class="table table-striped table-bordered table-hover"> <tbody> <% @twits.each do |twit| %> <tr> <td class="col-sm-8 col-xs-8"><%= twit.tweet %></td> <td class="col-sm-4 col-xs-4"><%= link_to 'Show', admin_twit_path(twit), class: "btn btn-primary" %> <%= link_to 'Edit', edit_admin_twit_path(twit), class: "btn btn-default" %> <%= link_to "Delete", admin_twit_path(twit), class: "btn btn-danger", data: {:confirm => "Are you sure?"}, method: :delete %> </td> </tr> <% end %> </tbody> </table> </div> </div> </div> </div> 

Admin Новая страница Twit

 #app/views/admin/twits/new.html.erb <div class="container-fluid"> <div class="row"> <div class="col-sm-offset-4 col-sm-4 col-xs-12"> <%= form_for @twit do |f| %> <div class="form-group"> <%= f.label :tweet %> <%= f.text_field :tweet, class: "form-control" %> </div> <div class="form-group"> <%= f.submit "Submit", class: "btn btn-primary" %> <%= link_to "Cancel", :back, class: "btn btn-default" %> </div> <% end %> </div> </div> </div> 

Admin Twit Показать страницу

 #app/views/admin/twits/show.html.erb <div> <h2><%= @twit.tweet %></h2> </div> 

Индексная страница Twit

 #app/views/twits/index.html.erb <div class="container-fluid"> <p id="notice"><%= notice %></p> <h1>Listing Twits</h1> <div class="row"> <div class="col-sm-12 col-xs-12"> <%= link_to "New Tweet", new_twit_path, class: "btn btn-primary pull-right" %> </div> </div> <div class="row"> <div class="col-sm-12 col-xs-12"> <div class="table-responsive"> <table class="table table-striped table-bordered table-hover"> <tbody> <% @twits.each do |twit| %> <tr> <td><%= twit.tweet %></td> <td><%= link_to 'Show', twit, class: "btn btn-primary" %></td> </tr> <% end %> </tbody> </table> </div> </div> </div> </div> 

Новая страница Twit

 #app/views/twits/new.html.erb <div class="container-fluid"> <div class="row"> <div class="col-sm-offset-4 col-sm-4 col-xs-12"> <%= form_for @twit do |f| %> <div class="form-group"> <%= f.label :tweet %> <%= f.text_field :tweet, class: "form-control" %> </div> <div class="form-group"> <%= f.submit "Submit", class: "btn btn-primary" %> <%= link_to "Cancel", :back, class: "btn btn-default" %> </div> <% end %> </div> </div> </div> 

Twit Show Page

 #app/views/twits/show.html.erb <div> <h2><%= @twit.tweet %></h2> </div> 

Теперь запустите сервер Rails, запустив rails server . Перейдите через ваш сайт, и вы увидите, что все работает отлично. Ваша кодовая база аккуратна, а контроллеры тонкие, благодаря concern .

Проблемы в моделях

ActiveSupport::Concern работает в моделях Rails, как мы видели в контроллерах. Если вы поймете, что мы сделали выше, вы сможете реализовать это в своих моделях, где это необходимо. Допустим, у нас есть функция reply в нашем приложении. С помощью этой функции пользователи могут отвечать на твиты (дух). Наряду с функцией ответа у нас есть функция голосования, позволяющая пользователям голосовать за твиты и ответы. Итак, теперь у нас есть три модели: Twit , Reply и Vote .

Twit и Reply имеют много votes , поэтому их модели выглядят так:

 class Twit < ActiveRecord::Base has_many :votes, as: :votable has_many :replies def vote! votes.create end end class Reply < ActiveRecord::Base has_many :votes, as: :votable belongs_to :twits def vote! votes.create end end class Vote < ActiveRecord::Base belongs_to :votable, polymorphic: true end 

Используя заботы, вы можете сделать вещи красивыми и аккуратными. Вот как вы можете это сделать:

 module Votable extend ActiveSupport::Concern included do has_many :votes, as: :votable end def vote! votes.create end end class Twit < ActiveRecord::Base include Votable has_many :replies end class Reply < ActiveRecord::Base include Votable belongs_to :twit end 

Я уверен, что вы согласитесь со мной, что этот путь намного лучше.

Вывод

Цель этого урока проста. Я просто хотел показать вам способ ActiveSupport::Concern принципу СУХОЙ, используя ActiveSupport::Concern . Я надеюсь, что это стоило времени 🙂