20200731のRailsに関する記事は20件です。

ActiveHashについて

DB設計をしてる時に便利なgemを見つけたのでまとめてみたいと思います。

ActiveHashとは

都道府県一覧やカテゴリーなど「基本的に変更されないデータ」(区分情報等)があったとします。基本的に変更されないデータであるため、データベースに保存する必要性がありません。

ですがビューファイルなどに都道府県のオプションを書くと見るに耐えないコードになってしまいます。

そういう悩みを解決するのに便利なのがactive_hash。
ハッシュデータをモデルとして定義することで、ActiveRecordと同じように使えることができます。

それでは定義の仕方を見ていきましょう。

まずGemfileにactive_hashを記載して、bundle installします。

gem 'active_hash'

モデル作成

今回はArticleモデルとgenreモデルを作りたいと思います。

rails g model article
rails g model genre --skip-migration

genreモデルには情報をデータベースに持たせないのでマイグレーションファイルを作成する必要はありません。

ハッシュの作成

次にデータをハッシュの形で定義します。

app/models/genre.rb
class Genre < ActiveHash::Base
 self.data = [
   { id: 1, name: '--' },
   { id: 2, name: '経済' },
   { id: 3, name: '政治' },
   { id: 4, name: '地域' },
   { id: 5, name: '国際' },
   { id: 6, name: 'IT' },
   { id: 7, name: 'エンタメ' },
   { id: 8, name: 'スポーツ' },
   { id: 9, name: 'グルメ' },
   { id: 10, name: 'その他' }
 ]
end

ここで気をつけるのはActiveHash::BaseをGenreモデルに継承することで
ActiveRecordのメソッド(allなど)が使えるようになります。

使用するクラスにアソシエーションを定義する。

ActiveHash::Associations::ActiveRecordExtensionsモジュールをArticleクラスにextendする事で
このモジュールに定義してあるbelongs_to_active_hashメソッドをArticleのクラスメソッドとして
使用する事が出来ています。

app/models/article.rb
class Article < ApplicationRecord
  extend ActiveHash::Associations::ActiveRecordExtensions
  belongs_to_active_hash :genre

  end
end

コンソールで試してみましょう

irb(main):001:0> Article.create(genre_id:1)
=> #<Article id: nil, title: nil, genre_id: 1, created_at: nil, updated_at: nil>

無事Articleモデルでgenreモデルで定義したデータを取得することができました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsのリクエストがルーティングにアクセスするまでの流れについて

概要

Railsアプリケーションでのリクエストからルーティングにアクセスするまでをまとめてみました。

Railsのルーティングとは

役割

『クライアントからのリクエストを判断し、どのプログラムに処理を渡すのか』を決める役割を担っている部分になります。

処理に必要な情報

『リクエストURI』 と 『HTTPメソッド』 です。

つまり...

この2つの情報を持ったリクエストがクライアントから送信されていることになります。

今回のゴール

リクエストは

・ 『どうやってRailsアプリにアクセスしているのか?』
・ 『アクセス時にどのような情報を持っているのか?』

について、知ることです。

<1> どうやってRailsアプリにアクセスしているのか?

結論

クライアントはRailsアプリのあるWebサーバの所在を表す URI をもとにアクセスしている。

URIとは

一般的にURLという意味で使われている
例)http://sample.com/samples といったものです。

このURIを分解してすると、このように分けられる

  ・ http: => スキーム

  ・ sample.com => ホスト

  ・ samples => パス

スキームとは・・・

『どんな手段で通信するのか』を明示的に示している部分になります。
つまり今回の場合だと『 HTTP通信をしますよ 』ということを示しています。

HTTPとは・・・

Hyper Text Transfer Protocolの略で、
Webページを構成するHTMLなどのコンテンツの送受信に用いられる通信プロトコルです。

また、SSL/TLSプロトコル(暗号化と認証の機能がある)を用いて行われるHTTP通信を行うことをHTTPS通信(https:)と呼び、HTTP通信よりも安全です。

通信プロトコルとは・・・

『ネットワーク上でデータ通信するためのルールをまとめたもの』です。
よく例えで使われているのが、言語やマナーでした。
つまり、人間の世界でも「郷に入れば郷に従え」ということわざがあるように
コンピュータの世界にもデータをスムーズにやりとりするために細かい取り決めがあり
それを通信プロトコルと呼びます。

また、通信プロトコルはOSI参照モデルをもとに7つの階層に分けることによって
それぞれの階層で行われる通信機能を定義しています。

ちなみに、HTTPは第7階層のアプリケーション層に区分されています。

ホストとは・・・

『どこのサーバにアクセスするのか』を明示的に示している部分になります。
今回の場合だと、DNSというサービスを使って
sample.comのサーバにアクセスしますよ 』ということを示しています。

DNSとは・・・

Domain Name Systemの略で人間が覚えにくいIPアドレスにドメイン名を割り当て、覚えやすい情報にするシステムです。

例) 127.0.12.1(IPアドレス) <=> sample.com(ドメイン名)

パスとは・・・

『どのファイルをの情報が欲しいのか』を明示的に示している部分になります。
今回の場合だと『 samplesファイルを下さいね 』ということを示してします。

<1> まとめ

・WebブラウザがWebサーバと通信する時は、HTTP通信を使用する。
・クライアントはURIをもとにアクセスしている。
・URIは3つの部分に分けられる。(スキーム・ホスト・パス)

<2> アクセス時にどのような情報を持っているのか

結論

HTTPリクエストというクライアントからの情報です。

HTTPリクエストとは・・・

HTTP通信をする際にWebブラウザからWebサーバへ送信される『リクエストライン』『ヘッダー』『ボディ』の3つで構成されるリクエストになります。

HTTPリクエストのイメージ
  GET /samples HTTP/1.1   // リクエストライン
  Host: sample.com     /* ヘッダー
  Accept-Language: ja     */
                          // 空白行
  q=test&test=%E6%A4%9C   // ボディ

