ActiveRecordでの内部結合

ActiveRecordでの内部結合

複雑なER図でのActiveRecordの使い方を勉強したので、いくつかの代表例をこちらにまとめます
ER図は、以下になります(IE記法で[1対多]の表記が逆になってしまっています)

https://img.esa.io/uploads/production/attachments/2315/2020/06/25/53778/767019fe-45ce-4964-8a4f-b72307a40132.png

・内部結合したテーブルにwhereメソッドで条件を加える方法を以下に示します

テーブルのそれぞれの関係は、以下のようになります

class Film < ActiveRecord::Base
  has_many :inventories
end
class Inventory < ActiveRecord::Base
  has_many :rentals
  belongs_to :film
end
class Rental < ActiveRecord::Base
  belongs_to :inventory
end

上記のようなテーブル関係にある時に、[2005年に貸し出ししていて、まだ返って来ていない映画の一覧(Filmテーブルから取得するカラムは、film_idとtitle)]を取得する場合は、 取得する[Filmsのテーブル]と貸出状況が分かる[Rentalsテーブル]を使うことになるが、この両者のテーブルは互いに直接繋がっていないので、間にある[Inventoriesテーブル]を通して繋げる
イメージとしては、[Filmsテーブル]に[Inventoriesテーブル]をくっつけて、そこに[Rentalsテーブル]をくっつけることになる

以下のコードのように記述すると、[2005年に貸し出ししていて、まだ返って来ていない映画の一覧]を取得できる

Film.select(:film_id, :title).joins(inventories: :rentals).where(rental: { rental_date: Time.new(2005).beginning_of_year..Time.new(2005).end_of_year, return_date: nil })

+---------+------------------+
| film_id | title            |
+---------+------------------+
| 1       | ACADEMY DINOSAUR |
+---------+------------------+

# where(rental: { rental_date:  で内部結合したテーブルのうちrentalテーブルのrental_dateカラムという書き方ができる

・ネスト化した内部結合でテーブルをグループ化する方法を以下に示します

テーブルのそれぞれの関係は、以下のようになります

class Category < ActiveRecord::Base
  has_many :film_categories
  has_many :films, through: :film_categories
end
class Film < ActiveRecord::Base
  has_many :film_categories
  has_many :categories, through: :film_categories
  has_many :inventories
end
class Inventory < ActiveRecord::Base
  has_many :rentals
  belongs_to :film
end
class Rental < ActiveRecord::Base
  has_many :payments
  belongs_to :inventory
end
class Payment < ActiveRecord::Base
  belongs_to :rental
end

上記のようなテーブル関係にある時に、[カテゴリごとに売上を集計して上位5つ]を取得する場合は、 取得する[categoriesのテーブル]と売上が分かる[paymentsテーブル]を使うことになるが、この両者のテーブルは互いに直接繋がっていないので、間にある[filmsテーブル],[inventoriesテーブル],[rentalsテーブル]を通して繋げる

以下のコードのように記述すると、[カテゴリごとに売上を集計して上位5つ]を取得できる

Category.select("category.category_id, category.name, sum(payment.amount) as revenue").joins(films: { inventories: { rentals: :payments } }).group("category.category_id, category.name").limit(5).order(revenue: :desc)

+-------------+-----------+---------+
| category_id | name      | revenue |
+-------------+-----------+---------+
| 15          | Sports    | 5314.21 |
| 14          | Sci-Fi    | 4756.98 |
| 2           | Animation | 4656.3  |
| 7           | Drama     | 4587.39 |
| 5           | Comedy    | 4383.58 |
+-------------+-----------+---------+

# 内部結合をネスト化していたりして、記述が複雑だが以下の画像で説明しています

・複数の関連付けを内部結合する方法を以下に示します

テーブルのそれぞれの関係は、以下のようになります

class Film < ActiveRecord::Base
  has_many :film_actors
  has_many :actors, through: :film_actors
  has_many :inventories
end
class Actor < ActiveRecord::Base
  has_many :film_actors
  has_many :films, through: :film_actors
end
class Inventory < ActiveRecord::Base
  has_many :rentals
  belongs_to :film
end
class Rental < ActiveRecord::Base
  has_many :payments
  belongs_to :inventory
end
class Payment < ActiveRecord::Base
  belongs_to :rental
end

上記のようなテーブル関係にある時に、[JOE SWANKが出演している映画で、売れてるランキングトップ10]を取得する場合は、 取得する[filmsテーブル]と売上が分かる[paymentsテーブル]と俳優が分かる[actorsテーブル]を使うことになるが、[filmsテーブル]と[paymentsテーブル]、[filmsテーブル]と[paymentsテーブル]は互いに直接繋がっていないので、間にある[inventoriesテーブル],[rentalsテーブル],[paymentsテーブル]を通して繋げる

以下のコードのように記述すると、[JOE SWANKが出演している映画で、売れてるランキングトップ10]を取得できる

Film.select("film.film_id, film.title, sum(payment.amount) as revenue").joins(:actors, inventories: { rentals: :payments }).where(actor: { first_name: "JOE", last_name: "SWANK" }).limit(10).group(:film_id).order(revenue: :desc)

+---------+------------------------+---------+
| film_id | title                  | revenue |
+---------+------------------------+---------+
| 865     | SUNRISE LEAGUE         | 170.76  |
| 964     | WATERFRONT DELIVERANCE | 121.83  |
| 873     | SWEETHEARTS SUSPECTS   | 98.71   |
| 434     | HORROR REIGN           | 87.73   |
| 510     | LAWLESS VISION         | 86.84   |
| 889     | TIES HUNGER            | 80.89   |
| 74      | BIRCH ANTITRUST        | 74.89   |
| 514     | LEBOWSKI SOLDIERS      | 72.79   |
| 650     | PACIFIC AMISTAD        | 62.76   |
| 811     | SMILE EARRING          | 58.9    |
+---------+------------------------+---------+

# 内部結合をネスト化していたりして、記述が複雑だが以下の画像で説明しています

参考記事

【Rails】 joinsメソッドのテーブル結合からネストまでの解説書 | Pikawaka - ピカ1わかりやすいプログラミング用語サイト

Railsのjoinsで3つのテーブルをくっつける|えりりん|note

ActiveRecordにおけるGROUP BYの使い方 - Qiita

Railsの便利な日時メソッド

Railsの便利な日時メソッド

Railsで開発をしていく中で、日時のメソッドを使うことがあります
今回は、Railsの便利な日時のメソッドをこちらにまとめます

[new]メソッド

irb(main):113:0> Time.new(2015,10,01)
=> 2015-10-01 00:00:00 +0900

irb(main):116:0> Time.new(2015)
=> 2015-01-01 00:00:00 +0900

# newメソッドに引数を渡すと、その日時でTimeインスタンスが生成される
   newメソッドに年の引数のみを渡しても、その年でTimeインスタンスが生成される

[beginning_of_year]メソッド

irb(main):115:0> Time.new(2015,10,01).beginning_of_year
=> 2015-01-01 00:00:00 +0900

# レシーバのTimeインスタンスの年の最初の日を返す

[.end_of_year]メソッド

irb(main):117:0> Time.new(2015,10,01).end_of_year
=> 2015-12-31 23:59:59 +0900

# レシーバのTimeインスタンスの年の最後の日を返す

ActionMailerのdeliver_nowとdeliver_laterの違い

ActionMailerのdeliver_nowとdeliver_laterの違い

ActionMailerを実装している中で、[deliver_now]メソッドと[deliver_later]メソッドがあり、両者の違いが分からなかった為、こちらにまとめます

[deliver_now]メソッド: 同期処理に送信される(ジョブの状態に関係なく、現時点ですぐにメールが送信される)
[deliver_later]メソッド: 非同期処理で送信される(現時点ですぐにメールが送信されずに、ジョブのキューにプッシュされる。ジョブが実行されていない場合は、メールが送信されない)


非同期処理:時間がかかってしまうような重い処理は、後でやる処理としてリスト化して登録する

Railsガイドには、以下のように記載してある

Image from Gyazo

参考記事

Action Mailer - Qiita