また、反対にWebサーバからWebブラウザ送信される通信を『HTTPレスポンス』といい
『HTTPリクエスト』と『HTTPレスポンス』をワンセットでやりとりすることで
Webアプリケーションが出来ています。

リクエストラインとは・・・

Webサーバ内のどのリソースにどんな操作をするかを表した部分で
『HTTPメソッド』『リクエストURI』『HTTPバージョン』の3つの要素を1行で表したものです。

 <HTTPメソッド>     <リクエストURI>     <HTTPバージョン>
     GET           /samples         HTTP/1.1
HTTPメソッドとは・・・

Webサーバに対して『やって欲しい操作』を明示的に示している部分になります。
種類は8つありますが、基本的にRailsで使われるのは4種類になります。

メソッド 操作の内容
GET 指定されたURIのリソースを取得する。少量のデータを送信する。
POST 大量のデータや機密性のあるデータを送信する。
PUT/PATCH 指定したURIにリソースを保存する。
DELETE 指定したURIのリソースを削除する。
リクエストURIとは・・・

リクエストの対象となるデータを指す情報のことです。

HTTPバージョンとは・・・

Webブラウザがサポートするバージョンのことです。
今回の例だとバージョンが『1.1』という意味になります。

ヘッダーとは・・・

リクエストの詳細情報を表した部分になります。
唯一の必須のヘッダーは『Host』で、ホスト名を指定して『どのWebサーバへのリクエストか』を明示的に示します。

また、ヘッダーの種類はたくさんあり、Webブラウザ側でサポートするデータのタイプ、データの圧縮方法、ブラウザの種類などの情報があります。

ヘッダーイメージ
  Host: sample.com   // リクエスト先のサーバ
  User-Agent:        // サポートするブラウザの種類
  Accept:            // ブラウザが受信可能なデータ形式
  Accept-Encoding:   // ブラウザのデータの圧縮方法
  Accept-Language:   // ブラウザが受信可能な言語
  ...

ボディとは

Webサーバにデータを送る時に使用する部分になります。
HTTPメソッドがPOSTとPUTの時に使用され、ヘッダーの下に空白行を一行入れ、その下に記述されます。
また、使用されない場合は省略されます。

<2> まとめ

・Webサーバーは、クライアントからの情報をHTTPリクエストで受信する。
・HTTPリクエストは3つの部分に分けられる。(リクエストライン・ヘッダー・ボディ)
・Railsのルーティングに必要な情報は、リクエストラインの中に入っている。

以上になります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsでkaminariを使う

プログラミングの勉強日記

2020年7月31日 Progate Lv.226

概要

 こちらの記事で扱ったgemのkaminariを説明する。kaminariは1つのページに掲載するコンテンツが増えたときに複数ページに分けて表示させるページネーションを簡単に実装することができる。

0731.png
 ページネーションを使うことで、情報量の多いサイトをまとめられるということだけではなく、表示される項目が一定数を超えると自動的に次のページへ内容を移行することもできるので便利である。

使い方

 kaminariを使うためにGemfileを編集する。

Gemfile
gem 'kaminari'

 Gemfileを編集したら、kaminariをインストールする。

ターミナル
bundle install

 これでkaminariで定義されたメソッドや機能を使うことができるようになる。

ページネーションの定義

 コントローラとビューでページネーションが表示されるようにする。

controllerファイル
def index
  @users = User.page(params[:page]).per(10)
end
ビューファイル
<%# ページネーションの表示 %>
<%= paginate @users %>

見た目の変更

 kaminariで表示されるページネーションはビューの中でCSSを適用させることはできない。なので、見た目を変えたい場合はapp/view/kaminariフォルダを作り、その中にページネーションの部分のHTMLが記述されているようにターミナルで以下のコマンドを実行する。

ターミナル
$ rails g kaminari:views default

Bootstapに合わせる

 bootstrap用にカスタマイズすることもできる。このためにはもう一度Gemfileを変更する。

Gemfile
gem 'kaminari-bootstrap', '~> 3.0.1'

 Gemfileを編集したら、インストールする。

ターミナル
bundle install

 そして、config/config/locales/kaminari_ja.ymlを追加して編集する。

config/locales/kaminari_ja.yml
ja:
  views:
    pagination:
      first: "&laquo; 最初"
      last: "最後 &raquo;"
      previous: "&lsaquo; 前"
      next: " &rsaquo;"
      truncate: "..."
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsアプリで日時の取得にはどのクラスを使うべきか(Time, DateTime, TimeWithZone)

日時の取得はどのクラスを使うべきか

  • Timeクラス
  • DateTimeクラス
  • TimeWithZoneクラス

Railsでは、特別な理由がない限り、
TimeクラスやDateTimeクラスより、TimeWithZoneクラスで統一した方が良い。

タイムゾーンについて

タイムゾーンを設定する場所は3箇所あります。

  • システム
  • 環境変数 ENV['TZ']
  • application.rb

application.rbにどのタイムゾーンが設定してあるか確認するには、Time.zone.nameを使います。

# application.rb
config.time_zone = 'Tokyo'

Time.zone.name
=> "Tokyo"

どのタイムゾーンが使われるのか

ややこしいことに、クラスやそのクラスに関連するメソッドによってどのタイムゾーンが使われるか変わります。
例えば、
Timeクラスに関連するメソッドnowを使ったTime.nowは環境変数のタイムゾーンを使い、
Timeクラスに関連するメソッドcurrentを使ったTime.currentはapplication.rbのタイムゾーンを使います。

#環境変数が使われ、classはTimeになる
[1] pry(main)> Time.now
=> 2020-07-31 19:39:18 +0900
[2] pry(main)> Time.now.class
=> Time

#application.rbが使われ、classはTimeWithZoneになる
[3] pry(main)> Time.current
=> Fri, 31 Jul 2020 19:39:35 JST +09:00
[4] pry(main)> Time.current.class
=> ActiveSupport::TimeWithZone

現在の日時の取得にはTime.currentかTime.zone.nowを使おう

現在の日時を取得する場合、以下の方法があります。

  1. Time.now (環境変数のタイムゾーンを使う)
  2. DateTime.now (環境変数のタイムゾーンを使う)
  3. Time.current (application.rbのタイムゾーンを使う)
  4. Time.zone.now (application.rbのタイムゾーンを使う)

結論としては、TimeWithZoneクラスである3または4を使っておいた方が良いです(もし国際的なアプリケーションにしようと思ったら色々と器用に扱える為)。

# 環境変数のタイムゾーンが使われる
[1] pry(main)> Time.now
=> 2020-07-31 19:43:14 +0900 #Timeクラス
[2] pry(main)> DateTime.now
=> Fri, 31 Jul 2020 19:43:21 +0900 #DateTimeクラス

# application.rbのタイムゾーンが使われる
[3] pry(main)> Time.current
=> Fri, 31 Jul 2020 19:43:26 JST +09:00 #ActiveSupport::TimeWithZoneクラス
[4] pry(main)> Time.zone.now
=> Fri, 31 Jul 2020 19:43:32 JST +09:00 #ActiveSupport::TimeWithZoneクラス

参考記事

以下の記事を参考にしました。記事ではとても詳しく説明してあります。
https://qiita.com/jnchito/items/cae89ee43c30f5d6fa2c

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

木構造のacts_as_treeで、N+1を消し去る

木構造

古いコードを見ると、アンチパターンが多いですね。今回も同様にアンチパターンの一つである隣接リストが使われていました。さらに、N+1問題が放置され、パフォーマンス的に悪いということで対応することになりました。
今回は、たった2階層だったので、隣接リストでもあまり問題がなさそうです。

GitHub

https://github.com/amerine/acts_as_tree

使い方

https://github.com/amerine/acts_as_tree#example 引用

class Category < ActiveRecord::Base
  acts_as_tree order: "name"
end

root      = Category.create("name" => "root")
child1    = root.children.create("name" => "child1")
subchild1 = child1.children.create("name" => "subchild1")

root.parent   # => nil
child1.parent # => root
root.children # => [child1]
root.children.first.children.first # => subchild1

前提

parent_id (あるいは、任意のキー) を木構造の親キーとします。

schema例

  create_table "technologies", force: :cascade do |t|
    t.string "name"
    t.bigint "parent_id"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["parent_id"], name: "index_technologies_on_parent_id"
  end

N+1の消し去り方

modelに追加した acts_at_tree によって、 children メソッドが生えますので、そちらをincludesして関連付け、事前に最小限のクエリ回数にて読み込まれるようにします。

解消コード

# model
class Technology < ApplicationRecord
  acts_as_tree

  def dig
    if children.empty?
      self
    else
      children.map(&:dig)
    end
  end
end

includesを付与してあげます。

# console
Technology
  .where(parent: nil)
  .includes(children: { children: [:children] }))
  .map(&:dig)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby on Railsの木構造Gemのacts_as_treeで、N+1を消し去る

手元動作環境

当然、サービスインしているコードを乗せるわけにはいかないので、手元環境で似たようなコードを書いて動作検証しています。

  • ruby 2.7.1p83
  • Rails 6.0.3.2
  • acts_as_tree (2.9.1)

木構造

古いコードを見ると、アンチパターンが多いですね。今回も同様にアンチパターンの一つである隣接リストが使われていました。さらに、N+1問題が放置され、パフォーマンス的に悪いということで対応することになりました。
今回は、たった2階層だったので、隣接リストでもあまり問題がなさそうです。

GitHub

https://github.com/amerine/acts_as_tree

使い方

https://github.com/amerine/acts_as_tree#example 引用

class Category < ActiveRecord::Base
  acts_as_tree order: "name"
end

root      = Category.create("name" => "root")
child1    = root.children.create("name" => "child1")
subchild1 = child1.children.create("name" => "subchild1")

root.parent   # => nil
child1.parent # => root
root.children # => [child1]
root.children.first.children.first # => subchild1

前提

parent_id (あるいは、任意のキー) を木構造の親キーとします。

schema例

  create_table "technologies", force: :cascade do |t|
    t.string "name"
    t.bigint "parent_id"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["parent_id"], name: "index_technologies_on_parent_id"
  end

N+1の消し去り方

modelに追加した acts_at_tree によって、 children メソッドが生えますので、そちらをincludesして関連付け、事前に最小限のクエリ回数にて読み込まれるようにします。

解消コード

# model
class Technology < ApplicationRecord
  acts_as_tree

  def dig
    if children.empty?
      self
    else
      children.map(&:dig)
    end
  end
end

includesを付与してあげます。

# console
Technology
  .where(parent: nil)
  .includes(children: { children: [:children] }))
  .map(&:dig)

response

2階層のコードを抜き出して見ると、このような形になります。

{
  "id":6,
  "name":"AWS",
  "parent_id":null,
  "children":[
    {
      "id":7,
      "name":"コンピューティング",
      "parent_id":6,
      "children":[
        {
          "id":8,
          "name":"EC2",
          "parent_id":7
        },
        {
          "id":9,
          "name":"Elastic Beanstalk",
          "parent_id":7
        },
        {
          "id":10,
          "name":"Lambda",
          "parent_id":7
        }
      ]
    }
  ]
}

発行SQLの比較

includesがない場合の例

60件ぐらいデータを入れてみた
データに応じて、SQLの数が増加していく