ruby on rails - アクションジョブ/メーラーの `deliver_now`と` deliver_later`の違い

Active Jobについて - Qiita

ActionMailerのプレビュー

[ActionMailerのプレビュー]について

[ActionMailer]のプレビュー機能が便利だった為、こちらにまとめます
[ActionMailer]のプレビュー機能を使えば、実際にメールが送信されることはないので、何度もメールのレイアウトを確認することができる

プレビュー機能の手順を以下に示します

下記コマンドで、[ActionMailer]を生成する

$ rails generate mailer (メーラー名)


上記で[ActionMailer]を生成した際に、[spec/mailers/previews/]のフォルダ以下にpreviewのファイルが生成されているので、こちらに記載する。
Railsガイドには、[test/mailers/previews/]フォルダ以下にpreviewファイルを配置するとなっており、文章のまま[test/mailers/previews/]を作ってしまったので注意が必要!!
以下に例を示します

[app/mailers/article_mailer.rb]

class ArticleMailer < ApplicationMailer
  def report_summary
    mail(to: 'admin@example.com', subject: '公開済記事の集計結果')
  end
end
[app/mailers/previews/article_mailer_preview.rb]

class ArticleMailerPreview < ActionMailer::Preview
  def report_summary                     # メーラーと同じメソッド名で定義する
    ArticleMailer.report_summary   #送信したいメーラーを呼び出す[(メーラー名).(メーラーのメソッド)]
  end
end


サーバーを起動して以下のURLにアクセスすると、メールのプレビューが確認できる

http://localhost:3000/rails/mailers/(メーラー名)/(メソッド名)
   ① 以下が例
http://localhost:3000/rails/mailers/user_mailer/welcome_email


http://localhost:3000/rails/mailersで、使用できるメーラーが以下のように表示される

Image from Gyazo


メソッド名をクリックすると以下のようにメールがプレビューで表示される(このURLが①になる)
このメソッド名のリンク(report_summary)は、previewクラスに作成したreport_summaryメソッドに対応して表示される

Image from Gyazo

※ポイント
[ActionMailer]のプレビュー機能では、メールが送信されているわけではないのでプレビューで表示されるからメールの送信機能も動いていると勘違いしないようにする

参考記事

【Rails入門】Action Mailerのメール送信を初心者向けに基礎から解説 | 侍エンジニアブログ

オブジェクト指向とgetterとsetterとカプセル化

[オブジェクト指向とgetterとsetterとカプセル化]

[オブジェクト指向],[getter],[setter],[カプセル化]という言葉を見かけることが多いが、それらの意味をあまり理解していなかったのでこちらにまとめます

[オブジェクト指向]
○○指向というのは、〇〇を重視しているというような意味になる
例えば、恋人を選ぶ際にルックス指向や性格指向といった感じ
オブジェクト指向プログラミングというのは、オブジェクトをたくさん作ってアプリケーションを作るという事
Twitterで例えたら、ユーザーのアカウントというオブジェクトやTweetというオブジェクト、フォローというオブジェクトなどが集まってTwitterが出来上がるということ

[getter]と[setter]
[getter]というのは、クラスの外からオブジェクトの属性の値を参照できるようにするメソッドのこと
[setter]というのは、クラスの外からオブジェクトの属性の値を変えることができるようにするメソッドのこと
今回のように、オブジェクトの属性値を変更できるオブジェクトの事をミュータブルなオブジェクトという
以下に例を示します

class Food
  def name=(text)   # setter: クラスの外からname属性に値をセットしたり変更したりできる
    @name = text
  end

  def name      # getter: クラスの外からname属性の値を参照できる
    @name
  end
end

food = Food.new        
food.name = "寿司"
p food.name    => "寿司"

[カプセル化]
[カプセル化]というのは、クラスの外からオブジェクトの属性の値を参照出来ないようにしたり、値を更新できないようにすること
基本的には、オブジェクトの属性値は外部から値が更新されないようにカプセル化した方が良いことがほとんど
今回のように、オブジェクトの属性値が変更できないオブジェクトの事をイミュータブルなオブジェクトという
以下に例を示します