SELECT "technologies".* FROM "technologies" WHERE "technologies"."parent_id" IS NULL
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 1], ["LIMIT", 1]]
SELECT "technologies".* FROM "technologies" WHERE "technologies"."parent_id" = $1  [["parent_id", 1]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 2], ["LIMIT", 1]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 3], ["LIMIT", 1]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 4], ["LIMIT", 1]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 5], ["LIMIT", 1]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 6], ["LIMIT", 1]]
SELECT "technologies".* FROM "technologies" WHERE "technologies"."parent_id" = $1  [["parent_id", 6]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 7], ["LIMIT", 1]]
SELECT "technologies".* FROM "technologies" WHERE "technologies"."parent_id" = $1  [["parent_id", 7]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 8], ["LIMIT", 1]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 9], ["LIMIT", 1]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 10], ["LIMIT", 1]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 11], ["LIMIT", 1]]
SELECT "technologies".* FROM "technologies" WHERE "technologies"."parent_id" = $1  [["parent_id", 11]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 12], ["LIMIT", 1]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id  SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id  SELECT 1 AS one F_i  SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id  SE= $1  [["parent_id", 14]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 15], ["LIMIT", 1]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 16], ["LIMIT", 1]]
SELECT "technologies".* FROM "technologies" WHERE "technologies"."parent_id" = $1  [["parent_id", 16]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 17], ["LIMIT", 1]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["paren  SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["paren  SELECT 1 AS one FROM "ent_id", 19], ["LIMIT", 1]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 20], ["LIMIT", 1]]
SELECT "technologies".* FROM "technologies" WHERE "technologies"."parent_id" = $1  [["parent_id", 20]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 21], ["LIMIT", 1]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 22], ["LIMIT", 1]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 23], ["LIMIT", 1]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIM  SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIM  SELECT 1 AS one FROM "technologies" W" = $1  [["parent_id", 24]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 25], ["LIMIT", 1]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 26], ["LIMIT", 1]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 27], ["LIMIT", 1]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 28], ["LIMIT", 1]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 29], ["LIMIT", 1]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIM  SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIM  SELECT 1 AS one FROM "technologies" WIMIT $2  [["parent_id", 31], ["LIMIT", 1]]
SELECT "technologies".* FROM "technologies" WHERE "technologies"."parent_id" = $1  [["parent_id", 31]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 32], ["LIMIT", 1]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 33], ["LIMIT", 1]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 34], ["LIMIT", 1]]
SELECT "technologies".* FROM "technologies" WHERE "technologies"."parent_id" = $1  [["parent_id", 34]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 35], ["LIMIT", 1]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 36], ["LIMIT", 1]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 37], ["LIMIT", 1]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 38], ["LIMIT", 1]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 39], ["LIMIT", 1]]
SELECT "technologies".* FROM "technologies" WHERE "technologies"."parent_id" = $1  [["parent_id", 39]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 40], ["LIMIT", 1]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 41], ["LIMIT", 1]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 42], ["LIMIT", 1]]
SELECT "technologies".* FROM "technologies" WHERE "technologies"."parent_id" = $1  [["parent_id", 42]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 43], ["LIMIT", 1]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 44], ["LIMIT", 1]]
SELECT "technologies".* FROM "technologies" WHERE "technologies"."parent_id" = $1  [["parent_id", 44]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 45], ["LIMIT", 1]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 46], ["LIMIT", 1]]
SELECT "technologies".* FROM "technologies" WHERE "technologies"."parent_id" = $1  [["parent_id", 46]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 47], ["LIMIT", 1]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 48], ["LIMIT", 1]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 49], ["LIMIT", 1]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["paren  SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["paren  SELECT 1 AS one FROM "ent_id", 51], ["LIMIT", 1]]
SELECT "technologies".* FROM "technologies" WHERE "technologies"."parent_id" = $1  [["parent_id", 51]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 52], ["LIMIT", 1]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 53], ["LIMIT", 1]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 54], ["LIMIT", 1]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 55], ["LIMIT", 1]]
SELECT "technologies".* FROM "technologies" WHERE "technologies"."parent_id"   SELECT "technologies".* FROM "technologies" WHERE "technologies"."parent_id"   SELECT "technologies".* FROM "technolent_id", 56], ["LIMIT", 1]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 57], ["LIMIT", 1]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 58], ["LIMIT", 1]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 59], ["LIMIT", 1]]
SELECT "technologies".* FROM "technologies" WHERE "technologies"."parent_id" = $1  [["parent_id", 59]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["paren  SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["paren  SELECT 1 AS one FROM "ent_id", 61], ["LIMIT", 1]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 62], ["LIMIT", 1]]
SELECT 1 AS one FROM "technologies" WHERE "technologies"."parent_id" = $1 LIMIT $2  [["parent_id", 63], ["LIMIT", 1]]

N+1問題解消時の例

SELECT "technologies".* FROM "technologies" WHERE "technologies"."parent_id" IS NULL
SELECT "technologies".* FROM "technologies" WHERE "technologies"."parent_id" IN ($1, $2, $3, $4, $5, $6)  [["parent_id", 1], ["parent_id", 6], ["parent_id", 46], ["parent_id", 51], ["parent_id", 55], ["parent_id", 59]]
SELECT "technologies".* FROM "technologies" WHERE "technologies"."parent_id" IN ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29)  [["parent_id", 2], ["parent_id", 3], ["parent_id", 4], ["parent_id", 5], ["parent_id", 7], ["parent_id", 11], ["parent_id", 14], ["parent_id", 16], ["parent_id", 20], ["parent_id", 24], ["parent_id", 31], ["parent_id", 34], ["parent_id", 39], ["parent_id", 42], ["parent_id", 44], ["parent_id", 47], ["parent_id", 48], ["parent_id", 49], ["parent_id", 50], ["parent_id", 52], ["parent_id", 53], ["parent_id", 54], ["parent_id", 56], ["parent_id", 57], ["parent_id", 58], ["parent_id", 60], ["parent_id", 61], ["parent_id", 62], ["parent_id", 63]]
SELECT "technologies".* FROM "technologies" WHERE "technologies"."parent_id" IN ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28)  [["parent_id", 8], ["parent_id", 9], ["parent_id", 10], ["parent_id", 12], ["parent_id", 13], ["parent_id", 15], ["parent_id", 17], ["parent_id", 18], ["parent_id", 19], ["parent_id", 21], ["parent_id", 22], ["parent_id", 23], ["parent_id", 25], ["parent_id", 26], ["parent_id", 27], ["parent_id", 28], ["parent_id", 29], ["parent_id", 30], ["parent_id", 32], ["parent_id", 33], ["parent_id", 35], ["parent_id", 36], ["parent_id", 37], ["parent_id", 38], ["parent_id", 40], ["parent_id", 41], ["parent_id", 43], ["parent_id", 45]]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Controllerに定義するアクション一覧

Controllerに定義するアクション一覧

RailsではWebアプリケーションの基本となる以下の7つの役割を持つアクションを用います。

アクション名 役割
new データを新規作成する
create データを追加(保存)する
index データの一覧を表示する
show データの内容(詳細)を表示する
edit データを更新するためのフォームを作成する
update データを更新する
destroy データを削除する
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

hamlのヘルパーメソッドの使用例

link_toの使い方

link_toを使用する際、

link_to "テキスト","リンク先のパス"

などで使われることが多いが、テキスト等であればそこに表示したいテキストを記入すればよいが、ロゴを指定しないととなると少し書き方が変わってくる。

例えば
VScodeのimageの中に"logo.png"というロゴを入れてそれを指定する場合は

= link_to root_path do
  = image_tag src ="logo.png",size"140x40"

と記入してあげるとlogoを表示することができる。

submitでのロゴの使い方

form_forなどでlogoボタンを使用する際、f.submitだとlogoが表示できず
例えばbutton_tagを利用すると

= button_tag type:'submit', class: 'submit_btn' do
  = icon('fas','search')

と使用することで、ロゴのボタンを使用することができる。
またこちらのロゴを使用する際はFontAwesomeをgemファイルからインストールする

gem 'font-awesome-sass'

こちらを入力してbundle install

またapplication.scssに

@import "font-awesome-sprockets";
@import "font-awesome";

を入力するのを忘れずに。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

NoMethodError: undefined method `paginate' for を解決する (Railsチュートリアル 10章)

発生しているエラー

Railsチュートリアル10章 10.3.3 ページネーション

Gemfile
gem 'will_paginate',           '3.1.6'
gem 'bootstrap-will_paginate', '1.0.0'
$ bundle install

そしてチュートリアルの指示通りにコードを追記し、

$ rails test

を実行すると、

Error:
UsersIndexTest#test_index_including_pagination:
NoMethodError: undefined method `paginate' for #<Class:0x00007fb0253e5c60>
    app/controllers/users_controller.rb:6:in `index'
    test/integration/users_index_test.rb:14:in `block in <class:UsersIndexTest>'

というエラーが発生する。

対処法

$ spring stop

これを実行すると解決する。

参考:RAILS_ENV=test時のみ、paginateメソッドがundefinedになる

springとは

Spring は Rails アプリケーションの preloader(プリローダー)の gem です。(pre + load = 前もってロードしておく)

参考:RailsでSpringを導入して開発効率を上げる

今回はロードを勝手にしてくれるspringによる影響でエラーが発生していたようですね。
詳しく説明できる方は、コメントで補足していただけると幸いです。


役に立ったら是非LGTMボタンをポチッと押していただけると嬉しいです。
一緒に Rails学習 頑張りましょう!:raised_hand_tone1:

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初学者向け】Route53でドメイン取得してポートフォリオに紐つける手順

前提条件

EC2でアプリをデプロイしている事
→もしまだデプロイ出来ていないのならこちらの記事を参考にしてください。私も同じ様にやってきました。

手順

他にも沢山の参考記事がある為そちらを参考にしましたが、ドメイン取得に関してはこちらの記事で問題なく出来ます。
ただしこの記事にもある様にnginxで確実にエラーが出ます。
nginx.png

なので、デプロイの時と同様に

[name(自分の名前)|~]$cd /etc/nginx/conf.d/

[name(自分の名前)|conf.d]$ sudo vi ◯◯.conf #自分のアプリ名