class Food

private
  def name=(text)
    @name = text
  end

  def name
    @name
  end
end

food = Food.new
food.name = "寿司"     => エラーになる     # クラスの中に[getter]と[setter]を記述しているが、privateにする事でクラスの外から[getter]と[setter]を呼び出せないようにして、クラスの外から値を参照したり、値を更新できないようにしている<br>
p food.name                 => エラーになる

参考記事

www.youtube.com

www.youtube.com

複数ファイルのアップロード

複数ファイルのアップロード

複数ファイルをアップロードしたい時は、以下のように記述する(simple_formの場合)

[app/views ファイル]

= f.input :main_images, as: :file, input_html: { multiple: true }    # input_html: { multiple: true } というオプションを付ける
[app/controllers ファイル]

  def site_params
    params.require(:site).permit(:name, :subtitle, :description, :favicon, :og_image, main_images: [])
  end

上記のようにコントローラでストロングパラメータで複数ファイルをアップロードしたものを受け取る際は、配列になるのでmain_images: []のように記述する

参考記事

【Rails 5.2】 Active Storageの使い方 - Qiita

Swiper

Swiper

Swiperは、画像などを動的にスライドできるjQueryプラグイン

導入手順は、以下のようになる

$ yarn add swiper


[node_modules]にswiperがインストールされる
swiperの公式にあるように[swiper-bundle.css]と[swiper-bundle.js]を読み込むようにしなければいけない

Image from Gyazo

今回は、swiperを使用するlayoutファイルに[application.js]と[application.css.scss]を使用していると仮定しています

[app/assets/javascripts/application.js]

//= require swiper/swiper-bundle.js
[app/assets/stylesheets/application.css.scss]

@import 'swiper/swiper-bundle';

上記のassetに記述したファイルのパスを以下に記述することで、絶対パスで記述しなくても読み込める

[config/initializers/assets.rb]

Rails.application.config.assets.paths << Rails.root.join('node_modules')
[app/views/layouts/_header.html.slim]

header
  .swiper-container
    .swiper-wrapper   # スライドさせたい部分にクラスを付ける①
      - if current_site.main_images.present?
        - current_site.main_images.each do |main_image|
          = image_tag url_for(main_image), class: 'swiper-slide'    #  スライドさせる対象にswiper-slideというクラスを付ける
      - else
        = image_tag '/images/cover.jpg', class: 'swiper-slide'
  .container.blog-title
    h1 = link_to current_site.name, root_path
    p.lead = current_site.subtitle

javascript:
  $(document).ready(function() {
    new Swiper('.swiper-container', {       #   ①のクラス名を利用
      loop: true,
      autoplay: {
        delay: 3000,
      },
    })
  })

※ポイント

$(document).ready(function() {
  # 指定した処理
});

上記の記述は、DOMの読み込みが終わったらfunction()の中の処理を実行するという意味
JavaScriptは、HTMLが全て読み込まれてから出ないと正しく動作しない為、これを記述する
非同期通信(remote: true)で何かをクリックした時とかに[js.erb]ファイルをレスポンスする時は、ブラウザ上で画面が既に表示されているので、DOMの読み込みが終わっているが、今回のようにブラウザに画面が表示されるのと同時にJavaScriptを使う場合とかだと画面上のDOMが読み込まれているか分からないので、 [$(document).ready(function() { });] を記述する

参考記事

jQueryの基本 - $(document).ready - Qiita

JavaScriptの$(document).readyの使い方を現役エンジニアが解説【初心者向け】 | TechAcademyマガジン

jQuery入門講座 使い方-jQueryの省略構文

swiperをyarnで導入して、画像をスライダー形式にする! - Qiita

Swiperの導入 - manabuのまなび

【2021年版 RailsアプリにSwiper.jsでカルーセルを実装】 - Qiita

RailsでSwiperを導入する方法(Swiperは2020年7月にバージョンアップし、従来と設定方法が変わりました!) - Qiita

RailsでSwiperを導入する方法(Swiperは2020年7月にバージョンアップし、従来と設定方法が変わりました!) - Qiita