# log directory
error_log  /var/www/rails/〇〇/log/nginx.error.log; #自分のアプリケーション名に変更
access_log /var/www/rails/〇〇/log/nginx.access.log; #自分のアプリケーション名に変更
# max body size
client_max_body_size 2G;
upstream app_server {
  # for UNIX domain socket setups
  server unix:/var/www/rails/mumu/tmp/sockets/.unicorn.sock fail_timeout=0; #自分のアプリケーション名に変更
}
server {
  listen 80;
  server_name ~~~.~~~.~~~.~~~;(#アプリのElastic IPに変更してください)←ここを取得したドメインに書き換えてください
  # nginx so increasing this is generally safe...
  keepalive_timeout 5;
  # path for static files
  root /var/www/rails/〇〇/public; #自分のアプリケーション名に変更
  # page cache loading
  try_files $uri/index.html $uri.html $uri @app;
  location @app {
    # HTTP headers
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_pass http://app_server;
  }
  # Rails error pages
  error_page 500 502 503 504 /500.html;
  location = /500.html {
    root /var/www/rails/〇〇/public; #自分のアプリケーション名に変更
  }
}

保存出来たらnginxを再起動します。

[name(自分の名前)|conf.d]$ cd /var/www/rails/〇〇 #自分のアプリ名
[name(自分の名前)|〇〇]$ sudo service nginx restart

Stopping nginx:                                            [  OK  ]
Starting nginx:                                            [  OK  ]

こうなっていれば大丈夫です!
後は取得したドメインを検索し問題なくサイトを開けるか確認してください!

SSL化について

【初学者向け】AWSでSSL化する方法に記載しています。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

resourcesとresourceを組み合わせて使う時の注意点についてまとめてみた

先日、resourcesとresourceの違いについてまとめてみましたが、組み合わせて使う際の注意点もあったのでまとめてみました。
違いについてはこちら → resourcesとresourceの違い - Qiita

resourcesとresourceを組み合わせて使う場合

resourcesとresourceを組み合わせて使うと、下記のようなルーティングが簡単に生成できます。

  • 全ユーザーの一覧表示
  • 全ユーザーの詳細表示
  • 自ユーザーの詳細表示
  • 自ユーザー情報の編集

全ユーザー一覧、各ユーザーの詳細については、誰でも表示できるが、ユーザー情報の編集は自分のものしかできないという状態。

config/routes.rb
  resources :users, only: [:index, :show]
  resource :user, only: [:show, :edit, :update]

rails routesで生成されたルートを確認すると、下記のような感じ。

ターミナル
   Prefix Verb  URI Pattern          Controller#Action
    users GET   /users(.:format)     users#index
     user GET   /users/:id(.:format) users#show
edit_user GET   /user/edit(.:format) users#edit
          GET   /user(.:format)      users#show
          PATCH /user(.:format)      users#update
          PUT   /user(.:format)      users#update

user GET /users/:id(.:format) users#showの部分、user_pathが/users/:idに割り当てられてしまうことで、/userを返すべきヘルパーメソッドが生成されない。
ということで、順番を逆にして記載してみた。

config/routes.rb
  resource :user, only: [:show, :edit, :update]
  resources :users, only: [:index, :show]

rails routesで生成されたルートを確認すると、下記のような感じ。

ターミナル
   Prefix Verb  URI Pattern          Controller#Action
edit_user GET   /user/edit(.:format) users#edit
     user GET   /user(.:format)      users#show
          PATCH /user(.:format)      users#update
          PUT   /user(.:format)      users#update
    users GET   /users(.:format)     users#index
          GET   /users/:id(.:format) users#show

自ユーザーは、ログイン機能(session管理)で特定できれば、リクエストに:idを含む必要はないので、自ユーザーに関するルーティングはresourceを使用しています。

自分以外のユーザーの詳細画面を見るには、対象となるユーザーのIDを指定する必要があるので、resourcesでリクエストに:idを含むルートを生成しています。

結論

単数形で使用するresourceと、複数形で使用するresourcesを組み合わせてルーティングを作る際は、単数形のresourceを上に書くようにする。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails 本番環境で画像が読み込まれないエラー

この記事について

本番環境でbackground-imageの画像を読み込む時にはエラーが出ないのに、image_tagのところだけでエラーが出てしまっていたので、そのエラー解決方法について

環境

・Ruby 2.6.6, Rails 6.0.3.2
・Docker,Docker-compose(開発環境)
・AWS/本番環境(EC2, RDS, VPC, EIP, Route53, IAM, S3)

参考URL

https://stackoverflow.com/questions/49440304/rails-asset-is-not-present-in-asset-pipeline-when-using-image-tag
この人と全く同じ現象になってました。

出ていたエラー

qiita.rb
ActionView::Template::Error (The asset "chat_example.jpeg" is not present in the asset pipeline.):

asset へのパイプラインがうまくいっていない様子

解決方法

config/environments/production.rbにある、config.assets.compile = falseをtrueに変えてあげる。

config/environments/production.rb
- config.assets.compile = false
+ config.assets.compile = true
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Controller作成と同時にアクションとviewファイルを作成(rails)

Controller作成と同時にアクションとviewファイルを作成

railsではcontrollerを作成する際、同時にアクションとviewファイルを作成することができます。
実際に手動でアクションやviewファイルを作成する手間を省くことができます。
以下がコマンドの型です。

$ rails g controller コントローラ名 アクション名

例えば、book controllerを作成し、newアクションを追加したい場合、、、

$ rails g controller book new

こんな感じになれば、成功。

[vagrant@localhost sample_app]$ rails g controller books new
Running via Spring preloader in process 25819
      create  app/controllers/books_controller.rb
       route  get 'books/new'
      invoke  erb
      create    app/views/books
      create    app/views/books/new.html.erb
      invoke  test_unit
      create    test/controllers/books_controller_test.rb
      invoke  helper
      create    app/helpers/books_helper.rb
      invoke    test_unit
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/books.coffee
      invoke    scss
      create      app/assets/stylesheets/books.scss
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

カラムの追加と削除

カラムとは?

カラムとはデータベースの中のテーブルが持つ要素です。
例えば、ユーザーというテーブルの中の「名前、生年月日、住所」などの項目をカラムと言います。

カラムの追加

新たに別のカラムを追加したいということはよくあると思います。
そんなときは以下のコマンドでカラムを追加です。

$ rails g migration Addカラム名Toテーブル名 カラム名:型名

例えば、「User」というテーブルに「name」というカラムを追加したい場合、

$ rails g migration AddNameToUsers name:string
$ rails db:migrate

ここで「rails db:migrate」を忘れてしまうと、テーブルへ反映されませんので、
気をつけてください。

カラムの削除

カラム名を間違えて登録した時など、カラムを削除したいこともあると思います。
カラムを追加する時とあまり変わりません。

$ rails g migration Removeカラム名Fromテーブル名 カラム名:型名

「User」というテーブルの「name」というカラムを削除したい場合、

$ rails g migration RemoveNameFromUsers name:string
$ rails db:migrate

ここでも「rails db:migrate」は忘れずに。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails] ActiveModel::Attributesの使い方(配列化やネストしたhashの取り扱いなども)

ActiveModel::Attributesの使い方について使い方をまとめてみたいと思います。他の方の記事も少しだけありましたが、配列の作り方とか、ネストしているhashをオブジェクト化するやり方があまりまとまってなかったので初投稿してみることにしました。

サマリ

記事の下まで読むと、ネストしているhashをActiveModel::Attributesを使ってきれいにオブジェクト化することができます。こんな感じです。

hash = {name: "徒然草", price: 1000, authors: [{name: "兼好法師", age: 35}, {name: "健康男児", age: 20}]}
book = Book.new(hash)
book.name
# => "徒然草"
book.authors.class
# => Array
book.authors.first.class
# => Author
book.authors.first.name
# => "兼好法師"
book.authors.first.age
# => 35

なお、ActiveModel::Attributesとはなにかについては触れません。あとバリデーションについても触れてません。

この記事で書くこと

  • 基本的な書き方
  • hashを渡してインスタンス生成
  • カスタムタイプを使う
  • array型を作る
  • ネストしたインスタンスを生成する

基本的な書き方

ActiveModel::Attributesを使うためには自分で作ったclassにActiveModel::Modelと一緒にincludeして使います。

class Book
  include ActiveModel::Model
  include ActiveModel::Attributes
end

属性(attribute)を定義する

クラスに持たせる属性を定義します。

class Book
  include ActiveModel::Model
  include ActiveModel::Attributes

  attribute :name, :string
  attribute :price, :integer, default: 100
  attribute :createdate, :datetime
end

defaultを設定すると、インスタンス生成した際のデフォルト値を設定できます。

標準で使用できる型

:stringなどの型っぽいのがシンボルで渡されているけど、これはなに?」と思うと思います。これはActiveModel::Typeのなかで事前に定義されている型です。ActiveModel::Attributesでは、ActiveModel::Typeの型を使う必要があります。基本的な型はそろっていますが、これ以外の型を使いたい場合はカスタムタイプを自分で定義する必要があります。(後述)
デフォルトで使える方は以下の通り。

:big_integer -> BigInteger型
:binary -> Binary型
:boolean -> Boolean型
:date -> Date型
:datetime -> DateTime型
:decimal -> Decimal型
:float -> Float型
:immutable_string -> ImmutableString型
:integer -> Integer型
:string -> String型
:time -> Time型

Railsのコードのこちらにregisterメソッドで定義しています。

hashを渡してインスタンス生成

基本的な使い方はこのclassにhashを渡してあげてインスタンスを生成する形です。JSONデータをパースしてそのまま放り込むとオブジェクトが生成できるので便利です。

class Book
  include ActiveModel::Model
  include ActiveModel::Attributes

  attribute :name, :string
  attribute :price, :integer, default: 100
  attribute :createdate, :datetime
end

book = Book.new({name: "徒然草", price: 200, createdate: '1993-02-24T12:30:45'})
book.class # => Book

ちゃんとBookクラスのインスタンスが生成できています。attributeは.をつけて呼び出せます。

book.name # => "徒然草"
book.price # => 200
book.createdate # => 1993-02-24 12:30:45 UTC
book.createdate.class # => Time

ここでdefaultも試してみます。priceをhashに含めないでインスタンスを生成すると

book = Book.new({name: "徒然草", createdate: '1993-02-24T12:30:45'})
book.price # => 100

ちゃんとデフォルト値が入ります。逆にattributeにないものを入れようとすると、

book = Book.new({name: "徒然草", createdate: '1993-02-24T12:30:45'}, author: "aa")
# ArgumentError (wrong number of arguments (given 2, expected 0..1))

エラーになります。

attributesメソッド、attribute_namesメソッド

attributesメソッドで定義されている属性全てがhashで取得できます。

book.attributes
# => {"name"=>"徒然草", "price"=>100, "createdate"=>1993-02-24 12:30:45 UTC}

また、attribute_namesで属性の名前だけを配列で取得できます。(Rails 6.0.0以降の場合のみ)

book.attribute_names
# => ["name", "price", "createdate"]

カスタムタイプを使う

ここから応用編です。デフォルトで用意されている型ではないものを使いたい時はカスタムタイプを定義します。例えば以下のようなauthor属性を追加してみたいとします。

class Book
  include ActiveModel::Model
  include ActiveModel::Attributes

  attribute :name, :string
  attribute :price, :integer, default: 100
  attribute :createdate, :datetime
  attribute :author_name, :author
end

:authorは元々定義されているものではないので自分で型を作ります。

models/type_author.rb
# models/type_author.rbを新規作成
class TypeAuthor < ActiveModel::Type::String
  def cast_value(value)
    "吾輩は" + value + "である"
  end
end
config/initializers/types.rb
# config/initializers/types.rbを新規作成
ActiveModel::Type.register(:author, TypeAuthor)

これで定義完了。実際に使ってみます。

book = Book.new({name: "徒然草", price: 200, createdate: '1993-02-24T12:30:45', author_name: "兼好法師"})
book.author_name # => "吾輩は兼好法師である"

何をしたかと言うと、TypeAuthorクラスはActiveModel::Type::Stringクラスを継承していて、ActiveModel::Type::Stringクラスにあるcast_valueメソッドをオーバーライドしています。
インスタンス生成する際にsetterがこのメソッドを呼び出して、cast_valueメソッドの戻り値が属性にセットされる形になります。

Array型を作る

属性に配列を持たすこともできます。先ほどのTypeBookクラスを改良してみます。ActiveModel::Type::Valueクラスは先程使ったActiveModel::Type::Stringクラスの親クラスで、受け取った値をそのままsetする型です。

models/type_author.rb
# models/type_author.rb
class TypeAuthor < ActiveModel::Type::Value
  def cast_value(value)
    value
  end
end
config/initializers/types.rb
ActiveModel::Type.register(:author, TypeAuthor)

単純に受け取った値をそのまま返すだけにして、author_nameauthor_namesにして配列を受け取ります。この状態でauthor_namesに配列を渡してみるとどうなるでしょうか

class Book
  include ActiveModel::Model
  include ActiveModel::Attributes

  attribute :name, :string
  attribute :price, :integer, default: 100
  attribute :createdate, :datetime
  attribute :author_names, :author
end

book = Book.new({name: "徒然草", price: 200, createdate: '1993-02-24T12:30:45', author_names: ["兼好法師", "健康男児"]})
book.author_names
# => ["兼好法師", "健康男児"]

ちゃんと配列が格納されています。

ネストしたインスタンスを生成する

最後に属性に別のインスタンスの配列を持たせるような形にできないでしょうか。例えば、Bookクラスの属性としてAuthorクラスの配列が含まれているもの。hashにするとこんな元データです。APIで受け取ったJSONデータとかよくこのような形になっていますよね。

{
  name: "徒然草",
  price: 1000,
  authors: [
    {name: "兼好法師", age: 35},
    {name: "健康男児", age: 20}
  ]
}

兼好法師と健康男児はAuthorクラスのオブジェクトとして生成し、authorsの属性として配列で持つというケースです。これはどうやってやるかというと、今までの説明の全てを駆使してやります。

models/book.rb
class Book
  include ActiveModel::Model
  include ActiveModel::Attributes

  attribute :name, :string
  attribute :price, :integer, default: 100
  attribute :authors, :author_array, default: []
end
models/author.rb
class Author
  include ActiveModel::Model
  include ActiveModel::Attributes

  attribute :name, :string
  attribute :age, :integer
end
models/type_author_array.rb
class TypeAuthorArray < ActiveModel::Type::Value
  def cast_value(value)
    arr = []
    value.each do |v|
      arr.push Author.new(v)
    end
    arr
  end
end

# config/initializers/types.rb
ActiveModel::Type.register(:author_array, TypeAuthorArray)

ポイントとしては型のキャストをするTypeAuthorArrayクラスでAuthorクラスのインスタンスを生成して、配列に格納してBookクラスのインスタンスにセットしてあげる部分です。
早速使ってみましょう。

hash = {name: "徒然草", price: 1000, authors: [{name: "兼好法師", age: 35}, {name: "健康男児", age: 20}]}
book = Book.new(hash)
book.authors.class
# => Array
book.authors.first.class
# => Author
book.authors.first.name
# => "兼好法師"

素晴らしい。ちゃんとauthors属性にAuthorクラスのインスタンスの配列が格納されていて、Authorクラスの属性を呼んであげると、値が取得できました。

最後に

APIでJSONファイルを受け取った後、Modelのオブジェクトと同じように扱いたい場合はActiveModel::Attributeでインスタンスを作ってあげればいろんなことがよしなにできるようになります。

誰かの一助となれば幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】エラー解決 Unsupported argument type: 1 (Integer)

はじめに

今回は
Unsupported argument type: 1 (Integer)
こんなこと言われた時の対処方を記述します
お決まりのタイプミスによって発見したエラー文です笑

解決策

intger型の1という引数は期待してません、という感じのエラー文です。

という事は違う物を期待してるところで整数の1を渡してしまっているという事です。

User.find_by(session[:user_id])

犯人はこれでした!

findと書くべきところを間違えてfind_byと書いてますね、なのでfind_byのカラム名を書くところにsession[:user_id]が入っているせいで、今回は[1]という整数が入ってるのでそのまんま[1]って名前のカラムを探しに行こうとしてしまった、そんな感じみたいですね。

↓こんな感じの事ですね!

User.find_by(1)

という事で

User.find(session[:user_id])

findに書き換えて解決!

学び

  • findfind_byと間違えると高確率でUnsupported argument type: ... (Integer)が出ると思うのでこんな感じのエラーを見かけたらfind周りチェックするのがいいかもですね!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

herokuのエラー

今まで出たherokuのエラー

その1

error: src refspec master does not match any

解決方法

pushするブランチを間違っていた。作業しているブランチがdevelopだったのでdevelopと書きその後に:masterとつけるのが決まりみたい。

$ git push heroku develop:master
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

railsでmodelを作成する方法

railsでmodelを作成する方法

modelはデータベースとのやりとりを行ってくれます。
modelを作成するコマンドはこんな感じです。

$ rails g model モデル名

モデル名の命名規則は、「User」のように、英数字の単数形で指定します。
また、先頭は必ず英大文字で記述します。

実際に、modelを作成するためのコマンドを打つとこんな感じです。

$ rails g model User

成功すると、、、

$ rails g model User
Running via Spring preloader in process 4271
      invoke  active_record
      create    db/migrate/2020072951856_create_users.rb
      create    app/models/user.rb
      invoke    test_unit
      create      test/models/user_test.rb
      create      test/fixtures/users.yml

作成されるファイルは以下の4つです。

db/migrate/(作成日時)create_lists.rb:「マイグレーションファイル」というデータベースの設計図になるファイルです。
app/models/list.rb:モデルクラスはデータベースに対応したRubyのクラスです。
test/models/list
test.rb:モデルクラスのテストコードのひな形です。
test/fixtures/lists.yml:テストデータ作成のためのひな形です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

railsでルーティングを設定する方法

railsでルーティングを設定するには

routing(ルーティング)では、ユーザが特定のURLにアクセスした時に、
どのコントローラのどのアクションに処理を振り分けるかを定義します。
ルーティングは、URLとアクションを結びつける役目をしています。
例えば、「あるURLにアクセスがあった時に、homeコントローラのtopアクションの処理を振り分ける」という定義を、ルーティングに記述します。

ルーティングの設定は、configフォルダ内のroutes.rbファイルに記述します。
routes.rbファイルは、Railsアプリを作成するときに自動作成されるファイルです。
こんな感じです。

HTTPメソッド 'URL' => 'コントローラ#アクション'

※HTTPメソッドについてはこちらの記事で説明しています。
https://qiita.com/yusuke1209kitamura/items/e29787bc2920a656d505

わかりやすく、実際に書いてみます。

Rails.application.routes.draw do
  get 'top' => 'homes#top'
end

この例の場合、URL「top」にアクセスすると、
homesコントローラのtopアクションが呼び出されるように設定しています。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Controllerにアクションを追加する方法(rails)

Controllerにアクションを追加するには

controllerを作成したあとは、アクションを記述する必要があります。
アクションとはコントローラに定義されている処理内容です。ユーザーが実際に呼び出して使います。

アクションはcontrollerファイルに対して、こんな感じで記述します。

topページを表示したい場合
def~の後は表示したいviewページの名前になります。

class HomesController < ApplicationController
  def top

  end
end

defはdefine(定義する)を意味します。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む