20191012のRailsに関する記事は30件です。

Rails、Slimでflashメッセージがうまくだせない

Slimで新規登録に成功したらフラッシュメッセージを表示してindexに戻りたいのに次のように書いたらエラーが出る。

classを書かずにやると素テキストで成功メッセージが出るのでコントローラー側は大丈夫なはず。

app/controllers/blogs_controller.rb
  def create
    @blog = Blog.create(blog_params)
    redirect_to blogs_path
    flash[:success] = 'add success'
  end
index.html.slim
= flash[:success], class: 'alert alert-primary'
h1 Blog articles

= link_to '新規登録', new_blog_path, class: 'btn btn-primary'

- @blogs.each do |blog|
  br
  = blog.id
  br
  = blog.title
  br
  = blog.article

error
syntax error, unexpected ':' ..._safe((flash[:success], class : 'alert alert-primary'))).to_... ... ^


いろいろぐぐり、試行錯誤した。
classの書き方が違っていた。
link_toのクラスの書き方が、「class: 'btn btn-primary'」だったから同じだと思っていたが違うみたい。

ネットでいろいろぐぐったけど、Slimでflashのclassの書き方の参考が見つけられなかった。
これでエラーが取れたけど、ずっとフラッシュの緑枠がある。

  .alert.alert-success
    = flash[:success]

h1 Blog articles

勘違いしていた。if文を入れないと常にフラッシュが出てしまう。
flashでるときだけ値をコントローラーから勝手に渡してくれるような気がしていた。
if文を書く。
これでOK。

- if flash[:success]
  .alert.alert-success
    = flash[:success]

h1 Blog articles

参考サイト
redirect_to使った時にBootstrap対応のフラッシュメッセージを表示させる - Qiita

railsでflashを使ってサクセス・エラーメッセージを表示する - Qiita

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

AWS EC2インスタンスの作成手順

はじめに

EC2インスタンスの作成手順をメモする

EC2インスタンスの作成

  • AWS マネジメントコンソールにログイン
  • 下記画像のサービス→EC2をクリック スクリーンショット 2019-10-12 23.05.11.png
  • インスタンスの作成をクリック スクリーンショット 2019-10-12 23.10.03.png
  • AMIを選択 スクリーンショット 2019-10-12 23.12.41.png Amazon Linux AMIを選択する
  • タイプの選択 スクリーンショット 2019-10-12 23.15.14.png 図のように上から2つ目の無料枠を選択しておく。その後画像右下の【確認と作成】をクリック
  • インスタンス作成の確認画面
    • ここでは右下にある【起動】をクリック。
  • キーペアをダウンロードする
    • modalが表示されるので、【新しいキーペアの作成】を選択し、【キーペア名】を任意で入力後、キーペアのダウンロードを行う。
    • 【インスタンスの作成】をクリック
  • インスタンス一覧からインスタンスを選択
    • インスタンスIDをメモ # Elastic IPと紐付ける - 下図の Elastic IPをクリック後、【新しいアドレスの割り当て】をクリック スクリーンショット 2019-10-12 23.28.27.png スクリーンショット 2019-10-12 23.29.21.png
    •  その後、割り当てをクリック
    •  その後、閉じるをクリック
  • 取得したElastic IPアドレスを紐づける スクリーンショット 2019-10-12 23.29.21.png 上図の【アクション】から【アドレスの関連付け】を選択
  • 【アドレスの関連付け】ページにあるインスタンスには先ほどメモしたIDを入力、【プライベートID】には入力しない、【関連付け】をクリック
  • インスタンス画面からElastic IPが紐づけられたことを確認する

ポートを開く

  • インスタンス画面
    • セキュリティグループのリンクをクリック
    • 「インバウンド」タブの中の「編集」をクリック
    • 開かれたモーダルで、ルールの追加」をクリック、タイプを「HTTP」、プロトコルを「TCP」、ポート範囲を「80」、送信元を「カスタム / 0.0.0.0/0, ::/0」に設定

EC2インスタンスへのログイン

$ cd ~

$ mv Downloads/鍵の名前.pem .ssh/

$ cd .ssh/

$ chmod 600 鍵の名前.pem

$ ssh -i 鍵の名前.pem ec2-user@作成したEC2インスタンスと紐付けたElastic IP
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】scaffoldを使用したアプリケーションの生成手順

先日、scaffoldを初めて使ってみたのですが、めちゃめちゃ便利だったので、使用手順を中心に記事を書かせていただきます。

おそらく「scaffoldってなんだ?」という方もいらっしゃると思うので、まずはscaffoldについて簡単に。

scaffoldとは?

Railsの開発に必要なルーティング、コントローラ、ビューの作成に加え、基本的なアクションの設定なども自動ですべてやってくれる便利ツールのこと

→もちろん、これを使って世の中に出すアプリケーションをつくるのは違う(というより無駄が多い)と思いますが、個人的には試しに機能を実装するための試作品をつくる上で、ベースとなるRailsのアプリケーションをなるべく工数かけずに作りたいと思っていたので、最近めちゃめちゃよく使っています。

ということで、以下scaffoldを使用したRailsアプリケーションの生成手順について記載します。
本当に簡単なので、もしよかったら実際に使ってみて下さい。

scaffoldを使用したRailsアプリケーションの生成手順

<想定読者>
Railsを使用したアプリケーション開発の初学者
または、これからRailsを使用したアプリケーション開発を行いたいと考えている方

<環境やバージョン>
・MacOS
・Rails 5.2.3

まずは、通常通りrails newでrailsアプリケーションを作成しましょう。
※例ではtodoリストのアプリケーションをつくる想定で「todo_list」というアプリケーション名にしています。

ターミナル
$ rails new todo_list

コマンドを使用して作成したアプリケーション(todo_lint)へ移動

ターミナル
$ cd todo_list

いよいよ本題のscaffoldを使用した各種ファイルの生成を行います。
scaffoldを使用する場合、ターミナルで以下のコマンドを入力します。
$ rails generate scaffold モデル名 カラム名1:データ型1 カラム名2:データ型 2 …
こうすることにより、ルーティング、コントローラ、ビューのファイルだけではなく、モデルやマイグレーションファイルの作成も自動で行なってくれます。
※例では、content、priority、limitの3つのカラムを作成するための設定を行なっています。

ターミナル
$ rails g scaffold todo content:text priority:string limit:date

これでscaffoldを使用したRailsアプリケーションの雛形は作成できました。
最後に、作成されたマイグレーションファイルを元に、データベースのテーブル作成を実施しましょう。

ターミナル
$ rake db:migrate

以上でscaffoldを使用したアプリケーションの生成手順は終了です。

ローカルのサーバにアクセスすると、以下のページが表示されるかと思います。

スクリーンショット 2019-10-10 16.26.06.png

「ん?たしかに必要なファイルは作成されたけど、ビューファイルに記載された内容がブラウザに表示されない、、」という方↓

そうなんです。各種アクションやビューに対応したルーティングの設定はされていますが、rootの設定はされないので、そこは個別設定しましょう。

config/routes.rb
root to: "todos#index"

すると、以下のようにビューがきちんと表示されたかと思います。
スクリーンショット 2019-10-10 16.37.26.png

どうでした?簡単でしたよね?

ちなみに、今回作成したtodoリストの場合のその他のビューはこんな感じになります。
<todoの新規作成ページ>
alt

<todo一覧ページ>
スクリーンショット 2019-10-10 16.51.56.png

それでは、最後になりましたが、今回使用したscaffoldで生成された(もしくは追加、変更された)ファイルを記載しますので、今後のアプリケーション開発の参考にしてみてください。

①コントローラ
・app/controllers/todos_controller.rb
※作成されたコントローラのファイルには、自動で「index」「show」「new」「edit」「create」「update」「destroy」の7つのアクションが定義されています。そのため、これらのアクションを呼び出してデータの参照、作成、変更、削除をすぐに行うことができます。

②ルーティング
・config/routes.rb
※ファイルの中には「resources :todos」という記述が追加されており、上記のアクションに対応したルーティングが自動で設定されます。

③ビュー
・app/views/todos/index.html.erb
・app/views/todos/edit.html.erb
・app/views/todos/show.html.erb
・app/views/todos/new.html.erb
・app/views/todos/_form.html.erb
※コントローラーで定義されたアクションに対応するビューファイルが全て作成されています。

④モデル、およびマイグレーションファイル
・db/migrate/2019xxxxxxx_create_todos.rb
・app/models/todo.rb
※scaffoldを使用した各種ファイルの作成時に指定した情報を含む、モデルおよびマイグレーションファイルが自動生成されています。

【参考記事】
覚えておくと超便利!Ruby on Railsのscaffoldの使い方【初心者向け】
https://techacademy.jp/magazine/7204

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

【Rails】scaffoldを使用したアプリケーションの作成手順

先日、scaffoldを初めて使ってみたのですが、めちゃめちゃ便利だったので、scaffoldを使用したRailsアプリケーションの作成手順を中心に記事を書かせていただきます。

おそらく「scaffoldってなんだ?」という方もいらっしゃると思うので、まずはscaffoldについて簡単に。

scaffoldとは?

Railsの開発に必要なルーティング、コントローラ、ビューの作成に加え、基本的なアクションの設定なども自動ですべてやってくれる便利ツールのこと

【個人的に感じたscaffoldの良さ】
①コントローラの作成だけでなく、7つの基本アクションも自動で設定してくれる
scaffoldで作成されたコントローラには、「index」「show」「new」「edit」「create」「update」「destroy」の7つのアクションが自動で定義されています。そのため、自分でアクションを設定しなくても、すぐにデータの参照や新規作成、変更、削除を行うことができます。

②作成されたコントローラに対応するルーティングも自動で設定してくれる
scaffoldコマンド後のroutes.rbには、「resources :コントローラ名」という記述が追加されており、新たに作成されたコントローラに対応するルーティングが自動で設定されています。

③コントローラで定義されたアクションに対応するビューも全て自動生成してくれる
scaffoldを使用した場合、コントローラで定義されたアクションに対応するビューファイルも全て自動で生成されます。

④必要なモデルおよびマイグレーションファイルも自動生成してくれる
scaffoldでは、コマンド入力時に「カラム名:データ型」を指定すると、指定された情報を含むモデルおよびマイグレーションファイルを自動生成してくれます。
そのため、あとは「$ rake db:migrate」コマンドを入力するだけで、データベースのテーブルも簡単に作成できます。(この辺りの詳細は後ほど)

→もちろん、scaffoldを使って世の中に出すアプリケーションをつくるのはちょっと違う(というより逆に無駄が多い)と思いますが、個人的には興味のある機能を試しに実装するための試作品をつくる上ではめちゃめちゃ便利だなと感じています。

ということで、以下scaffoldを使用したRailsアプリケーションの作成手順について記載します。
本当に簡単なので、もしよかったら実際に使ってみて下さい!

scaffoldを使用したRailsアプリケーションの生成手順

<想定読者>
Railsを使用したアプリケーション開発の初学者
または、これからRailsを使用したアプリケーション開発を行いたいと考えている方

<環境やバージョン>
・MacOS
・Rails 5.2.3

<作成手順>
まずは、通常通りrails newでrailsアプリケーションを作成しましょう。
※例ではtodoリストのアプリケーションをつくる想定で「todo_list」というアプリケーション名にしています。

ターミナル
$ rails new todo_list

コマンドを使用して作成したアプリケーション(todo_lint)へ移動

ターミナル
$ cd todo_list

いよいよ本題のscaffoldを使用した各種ファイルの生成を行います。
scaffoldを使用する場合、ターミナルで以下のコマンドを入力します。
$ rails generate scaffold モデル名 カラム名1:データ型1 カラム名2:データ型 2 …
こうすることにより、ルーティング、コントローラ、ビューのファイルだけではなく、モデルやマイグレーションファイルの作成も自動で行なってくれます。
※例では、content、priority、limitの3つのカラムを作成するための設定を行なっています。

ターミナル
$ rails g scaffold todo content:text priority:string limit:date

これでscaffoldを使用したRailsアプリケーションの雛形は作成できました。
最後に、作成されたマイグレーションファイルを元に、データベースのテーブル作成を実施しましょう。

ターミナル
$ rake db:migrate

以上でscaffoldを使用したアプリケーションの作成手順は終了です。
いかがでした?めちゃめちゃ簡単じゃないですか?

ちなみに、ここでローカルのサーバにアクセスしてみると、以下のページが表示されるかと思います。

スクリーンショット 2019-10-10 16.26.06.png

「ん?たしかに必要なファイルは作成されたけど、ビューファイルに記載された内容がブラウザに表示されない、、」という方↓

そうなんです。各種アクションやビューに対応したルーティングの設定はされていますが、rootの設定はされないので、そこは個別設定しましょう。

config/routes.rb
root to: "todos#index"

すると、以下のようにビューがきちんと表示されたかと思います。
これでもう完璧ですね!
スクリーンショット 2019-10-10 16.37.26.png

参考までにですが、今回作成したtodoリストの場合、その他のビューはこんな感じになります。
<todoの新規作成ページ>
alt

<todoリストの一覧ページ>
スクリーンショット 2019-10-10 16.51.56.png

それでは、最後になりましたが、今回使用したscaffoldで生成された(もしくは追加、変更された)ファイルを記載しますので、もし興味があれば今後のアプリケーション開発の参考にしてみてください。

①コントローラ
・app/controllers/todos_controller.rb

②ルーティング
・config/routes.rb
※ファイルの中には「resources :todos」という記述が自動追加

③ビュー
・app/views/todos/index.html.erb
・app/views/todos/edit.html.erb
・app/views/todos/show.html.erb
・app/views/todos/new.html.erb
・app/views/todos/_form.html.erb

④モデル、およびマイグレーションファイル
・db/migrate/2019xxxxxxx_create_todos.rb
・app/models/todo.rb

【参考記事】
覚えておくと超便利!Ruby on Railsのscaffoldの使い方【初心者向け】
https://techacademy.jp/magazine/7204

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

Active Recordの型変換はいつ実行されるのか?

上記の動作を知らずに実装していたらハマったのでメモします。
(ジャストなドキュメントを見つけられなかったためActive Recordのソースやその他記事等を確認しましたが、間違っている部分がありましたらご指摘ください)

前提

  • Rails 5.2.3

Task というクラスがあり、 tests という属性を持っているとします。
tests はRails上では ActiveSupport::TimeWithZone として扱われます。
アプリケーションのデフォルトのタイムゾーンは Tokyo です。

rails_console
[2] pry(main)> Task.columns_hash["tests"].type
=> :datetime
[5] pry(main)> Time.zone.name
=> "Tokyo"

疑問

以下のアクション( TasksController#index )が実行された場合に # ?? の部分はどのような値(タイムゾーン)となるか?

tasks_controller.rb
class TasksController < ApplicationController
  def index
    task = Task.new(tests: "2019/01/01")
    Time.use_zone("UTC"){ task.tests }
    puts task.tests # ??
  end
end

正解

正解は Tue, 01 Jan 2019 00:00:00 UTC +00:00
Tokyo のタイムゾーンになると思っていたのでハマった。

Active Recordの型変換

事前知識

Active Recordでは属性として入力された値を型変換することがあります。例えば属性にInteger型の Task.id があった場合で、idにInteger型以外の値が入力された場合でも、以下のように型変換して属性値として扱います。

rails_console
[1] pry(main)> task = Task.new
=> #<Task:0x00007f3a201cad70 id: nil, created_at: nil, updated_at: nil, tests: nil>
[2] pry(main)> task.id = "1"
=> "1"
[3] pry(main)> task.id
=> 1
[4] pry(main)> task.id = "2019/01/01"
=> "2019/01/01"
[5] pry(main)> task.id
=> 2019

型変換が実行されるタイミングは?

さて、上記の型変換が実行されるタイミングですが、これまで私は何となく、前述のケースでいえば以下(1)にようにモデルがインスタンス化されたときだと思っていました。しかし、今回調べたところ、正しくは、初めてその属性のアクセサメソッドが実行されたとき、のようです。
すなわち、以下(2)のタイミングのときにtask.tests は TimeWithZone 型に変換されるようです。

(1)    task = Task.new(tests: "2019/01/01")
(2)    Time.use_zone("UTC"){ task.tests }

では、 (1) ではどこまで処理が実行されるのかというと、アクセスメソッドを定義するまで、実行されるようです(他にも色々しているのでしょうが)。
以下、(1)(2)それそれが実行される流れを簡単にコードで追ってみようと思います。

ソースコードを読んでみる

Model.new すると起きること

ActiveRecord を継承したクラスが new される場合に実行されるコンストラクタは ActiveRecord::Core モジュール(includeされるクラスは ActiveRecord::Base クラス)のコンストラクタのようです。コードを抜粋して確認してみます。

core.rb
    # New objects can be instantiated as either empty (pass no construction parameter) or pre-set with
    # attributes but not yet saved (pass a hash with key names matching the associated table column names).
    # In both instances, valid attribute keys are determined by the column names of the associated table --
    # hence you can't have attributes that aren't part of the table columns.
    #
    # ==== Example:
    #   # Instantiates a single new object
    #   User.new(first_name: 'Jamie')
    def initialize(attributes = nil)
      self.class.define_attribute_methods
      @attributes = self.class._default_attributes.deep_dup

      init_internals
      initialize_internals_callback

      assign_attributes(attributes) if attributes

      yield self if block_given?
      _run_initialize_callbacks
    end

https://github.com/rails/rails/blob/5-2-stable/activerecord/lib/active_record/core.rb

上記の

self.class.define_attribute_methods

部分で属性のアクセサメソッドを定義していると想像できます。
さらにこの奥に進んでみると、ActiveRecord::AttributeMethods モジュールの中で define_attribute_methods が定義されていました。

attribute_methods.rb
      # Generates all the attribute related methods for columns in the database
      # accessors, mutators and query methods.
      def define_attribute_methods # :nodoc:
        return false if @attribute_methods_generated
        # Use a mutex; we don't want two threads simultaneously trying to define
        # attribute methods.
        generated_attribute_methods.synchronize do
          return false if @attribute_methods_generated
          superclass.define_attribute_methods unless self == base_class
          super(attribute_names)
          @attribute_methods_generated = true
        end
      end

https://github.com/rails/rails/blob/5-2-stable/activerecord/lib/active_record/attribute_methods.rb

この中の

 superclass.define_attribute_methods 

によって今度はActiveModelモジュールの方の同名のメソッドが呼ばれます。

attribute_methods.rb
      def define_attribute_methods(*attr_names)
        attr_names.flatten.each { |attr_name| define_attribute_method(attr_name) }
      end

https://github.com/rails/rails/blob/5-2-stable/activemodel/lib/active_model/attribute_methods.rb

この中での define_attribute_method メソッドがアクセサメソッドを定義していそうです。ここを追ってみると同じファイル内で以下のメソッドが定義されています。

attribute_methods.rb
      def define_attribute_method(attr_name)
        attribute_method_matchers.each do |matcher|
          method_name = matcher.method_name(attr_name)

          unless instance_method_already_implemented?(method_name)
            generate_method = "define_method_#{matcher.method_missing_target}"

            if respond_to?(generate_method, true)
              send(generate_method, attr_name.to_s)
            else
              define_proxy_call true, generated_attribute_methods, method_name, matcher.method_missing_target, attr_name.to_s
            end
          end
        end
        attribute_method_matchers_cache.clear
      end

https://github.com/rails/rails/blob/5-2-stable/activemodel/lib/active_model/attribute_methods.rb

この中の attribute_method_matchersActiveModel::AttributeMethods::ClassMethods::AttributeMethodMatcher の配列となっており、これらは <Atrribute名>_in_database 等すべての属性に共通して使えるようにするメソッドのプレフィックス等が格納されていました。
今回は、それらの深追いはせず、属性名自体のアクセサメソッドの定義の動作を追います。
それについては、上記コードの

send(generate_method, attr_name.to_s)

が呼ばれるようです。このとき引数は

generate_method # => define_method_attribute
attr_name.to_s # => tests

の通りでした。
define_method_attribute メソッドが実行されるので、追ってみると以下で定義されていました。

read.rb
          def define_method_attribute(name)
            safe_name = name.unpack("h*".freeze).first
            temp_method = "__temp__#{safe_name}"

            ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
            sync_with_transaction_state = "sync_with_transaction_state" if name == primary_key

            generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
              def #{temp_method}
                #{sync_with_transaction_state}
                name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
                _read_attribute(name) { |n| missing_attribute(n, caller) }
              end
            STR

            generated_attribute_methods.module_eval do
              alias_method name, temp_method
              undef_method temp_method
            end
          end

https://github.com/rails/rails/blob/5-2-stable/activerecord/lib/active_record/attribute_methods/read.rb

上記により、 module_evalによりメソッドが動的に定義されている様子がわかります。

型変換が実行されるタイミング

さて、上記によりモデルが new されてアクセサメソッドが定義されるまでの流れは分かりました。話を戻して型変換が実際に実行される処理を確認してみたいと思います。すなわち、今回の例では(2)を実行すると起きること、です。

(1)    task = Task.new(tests: "2019/01/01")
(2)    Time.use_zone("UTC"){ task.tests }

(2)の task.tests (=Model.newしたときに動的に定義されたアクセサメソッド)を実行すると、上記で確認したように以下メソッドの内容が実行されることになります。

read.rb
              def #{temp_method}
                #{sync_with_transaction_state}
                name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
                _read_attribute(name) { |n| missing_attribute(n, caller) }
              end

https://github.com/rails/rails/blob/5-2-stable/activerecord/lib/active_record/attribute_methods/read.rb

_read_attribute(name) が属性値を読み込んで値を返答している箇所と考えられるのでこの部分を追ってみると、同ファイルに定義されています。

read.rb
        def _read_attribute(attr_name) # :nodoc:
          @attributes.fetch_value(attr_name.to_s) { |n| yield n if block_given? }
        end

https://github.com/rails/rails/blob/5-2-stable/activerecord/lib/active_record/attribute_methods/read.rb

@attributesActiveModel::AttributeSet クラスです。
fetch_value メソッドを追ってみると、以下で定義されていました。

attribute_set.rb
      def fetch_value(name)
        self[name].value { |n| yield n if block_given? }
      end

https://github.com/rails/rails/blob/5-2-stable/activemodel/lib/active_model/attribute_set.rb

self[name].value は今回はWebブラウザからリクエストを送信しており ActiveModel::Attribute::FromUser でした。
ここで実行される value メソッドを追うと以下の定義されていました。

attribute.rb
    def value
      # `defined?` is cheaper than `||=` when we get back falsy values
      @value = type_cast(value_before_type_cast) unless defined?(@value)
      @value
    end

https://github.com/rails/rails/blob/5-2-stable/activemodel/lib/active_model/attribute.rb

さて、上記まで来るとかなり具体的なな処理が見えてきたように思います。
すなわち、

  • type_cast メソッドで元々の入力値( value_before_type_cast 、今回では "2019/01/01")をキャストしている
  • 一度キャストして返答した値は再びキャストしない( unless defined?(@value)

と考えてよいでしょう。せっかくなので type_cast ももう少し追ってみようと思います。同ファイル内に以下の定義があります。

attribute.rb
      class FromUser < Attribute # :nodoc:
        def type_cast(value) 
          type.cast(value)
        end

https://github.com/rails/rails/blob/5-2-stable/activemodel/lib/active_model/attribute.rb

type.classActiveRecord::AttributeMethods::TimeZoneConversion::TimeZoneConverter です。そのクラスの cast メソッドを確認してみると以下の定義が見つかります。

time_zone_conversion.rb
        def cast(value)
          return if value.nil?

          if value.is_a?(Hash)
            set_time_zone_without_conversion(super)
          elsif value.respond_to?(:in_time_zone)
            begin
              super(user_input_in_time_zone(value)) || super
            rescue ArgumentError
              nil
            end
          else
            map_avoiding_infinite_recursion(super) { |v| cast(v) }
          end
        end

https://github.com/rails/rails/blob/5-2-stable/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb

今回 value"2019/01/01" であり、Railsでは文字列が日時への変換対象のフォーマットを満たしていれば in_time_zoneメソッドで変換できるので、

            begin
              super(user_input_in_time_zone(value)) || super
            rescue ArgumentError
              nil
            end

の処理が実行されます。user_input_in_time_zone メソッドを追うと、

time_value.rb
        def user_input_in_time_zone(value)
          value.in_time_zone
        end

https://github.com/rails/rails/blob/5-2-stable/activemodel/lib/active_model/type/helpers/time_value.rb

が、実行されていました。なるほど型変換の実体は in_time_zoneメソッドが実行されていわけですね。すなわちその時の Time.zone のタイムゾーンにしたがって値が変換されると理解できます。

まとめ

さて、最初に戻って、以下のアクション( TasksController#index )が実行された場合に # ?? の部分が Tue, 01 Jan 2019 00:00:00 UTC +00:00 となる理由は、

  • (1) のタイミングでは各属性のアクセサメソッド等が定義されたたけで、実際にアクセサメソッド経由で取得される値の生成(型変換)はされていない
  • (2) のタイミングではじめてアクセサメソッドが実行され、元々入力された値が(必要な場合に)型変換されて返答される
    • 型変換は String#in_time_zone で実行されており、このときのタイムゾーンはUTCのためUTCのタイムゾーンで値が生成される
  • 一度型変換された値は二度目以降にアクセサメソッドが実行された後は再度型変換されないため、(3)のタイミングではタイムゾーンがTokyoであっても(2)で生成されたUTCの値が読み出される
tasks_controller.rb
class TasksController < ApplicationController
  def index
    task = Task.new(tests: "2019/01/01") # (1)
    Time.use_zone("UTC"){ task.tests } # (2)
    puts task.tests # ?? (3)
  end
end

感想

長かった…。

参考

『メタプログラミングRuby』にも少しActive Recordの記載があった(ただし少しVersionが古いので今と違う部分があるかも)。

メタプログラミングRuby 第2版
Paolo Perrotta
オライリージャパン
売り上げランキング: 85,250
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【rails】bootstrapでページネーション機能を作成する

gemのインストール

kaminariのgemをインストールします。

gem 'kaminari'
gem 'kaminari-bootstrap', '~> 3.0.1' #bootstrap3用
$ bundle install

インストール後、サーバーを再起動します。

ymlファイルの作成

config/localeskaminari_ja.ymlを作成し、以下を記載します。

kaminari_ja.yml
ja:
  views:
    pagination:
      first: "&laquo; 最初"
      last: "最後 &raquo;"
      previous: "&lsaquo; 前"
      next: " &rsaquo;"
      truncate: "..."

コントローラーの変更

class PostsController < ApplicationController
  def index
    @posts = Post.all
  end

以下のように変更します。

PER = 5  #区切りたいところを決める

class PostsController < ApplicationController
  def index
    @posts = Post.page(params[:page]).per(PER)
  end

ビューの変更

挿入したい箇所に以下を記入します。

    <div class="paginate text-center">
      <%= paginate @like_posts %>
    </div>

完成

c4f878fe5f7b7e018ed30be8cfe2d215.png

以上で、ページネーション機能が作成出来ます!

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

Gmailを用いた ActionMailer の使い方

はじめに

ActionMailerの実装は割と簡単にできたのですが、エラーコードが出ずにGmailの設定でかなりの時間を費やしてしまいました。
次にやるときには時間がかけずにすむようにまとめておきたいと思います。

コーディング

コマンド(自動生成)

rails g mailer UserMailer

create  app/mailers/user_mailer.rb
create  app/mailers/application_mailer.rb
invoke  erb
create    app/views/user_mailer
create    app/views/layouts/mailer.text.erb
create    app/views/layouts/mailer.html.erb
invoke  test_unit
create    test/mailers/user_mailer_test.rb
create    test/mailers/previews/user_mailer_preview.rb

Mailer

app/mailers/application_mailer.rb
class ApplicationMailer < ActionMailer::Base
  default from: "from@example.com"
  layout 'mailer'
end
app/mailers/user_mailer.rb
class UserMailer < ApplicationMailer
  default from: '~~~@gmail.com'

  # ここのメソッド名をメール文面の view ファイルの名前にする。
  # user と url は呼び出し元の コントローラーのメソッドから渡される。
  def user_create(user, url)
    # @user, @url に値を入れて view に渡す。
    @user = user
    @url = url
    mail(to: "user.name", subject: "ユーザーを新規登録しました。")
  end

Controller

user_controller.rb
class UsersController < ApplicationController

  def index
  end

  def new
    @user = User.new
  end

  def create
    @user = User.new(user_params)

    if @user.save
      @url = request.url + "/" + @user.id.to_s
      NotificationMailer.user_create(@user, @url).deliver
      redirect_to(user_path(@user))
    else
      render :new
    end
  end

  def show
    @user = User.find(params[:id])
  end

  def edit
    @user = User.find(params[:id])
  end

  def update
    @user = User.find(params[:id])
    if @user.update(user_params)

      @user = current_user
      @url = request.url
      NotificationMailer.user_update(@user, @url).deliver
      redirect_to(user_path(@user))
    else
      render :new
    end
  end
end

View

メール本文のテンプレートはデフォルトで二種類の形式が提供されています。

views/user_create/user_create.html.erb
<h1>UserMailer#user_create</h1>
<p>
<%= @user %> 様
</p>
<p>
  <%= @url %>, find me in app/views/user_mailer/user_create.html.erb
</p>
views/user_create/user_create.text.erb
UserMailer#user_create
<%= @user %> 様
<%= @url %>, find me in app/views/user_mailer/user_create.text.erb

Gmail googleの設定(重要)

google 二段階認証プロセスの設定
https://myaccount.google.com/signinoptions/two-step-verification/enroll-welcome

google アプリパスワードの作成
https://security.google.com/settings/security/apppasswords

config/initializers/mail.rb
ActionMailer::Base.delivery_method = :smtp
ActionMailer::Base.smtp_settings = {
  # address のところは変更しないので注意。自分はここでハマりました。
  address: 'smtp.gmail.com', 
  domain: 'gmail.com',
  port: 587,
  # user_name は自分のメールアドレスを記載。
  user_name: 'Gmail のメールアドレス',
  # password は作成したアプリパスワードを記載。
  password: 'Gmail のパスワード',
  authentication: 'plain',
  enable_starttls_auto: true
}

まとめ

駆け出しのエンジニアですので、何か不備がありましたらご連絡をお願いします。

参考

https://railsguides.jp/action_mailer_basics.html
https://qiita.com/hirotakasasaki/items/ec2ca5c611ed69b5e85e
https://thr3a.hatenablog.com/entry/20171202/1512175059

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

Rails ファイル添付機能 複数モデルでの共通化

はじめに

DRYの考え方を意識しはじめ、効率的なコーディングだと感じたので備忘録としてまとめます。

構成

UserモデルとPostモデルの両方にファイル添付機能を持たせたいとき、
Attachment_fileモデルに添付ファイルを格納して、
中間テーブルとしてUser_attachment_fileモデルとPost_attachment_fileモデルを設ける。

  • Attachment_fileモデル
  • Userモデル
  • User_attachment_fileモデル
  • Postモデル
  • Post_attachment_fileモデル

Gemfile

下記を書いて、bundle install

gem 'carrierwave'

コーディング

 Model

  • Attachment_fileモデル
  • Userモデル
  • User_attachment_fileモデル
  • Postモデル
  • Post_attachment_fileモデル
attachment_file.rb
mount_uploader :file, FileUploader
after_destroy :destory_intermediate

has_many :user_attachment_files
has_many :users, through: :user_attachment_files

has_many :post_attachment_files
has_many :posts, through: :post_attachment_files

def destory_intermediate
  user_attachment_files.destroy if user_attachment_files.present?
  post_attachment_files.destroy if post_attachment_files.present?
end
user.rb
class User < ActiveRecord::Base
  has_many :user_attachment_files, dependent: :destroy
  has_many :user_files, through: :user_attachment_files, dependent: :destroy
end
user_attachment_file.rb
class UserAttachmentFile < ActiveRecord::Base
  belongs_to :user
  belongs_to :attachment_file
end
post.rb
class Post < ActiveRecord::Base
  has_many :post_attachment_files, dependent: :destroy
  has_many :post_files, through: :post_attachment_files, dependent: :destroy
end
post_attachment_file.rb
class PostAttachmentFile < ActiveRecord::Base
  belongs_to :post
  belongs_to :attachment_file
end

Controller

  • Attachment_fileモデル

  • Attachment_fileモデル

attachment_files_controller.rb
class AttachmentFilesController < ApplicationController
  def create
    @attachment_file = AttachmentFile.create!(file_params)

    respond_to do |format|
      format.html
      format.json {render json: @attachment_file }
    end
  end

  def destroy
    @attachment_file = AttachmentFile.find(params[:id])
    if @attachment_file.destroy
      render :json => { result: 'success' }
    else
      render :json => { result: 'error' }
    end
  end

  private

  def file_params
    params.require(:attachment_file).permit(:id, :file)
  end
end

View

Dropzoneを使っています。ここでは詳細は割愛します。

(Dropzone.jsで複数ファイルアップロード後、削除したいファイルを指定したい:https://qiita.com/saekis/items/207379a056af73f143b7)

users/new.html.slim
tr
  th = label :user, :user_file
  td
    #user_file_uploader.dropzone                  / ポイント
    table.files
      - for file in @user.attachment_files do
        tr id="file_row_#{file.id}"
          td
            = file.original_filename
            = hidden_field_tag nil, file.id, name:'user[attachment_file_ids][]'
          td
            = link_to file.file_url, download: file.original_filename do
              = fa_icon "download"
              |ダウンロード
          td
            = link_to '#', onclick: "#", file_id: file.id, remote: true do
              |削除

Table

中間テーブル(User_attachment_file、Post_attachment_file)を持っているため、UserモデルとPostモデルではカラムを設ける必要はありません。

  • Userモデル
  • User_attachment_fileモデル
  • Postモデル
  • Post_attachment_fileモデル
  • Attachment_fileモデル
schema.rb
# Userモデル
create_table "users", force: :cascade, options: do |t|
  t.datetime "created_at",  null: false
  t.datetime "updated_at",  null: false
end

# User_attachment_fileモデル
create_table "user_attachment_files", force: :cascade, options: do |t|
  t.integer  "user_id",            limit: 4, null: false          # ポイント
  t.integer  "attachment_file_id", limit: 4, null: false          # ポイント
end

# Postモデル
create_table "posts", force: :cascade, options: do |t|
  t.datetime "created_at",  null: false
  t.datetime "updated_at",  null: false
end

# Post_attachment_fileモデル
create_table "post_attachment_files", force: :cascade, options: do |t|
  t.integer  "post_id",            limit: 4, null: false          # ポイント
  t.integer  "attachment_file_id", limit: 4, null: false          # ポイント
end

# Attachment_fileモデル
create_table "attachment_files", force: :cascade, options: do |t|
  t.string   "file",              limit: 255                      # ポイント
  t.string   "original_filename", limit: 255                      # ポイント
  t.datetime "created_at",                    null: false
  t.datetime "updated_at",                    null: false
end

まとめ

先輩のコードを見てこの方法を知りました。
人のコードを読むことの重要性を強く感じたので、コーディング → リファクタリング のサイクルを大切にしたいです。

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

【Rails】自分のアクティビティ一覧を表示する【通知機能の応用】

こんにちは!
ねこじょーかー(@nekojoker1234)と申します。

先日、ゼロから独学で勉強して、Webサービス「Lookmine」を立ち上げました。

このサービスでは、以下のような通知機能を実装しています。

通知一覧

この機能を応用して、自分が「いいね、コメント、フォロー」した履歴を「アクティビティ」として一覧で見る機能を作りました。

アクティビティ一覧

やっぱり、誰にいいねをしたとか、誰に何のコメントをしたとかは一覧で見れたほうがいいですよね。

そんなわけで、実装方法をこの記事で解説することにしました。

通知機能の実装が終わっている前提なので、まだ通知機能が出来ていない人は、「通知機能を実装する方法」から読んでくださいね。

アクティビティ一覧画面の作成

アクティビティ一覧の画面を作っていきましょう。
まずは、アクティビティコントローラーの作成からです。

$ rails g controller activities

通知一覧の画面はindexで作るので、ルーティングを追加しておきましょう。
他のメソッドは不要なので、onlyを追加しています。

config/routes.rb
  resources :activities, only: :index

indexアクションを実装します。

app/controllers/activities_controller.rb
class NotificationsController < ApplicationController
  def index
    @activities = current_user.active_notifications.page(params[:page]).per(20)
  end
end

アクティビティの一覧を表示するために新しくテーブルを作る必要はなく、通知機能で使ったテーブルをそのまま流用します。

今回は、active_notificationsで、自分が送った通知を取得します。

通知機能の実装で、以下の記述をしたのを覚えていますか?

app/models/user.rb
  has_many :active_notifications, class_name: 'Notification', foreign_key: 'visitor_id', dependent: :destroy
  has_many :passive_notifications, class_name: 'Notification', foreign_key: 'visited_id', dependent: :destroy

通知機能のときは、passive_notificationsを使って、相手に送った通知を取得していました。
今回は自分が送った通知を取得したいので、active_notificationsを使います。

次に、画面の実装です。
indexの画面を作っていきましょう。

app/views/activities/index.html.slim
.col-md-6.mx-auto
  - if @activities.exists?
    - @activities.each do |activity|
      = render 'activities/activity', activity: activity
  - else
    p.text-center
      | アクティビティはありません

自分が送った通知の分だけ、部分テンプレート_activity.html.slimを呼び出しています。

app/views/activities/_activities.html.slim
- visitor = activity.visitor
- visited = activity.visited
.form-inline
  span      
    - case activity.action
    - when 'follow' then
      = link_to user_path(visited) do
        = image_tag avatar_url(visited).to_s, class: "icon_mini" 
        strong
          = visited.name
      = "さんをフォローしました"
    - when 'like' then
      span
        = link_to post_path(activity.post) do
          = image_tag avatar_url(activity.post.user).to_s, class: "icon_mini" 
          strong
            = activity.post.user.name + 'さんの投稿'
      = "にいいねしました"
    - when 'comment' then
      - if activity.post.user_id == visitor.id
        = link_to "あなたの投稿", activity.post, style: "font-weight: bold;"
      - else
        span
          = link_to post_path(activity.post) do
            = image_tag avatar_url(activity.post.user).to_s, class: "icon_mini" 
            strong
              = activity.post.user.name + 'さんの投稿'
      = "にコメントしました"
      p.text-muted.mb-0
        = Comment.find_by(id: activity.comment_id)&.comment

.small.text-muted.text-right
  = time_ago_in_words(activity.created_at).upcase
hr

これを実装すると、以下のアクティビティ一覧が出来上がります。

アクティビティ一覧

フォローは相手のプロフィールをリンクにしています。

コメントは、コメント先の投稿をリンクにしていて、自分の投稿に対するコメントは「あなたの投稿」という表現にしています。
自分の投稿なのに「〇〇さんの投稿にコメントしました」という表現は、違和感がありますよね。

これで完成です!
お疲れさまでした!

あわせて読みたい

筆者のブログ:https://nekojokerblog.com

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

可変クラスの作り方 Rails Slim

はじめに

可変クラスを作るテクニック(?)を忘れないようにまとめておきたいと思います。

可変クラスの作り方

slim

slimで書くことが多いので、slimでまとめます。

ポイントとしては下記の形で書くことです。

  • "固定クラス + 可変クラス"
ruby.html.slim
.btn.outline.active class="#{a[num]}"
/ 固定クラス: .btn.outline.active
/ 可変クラス: #{a[num]}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails new したらこんなメッセージが表示されたので、調べてみた1.【chromedriver-helper is deprecated after 2019-03-31.】2.【Ruby Sass has reached end-of-life and should no longer be used.】

環境

ruby '2.6.5'
gem 'rails', '~> 5.2.3'
gem 'mysql2', '>= 0.4.4', '< 0.6.0'

メッセージ

 +--------------------------------------------------------------------+
  |                                                                    |
  |  NOTICE: chromedriver-helper is deprecated after 2019-03-31.       |
  |                                                                    |
  |  Please update to use the 'webdrivers' gem instead.                |
  |  See https://github.com/flavorjones/chromedriver-helper/issues/83  |
  |                                                                    |
  +--------------------------------------------------------------------+

Post-install message from sass:

Ruby Sass has reached end-of-life and should no longer be used.

* If you use Sass as a command-line tool, we recommend using Dart Sass, the new
  primary implementation: https://sass-lang.com/install

* If you use Sass as a plug-in for a Ruby web framework, we recommend using the
  sassc gem: https://github.com/sass/sassc-ruby#readme

* For more details, please refer to the Sass blog:
  https://sass-lang.com/blog/posts/7828841

こんなメッセージが表示されたので、とりあえずGoogle翻訳に頼ってみました

  • 翻訳
 +--------------------------------------------------------------------+
  |                                                                    |
  |  注意:chromedriver-helperは2019-03-31以降廃止されます                   |
  |                                                                    |
  |  代わりに 'webdrivers' gemを使用するように更新してください。                   |
  |  https://github.com/flavorjones/chromedriver-helper/issues/83      |
  |  を御覧ください                                                        |
  +--------------------------------------------------------------------+

Post-install message from sass:

Ruby Sassはサポート終了になったため、使用しないでください。

* Sassをコマンドラインツールとして使用する場合は、新しいDart Sassを使用することをお勧めします
   主要な実装:https://sass-lang.com/install

* SassをRuby Webフレームワークのプラグインとして使用する場合は、
   sassc gem:https://github.com/sass/sassc-ruby#readme

*詳細については、Sassブログを参照してください。
   https://sass-lang.com/blog/posts/7828841

ここで表示されているメッセージで言おうとしていることは以下の2つみたいです

  1. chromedriver-helper はサポート終わるから、代わりに webdrivers gem を使ってね
  2. Ruby Sass はサポート終了になるから対応策を載せておくよ。それぞれ対応してね

ってことみたいです

1. chromedirver-helper から webdrivers へ移行する

Gemfileにデフォルトでgem 'chromedriver-helper'と記述されているので、gem 'webdirvers'へ書き換える

Gemfile
group :test do
  # Adds support for Capybara system testing and selenium driver
  gem 'capybara', '>= 2.15'
  gem 'selenium-webdriver'

  # Easy installation and use of chromedriver to run system tests with Chrome
  # gem 'chromedriver-helper' # この行を削除

  gem 'webdrivers' # この行を追加
end

chromedriver-helperについて、webdriversへの詳しい移行手順を下の記事で載せてくれていますので、参考にさせてもらいました。

サポートが終了したchromedriver-helperからwebdrivers gemに移行する手順 - Qiita

2.sass-rails から sassc-rails へ書き換える

Gemfileのgem 'sass-rails', '...'の部分を gem 'sassc-rails'へ書き換える

Gemfile
# gem 'sass-rails', '~> 5.0' この行を削除
gem 'sassc-rails' # この行を追加

保存後bundle installを実行

これで先程のメッセージは表示されなくなりました。

【rails5】Ruby Sass is deprecated and will be unmaintained as of 26 March 2019. というメッセージ - Qiita
上記の記事を参考にしました。こちらの記事ではGemfile.lock内のsassとなっている箇所の削除となっていますが、自分は削除を行わずbundle installを実行してみました。

Gemfile.lock
# 省略

sassc (2.2.1)
  ffi (~> 1.9)
sassc-rails (2.1.2)
  railties (>= 4.0.0)
  sassc (>= 2.0)
  sprockets (> 3.0)
  sprockets-rails
  tilt

# 省略

DEPENDENCIES

# 省略

sassc-rails
# 以下省略

bundle install後 Gemfile.lock ファイルを確認するとsass-rails から sassc-railsへ書き換わっている

rails serverを実行サーバーが起動したので、正常に動作してるっぽいです。
また何かあれば追記していこうと思います。

参考

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

rootへのルーティング設定方法

目的

アプリの開発で仕様するrootメソッドの使い方をアウトプットを目的に備忘録的に残します。

ルーティング設定を行う

Railsアプリを作成、起動後「http://localhost:3000/」 にアクセスすると「public.index.html」に記載された内容が呼び出されています。
ただ、この状態はルートへアクセスしたときに用意したhtmlファイルを返しているだけです。

もしルートにアクセスした場合に特定のアクションを実行させたい場合、「root」メソッドを使ったルーティングを設定する必要があります。
そこで、ルーティングを設定して特定のアクションを実行し、viewを表示する方法を紹介していきます。

ルーティングの設定のため「config/routes.rb」ファイルを編集します。
indexアクションのビューをrootに設定されるように追記します。

config/routes.rb
root to: 'home#index'

まとめ

rootメソッドを定義することによって特定のアクションを実行することができます。

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

はじめてのPostgreSQL

この記事が対象とする読者

  • PostgreSQLをはじめて使う人

Homebrewを使ったインストールの仕方を簡潔にまとめます。

インストールの流れ

1. ターミナルからインストール

まずは、以下のコマンドを入力・実行します。

$ brew install postgresql

2. インストールの確認

正常にインストールできているかどうかを確認します。
バージョンを表す-Vは大文字なので注意です。

$ postgres -V
postgres (PostgreSQL) 11.5

3. PostgreSQLを起動

データベースとして使用するためには以下の起動のコマンドも必要です。

$ brew services start postgresql
==> Successfully started `postgresql` (label: homebrew.mxcl.postgresql)

ここまで完了すればデータベースが作成できるようになるはずです!

さいごに

PostgreSQL初心者による初心者のためのQiitaでした。
簡単な工程なのですが、検索しながらやっていたら迷ってしまったので共有しておきます。少しでも参考になればうれしいです。

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

GAE(FE)にDocker環境上のRuby on Railsをデプロイする

はじめに: Dockerで開発したら手軽にデプロイまでしたい。

Ruby on Railsの開発で今や主流のDocker環境。
ネット上に環境構築の情報は豊富に転がっているし、local開発は出来たし、Herokuにもデプロイできた。

でもやっぱりHerokuだとイマイチ速度が出ないし、
何よりAWSとかGCP使ってるほうがカッコイイ気がする!(小並感)

圧倒的に情報不足なDockerに乗っけたままAWSやGCPにデプロイをする方法。
今回は既にHerokuで運用中のwebサービスをGAE環境に移し替えたのでその際に行った手順と、躓いた点を書いていこうと思います。

自分自身も右往左往しながら、なんとかデプロイできたー!!動いたー!!
という感じなので、間違っている点や、改善点などが有りましたら是非色々とご教示いただけると助かります。

前提

  • 既にDocker+ docker-composeで環境構築済み、動作確認済みのRuby on Railsアプリケーションがある
  • GCPアカウント及びプロジェクトが作成済
  • Cloud SDKが導入済み

【参考】
Dockerを使ってRuby on Rails環境の構築をしてみる
https://qiita.com/me-654393/items/d11b871ce8d76e153b21

Cloud SDK のインストール
https://cloud.google.com/sdk/downloads?hl=ja

App Engineにアプリを作成する

まず最初に、ターミナルからGAEアプリを作成します。

$ gcloud app create
You are creating an app for project [project-name].
WARNING: Creating an App Engine application for a project is irreversible and the region
cannot be changed. More information about regions is at
<https://cloud.google.com/appengine/docs/locations>.

Please choose the region where you want your App Engine application
located:

 [1] asia-east2    (supports standard and flexible)
 [2] asia-northeast1 (supports standard and flexible)
 [3] asia-northeast2 (supports standard and flexible)
 [4] asia-south1   (supports standard and flexible)
 [5] australia-southeast1 (supports standard and flexible)
 [6] europe-west   (supports standard and flexible)
 [7] europe-west2  (supports standard and flexible)
 [8] europe-west3  (supports standard and flexible)
 [9] europe-west6  (supports standard and flexible)
 [10] northamerica-northeast1 (supports standard and flexible)
 [11] southamerica-east1 (supports standard and flexible)
 [12] us-central    (supports standard and flexible)
 [13] us-east1      (supports standard and flexible)
 [14] us-east4      (supports standard and flexible)
 [15] us-west2      (supports standard and flexible)
 [16] cancel
Please enter your numeric choice:  2

Creating App Engine application in project [project-name] and region [asia-northeast1]....done.
Success! The app is now created. Please use `gcloud app deploy` to deploy your first app.

asia-northeast1が東京リージョンですので、選択します。
※[project-name]となっている部分は各々作成したGCPプロジェクト名が入ります。

app.yamlを作成する

続いて、Dockerfileと同階層にapp.yamlを作成します。
app.yamlの説明に関してはここでは省略します。
【参考】https://cloud.google.com/appengine/docs/standard/go/config/appref?hl=ja

app.yaml
entrypoint: bundle exec rackup --port $PORT
env: flex
runtime: custom

env_variables:
  SECRET_KEY_BASE: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX(各々のsecret key baseを入れる)

SECRET_KEY_BASEの取得方法は、
コンソール上で、

$ docker-compose exec web bundle exec rails secret

表示される文字列をコピペすればOKです。

Railsサーバの起動ポートを8080に合わせる

GAEでは、8080番に対してアクセスするので、3000番などに設定している方は8080番に変更しましょう。

Dockerfile(例)
FROM ruby:2.6.3

RUN apt-get update -qq && \
  apt-get install -y build-essential \
  libpq-dev \
  nodejs

RUN mkdir /app_name
ENV APP_ROOT /app_name
WORKDIR $APP_ROOT

ADD ./Gemfile $APP_ROOT/Gemfile
ADD ./Gemfile.lock $APP_ROOT/Gemfile.lock

RUN bundle install
ADD . $APP_ROOT

# 8080番でポートを起動する
CMD /bin/sh -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 8080 -b '0.0.0.0'"

docker-compose.yml(例)
version: "3"
services:
  web:
    environment:
      TZ: Asia/Tokyo
    build: .
    volumes:
      - .:/app
    ports:
      - 8080:8080
    tty: true
    stdin_open: true

volumes:
  db-volume:

基本的な準備はここまで。

いざデプロイ!

さて、待ちに待ったデプロイ。この瞬間が一番ドキドキします。
ターミナルを開いて、

$ gcloud app deploy

descriptor:      [/project-name/app.yaml]
source:          [/projects/project-name]
target project:  [project-name]
target service:  [default]
target version:  [20190000t123456]
target url:      [https://project-name.appspot.com]
Do you want to continue (Y/n)?  Y(←上記の内容で問題なければ、Yを入力します。)

ビルドが始まり、しばらく待ち、

Updating service [default] (this may take several minutes)...done.
Setting traffic split for service [default]...done.
Deployed service [default] to [https://project-name.appspot.com]

You can stream logs from the command line by running:
  $ gcloud app logs tail -s default

To view your application in the web browser run:
  $ gcloud app browse

と、表示されれば成功。

$ gcloud app browse

で確認しましょう。

トラブルシューティング

「Switch to inspect mode.」と怒られる件について

Updating service [default] (this may take several minutes)...failed.
ERROR: (gcloud.app.deploy) Error Response: [9]
Application startup error:
Switch to inspect mode.

色々ググったけど今ひとつ情報源に当たれずにめちゃくちゃ困ってた。
8080番でRailsが起動しておらず、GAEがアクセスできていないってことだと思う。

原因はdocker-compose側でサーバーを起動していたこと。

docker-compose.yml(例)
version: "3"
services:
  web:
    build: .

    # ここがアウト
    command: /bin/sh -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 8080 -b '0.0.0.0'"

    volumes:
      - .:/app_name
    ports:
      - "8080:8080"
    links:
      - db

これだとこのエラーが出るみたい。
原因はいまいちよくわかっていないが、もしかしたらdocker-compose読んでいないのかも?

8080番ポートの下りで記載したDockerfileのように、

Dockerfile
CMD /bin/sh -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 8080 -b '0.0.0.0'"

で、8080を開いてあげるのがいいのかも。

【参考】
A weekend, a Rails app, a Kubernetes and an Azure.
https://medium.com/@michiels/a-weekend-a-rails-app-a-kubernetes-and-an-azure-d330b003d7c2

最後に:Herokuの方がわかりやすいけど、やっぱりGAE早い

今回は実行速度が気になったので、HerokuからGAEに乗り換えることを決意したわけですが、
予想通りGAEの方が速度が出ました。

左: Heroku
右: GAE
sp-heroku.jpg sp-gae.jpg

特にRailsの中身をいじること無く、20程度速度が速度が上昇しました。
改善案の部分でサーバー応答時間の短縮(TTFB)が表示されなくなったのがでかい気がします。

おまけ:宣伝

今回HerokuからGAEに移行したのはスポチューバーTVという、野球の技術指導メディアです。
スポーツ教育×Techの、主に野球の分野を伸ばしていけたらなと思っているので、興味のある方は是非御覧ください。
https://spotuber-tv.com/

参考

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

自分用メモ(View・部分テンプレート)

Viewあたり

レイアウト

部分テンプレートとも似ているが、レイアウトはページの外枠を定義するのに利用する(ヘッダー・フッター・メニューなど)

パーシャル(_〇〇.html.erb)

Viweの共通化に用いられる
ファイル名の先頭にアンダースコア( _ )をつける

呼び出すときはアンダースコアをつけない

ex)
layoutディレクトリの_form.html.erb

↓呼び出すときは
render 'layout/form'(app/viewsからの相対パスを用いる)

部分テンプレート

複数ページで共通の領域がある場合使用する(毎回同じコードを記載するのは無駄なので)
レイアウトとは違い、ページの外枠ではない、もう少し部分的なページの共通領域を定義するために利用する

<% %>と<%= %>の違いがピンと来ていない

<%= %>
与えられた式を出力する

<% %>
がブロックの中のコードを実行するだけ
→出力しないでもokな時にこちらを使用する(条件やループ)

基本的にテンプレートファイル(コントローラー名/アクション名.html.erb)は画面に大して何らかの出力を行うので、ほとんどの記述は<%= %>で行う

yieldとprovide

<% provide(:tag,xxx) %>で入力しxxxを
<%= yield(:tag) %>で受け取る感じ

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

Sessionメソッドの使い方、宣言方法

目的

ログイン機能を実装するときにSessionメソッドを使用するが、
機能の流れをおさらいするために備忘録として残しておく。

セッションとは?

・主にログイン機能に使用されるもので、ログイン状態を持続させるためにある機能。
・ステートフルな通信を実現するために必要な機能。

セッションという仕組みがなけれはページを移動するたびにログインし直さなければならない。

ログイン機能を実装するためRailsではあらかじめセッションを実装するためのメソッドが用意している。
また、セッションの情報はRailsの標準では、ブラウザ側のクッキーに保存される。

Sessionメソッドの使い方

まずは、ユーザーを登録するためにユーザーモデルを準備する。
ユーザーモデルには、名前とメールとパスワードが登録できるようにします。

gemをGemfileに追加

パスワードを暗号化するための
(コメントアウトされているためコメントインする必要がある)

Gemfile
gem 'bcrypt', '~> 3.1.7'

コメントインが完了したらgemをインストールする必要がある。

bundle install

Userモデルを作成

rails g model Users name:string email:string password_digest:string 

password_digestというカラムにはパスワードが入ります。
こちらはgemであるbcryptの仕様ですが、このカラムにするとパスワードが暗号化されて保存されます。

migrationファイルが作成されたので、データベースに反映しましょう。

rails db:migrate

これでユーザーモデルの作成は完成です。

次に作成したユーザモデルにパスワードの暗号化を有効にするための設定を加えます。

app/models/user.rb
has_secure_password
Sessionコントローラーを作成

ログイン機能を作成するためセッションコントローラーを作成する。

rails g controller sessions
app/controllers/session_sontroller.rb
class SessionsController < ApplicationController
  def new
  end

  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      session[:user_id] = user.id
      redirect_to user_path(user.id)
    else
      render 'new'
    end
  end
end

ログインボタンを押した後に、セッションに情報が保存されているか判断しています。

user = User.find_by(email: params[:session][:email].downcase)

params[:session][:email]で、ユーザのポストでした値を取り出し、メールアドレスには小文字しか入力できないため.downcaseで小文字に変換しています。
その後find_byメソッドで入力したemailをカラムに保存されたemailが一致した場合にユーザーに代入しています。

次に、

if user && user.authenticate(params[:session][:password])

&&を使って、条件を2つ指定しています。
user:userの中身があるかどうか
user.authenticate:userに代入されたレコードのパスワードがポストした値と一致しているか

パスワードが登録しているユーザー情報と一致していたらセッションにユーザーIDを登録しています。

session[:user_id] = user.id

session[:名前]は名前をつけて、セッションを登録できます。

また、elseの場合、

render 'new'

パスワードが登録しているユーザ情報と一致していなかった場合、入力フォームに再度飛ぶように設定されている。

これでセッションを作成することができました。

まとめ

セッションを作成する際に必要なものは以下である。

・パスワードを暗号化するためのgem:bcrypt
・gem:bcryptで暗号化したパスワードを保存するカラム:password_digest
・パスワードの暗号化を有効にするための設定:has_secure_password
・セッションを作成するためにメソッド:session[:名前] = params[:~]

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

MacでRailsにgem mysql2をインストールする時のトラブルシューティング

はじめに

2019年10月現在MacでRailsアプリ開発時にDBにMySQLを選択する場合、デフォルトではmysql2というgemをインストールします。
その時にいくつかエラーに出会ったので、まとめておきます。

実行環境

  • macOS Mojave v10.14.6
  • ruby v2.6.4
  • Rails v6.0.0

MySQLのクライアントが必要

MacにMySQLクライアントをインストールしていない場合、以下のようなエラーが表示されます。

Fetching mysql2 0.5.2
Installing mysql2 0.5.2 with native extensions
Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

    current directory: /Users/hogehoge/sample-app/vendor/bundle/gems/mysql2-0.5.2/ext/mysql2
/Users/hogehoge/.rbenv/versions/2.6.4/bin/ruby -I /Users/hogehoge/.rbenv/versions/2.6.4/lib/ruby/2.6.0 -r
./siteconf20191012-40688-181m253.rb extconf.rb
checking for rb_absint_size()... yes
checking for rb_absint_singlebit_p()... yes
checking for rb_wait_for_single_fd()... yes
checking for -lmysqlclient... no
-----
mysql client is missing. You may need to 'brew install mysql' or 'port install mysql', and try again.

Homebrewなどでインストールしておきましょう。
なお、2019年10月現在gemが対応しているバージョンは以下のようですので、採用バージョンに応じたものをインストールしましょう。

This gem is tested with the following MySQL and MariaDB versions:
MySQL 5.5, 5.6, 5.7, 8.0
MySQL Connector/C 6.0 and 6.1 (primarily on Windows)
MariaDB 5.5, 10.0, 10.1, 10.2, 10.3

(https://github.com/brianmario/mysql2 READMEより引用)

# MySQL最新バージョン(8.0)
$ brew install mysql

# 5.xの場合
$ brew install mysql@5.x

5.x系の場合はmysqlコマンドを使えるよう以下のようにPATHを通しておきましょう。

$ export PATH="/usr/local/opt/mysql@5.7/bin:$PATH"

bundle install時のlinkerエラー

無事MySQLクライアントをインストールできていても、以下のようなエラーが起こる場合があります。

Fetching mysql2 0.5.2
Installing mysql2 0.5.2 with native extensions
Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

    current directory: /Users/hogehoge/sample-app/vendor/bundle/gems/mysql2-0.5.2/ext/mysql2
/Users/hogehoge/.rbenv/versions/2.6.4/bin/ruby -I /Users/hogehoge/.rbenv/versions/2.6.4/lib/ruby/2.6.0 -r
./siteconf20191012-62886-155hohe.rb extconf.rb
checking for rb_absint_size()... yes
checking for rb_absint_singlebit_p()... yes
checking for rb_wait_for_single_fd()... yes
-----
Using mysql_config at /usr/local/bin/mysql_config
-----
checking for mysql.h... yes
checking for errmsg.h... yes
checking for SSL_MODE_DISABLED in mysql.h... yes
checking for SSL_MODE_PREFERRED in mysql.h... yes
checking for SSL_MODE_REQUIRED in mysql.h... yes
checking for SSL_MODE_VERIFY_CA in mysql.h... yes
checking for SSL_MODE_VERIFY_IDENTITY in mysql.h... yes
checking for MYSQL.net.vio in mysql.h... yes
checking for MYSQL.net.pvio in mysql.h... no
checking for MYSQL_ENABLE_CLEARTEXT_PLUGIN in mysql.h... yes
checking for SERVER_QUERY_NO_GOOD_INDEX_USED in mysql.h... yes
checking for SERVER_QUERY_NO_INDEX_USED in mysql.h... yes
checking for SERVER_QUERY_WAS_SLOW in mysql.h... yes
checking for MYSQL_OPTION_MULTI_STATEMENTS_ON in mysql.h... yes
checking for MYSQL_OPTION_MULTI_STATEMENTS_OFF in mysql.h... yes
checking for my_bool in mysql.h... no
-----
Dont know how to set rpath on your system, if MySQL libraries are not in path mysql2 may not load
-----
-----
Setting libpath to /usr/local/Cellar/mysql/8.0.17_1/lib
-----
creating Makefile

current directory: /Users/hogehoge/sample-app/vendor/bundle/gems/mysql2-0.5.2/ext/mysql2
make "DESTDIR=" clean

current directory: /Users/hogehoge/sample-app/vendor/bundle/gems/mysql2-0.5.2/ext/mysql2
make "DESTDIR="
compiling client.c
compiling infile.c
compiling mysql2_ext.c
compiling result.c
compiling statement.c
linking shared-object mysql2/mysql2.bundle
ld: library not found for -lssl
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make: *** [mysql2.bundle] Error 1

make failed, exit code 2

以下がこのエラーの原因で、ざっくり言うとgemのビルド時に、必要なopensslライブラリを見つけられないために発生しています。

ld: library not found for -lssl

詳細な原因は以下記事が大変参考になります。
https://qiita.com/HrsUed/items/ca2e0aee6a2402571cf6

解決策としては、bundle install時に以下オプションでpathを指定する必要があります。

  • --with-cppflags
  • --with-ldflags

brew infoでインストールされているopensslを確認すると、自分の環境のLDFLAGSとCPPFLAGSのpathが確認できます。

$ brew info openssl@1.1

openssl@1.1: stable 1.1.1d (bottled) [keg-only]
...
For compilers to find openssl@1.1 you may need to set:
  export LDFLAGS="-L/usr/local/opt/openssl@1.1/lib"
  export CPPFLAGS="-I/usr/local/opt/openssl@1.1/include"
...

こちらのpathをbundle configで設定してからインストールしましょう。

$ bundle config --local build.mysql2 "--with-cppflags=-I/usr/local/opt/openssl@1.1/include"
$ bundle config --local build.mysql2 "--with-ldflags=-L/usr/local/opt/openssl@1.1/lib"
$ bundle install

以上、自分が遭遇したエラー内容でした。

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

Railsチュートリアル 第10章<復習>

第10章の復習メモです。
個人的に重要と思ったことを書きます。

前回と同様、以下3つの視点で書きます。

  • 分かったこと
  • 分からなかったこと
  • 今回はスルーしたこと

分かったこと

PATCHリクエストについて

ユーザの新規作成画面(app/views/users/new.html.erb)と、編集画面(app/views/users/edit.html.erb)は、画面項目が同じなため、フォームを部分テンプレートに共通化した。

app/views/users/_form.html.erb
<%= form_for(@user) do |f| %>
  <%= render 'shared/error_messages', object: @user %>

  <%= f.label :name %>
  <%= f.text_field :name, class: 'form-control' %>

  <%= f.label :email %>
  <%= f.email_field :email, class: 'form-control' %>

  <%= f.label :password %>
  <%= f.password_field :password, class: 'form-control' %>

  <%= f.label :password_confirmation %>
  <%= f.password_field :password_confirmation, class: 'form-control' %>

  <%= f.submit yield(:button_text), class: "btn btn-primary" %>
<% end %>

共通化により懸念されるのが、

  • リクエスト先URLの違い
  • リクエストのメソッドの違い

ルーティングについて、7章で以下のように設定した。
image.png

これらの違いがあるので、普通に考えたら、分岐の処理を入れる必要がある。しかし、これについてはRailsが上手くやってくれる

Railsは、form_for(@user)を使ってフォームを構成すると、@user.new_record?がtrueのときにはPOSTを、falseのときにはPATCHを使います。

DBへのデータ一括登録

画面やコンソールから一件ずつ登録せずとも、DBにデータを一括登録する方法がある。
手順は以下の通り。

  1. Gemfileにgemを追加する
  2. 登録したいデータを用意する
  3. DBに反映させる

手順1. Gemfileにgemを追加する

Faker gemを追加する

source 'https://rubygems.org'

gem 'rails',          '5.1.6'
gem 'bcrypt',         '3.1.12'
gem 'faker',          '1.7.3'  # ← これを追加
.
.
.

手順2. 登録したいデータを用意する

db/seeds.rbファイルに、登録したいデータを記載する

db/seeds.rb
User.create!(name:  "Example User",
             email: "example@railstutorial.org",
             password:              "foobar",
             password_confirmation: "foobar")

99.times do |n|
  name  = Faker::Name.name
  email = "example-#{n+1}@railstutorial.org"
  password = "password"
  User.create!(name:  name,
               email: email,
               password:              password,
               password_confirmation: password)
end

Example Userという名前とメールアドレスを持つ1人のユーザと、それらしい名前とメールアドレスを持つ99人のユーザーを作成している。

手順3. DBに反映させる

現在登録されているデータを消しておきたい場合、

$ rails db:migrate:reset

を実行する。

手順2をDBに反映させるには、

$ rails db:seed

を実行する。

ページネーション

一覧画面で、表示件数を区切って出力できる。手順は以下の通り。

  1. Gemfileにgemを追加する
  2. ビュー、コントローラを編集する

手順1. Gemfileにgemを追加する

will_paginate gembootstrap-will_paginate gemを追加する

source 'https://rubygems.org'

gem 'rails',                   '5.1.6'
gem 'bcrypt',                  '3.1.12'
gem 'faker',                   '1.7.3'
gem 'will_paginate',           '3.1.6'  # ← これを追加
gem 'bootstrap-will_paginate', '1.0.0'  # ← これを追加
.
.
.

手順2. ビュー、コントローラを編集する

ビューに処理を追記

app/views/users/index.html.erb
<% provide(:title, 'All users') %>
<h1>All users</h1>

<%= will_paginate %>

<ul class="users">         # ← これを追加
  <% @users.each do |user| %>
    <li>
      <%= gravatar_for user, size: 50 %>
      <%= link_to user.name, user %>
    </li>
  <% end %>
</ul>

<%= will_paginate %>       # ← これを追加

Railsチュートリアルの説明を引用

このwill_paginateメソッドは少々不思議なことに、usersビューのコードの中から@usersオブジェクトを自動的に見つけ出し、それから他のページにアクセスするためのページネーションリンクを作成しています。

コントローラの処理を変更

app/controllers/users_controller.rb
class UsersController < ApplicationController
  before_action :logged_in_user, only: [:index, :edit, :update]
  .
  .
  .
  def index
    @users = User.paginate(page: params[:page])  # ← ここを変更
  end
  .
  .
  .
end

Railsチュートリアルの説明を引用

paginateでは、キーが:pageで値がページ番号のハッシュを引数に取ります。User.paginateは、:pageパラメーターに基いて、データベースからひとかたまりのデータ (デフォルトでは30) を取り出します。
paginateを使うことで、サンプルアプリケーションのユーザーのページネーションを行えるようになります。具体的には、indexアクション内のallをpaginateメソッドに置き換えます。ここで:pageパラメーターにはparams[:page]が使われていますが、これはwill_paginateによって自動的に生成されます。

モデルの論理値属性

モデルの属性に、論理値(boolean)を設定できる。

migrationファイルの作成

$ rails generate migration add_admin_to_users admin:boolean

migrationファイルの編集

default: falseを追加することで、全レコードfalse(0)で登録できる。

db/migrate/[timestamp]_add_admin_to_users.rb
class AddAdminToUsers < ActiveRecord::Migration[5.0]
  def change
    add_column :users, :admin, :boolean, default: false
  end
end

属性の状態を確認

<インスタンス>.<属性>?メソッドで、属性の状態を確認できる。

$ rails console --sandbox
>> user = User.first
>> user.admin?
=> false

分からなかったこと、今回はスルーしたこと

  • Strong Parameters
  • テスト全般
  • アプリケーションの仕様、ロジックの詳細
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【53日目】掲示板にタグを設定しよう!② タグ検索機能の実装、

タグ検索の実装

昨日の続きとなります。
タグの初期データを用意したり、個別の掲示板にタグを設定して保存したり、掲示板詳細画面に関連づけられたタグを表示する機能については【52日目】を参照してください。

今日の内容は下記のとおりです。
○ 掲示板一覧でタグによる絞り込み機能の実装
 ・ セレクトボックスの作成方法
○ ヘッダーメニュー(グローバルメニューの作成)
 ・ ヘルパーメソッドの自作方法
 ・ HTMLタグの自作方法
 ・ ルーティングの自作方法

掲示板一覧画面にタグのセレクトボックスを用意し、タグを入力して、該当のタグのある掲示板だけ残すようにしていきます。

掲示板一覧にタグのセレクトボックスを追加する(view)

indexのviewを編集して、掲示板一覧からタグを選び、選んだタグに関連づけられた掲示板のみを表示するようにします。

タグの選択は、プルダウン式のセレクトボックス(select_tagヘルパー使用)によって実装します。
セレクトボックスを選んだ瞬間に掲示板が絞り込まれるのは、「onchange」という属性によってJSを実行します。

index.html.erb
<%= form_tag boards_path, method: :get, class: 'boards__searchForm' do %>
 # form_tag, アクションへのパス, HTTPメソッド, HTMLオプション
 # classはセレクトボックスの位置を調整するためのHTMLオプションで、あとでCSSを編集する。
    <%= select_tag :tag_id, #  セレクトボックスのname属性がtag_idになる。
                   options_from_collection_for_select(Tag.all, :id, :name, params[:tag_id]),
                   #ヘルパーによってセレクトボックスの選択肢となるオプション要素を複数作成している
                   #選択肢としたいオブジェクトのリスト, オプションのvalue, オプションの表示名, 何を選択状態とするの指定
                   # 第四引数の指定によってGET通信でURLに含まれるクエリパラメータのtag_idを選択状態とすることができ、検索後も選択した状態に出来る。
                  { #ハッシュの中身全部が第三引数
                   prompt: 'タグで絞り込み', #空白の時に表示する文字
                   class: 'form-control boards__select', # クラス属性に設定する値
                   onchange: 'submit(this.form);'
                   #選択されるたびに実行するJSのコードを記載している
                   #onchange属性はセレクトボックスに限らず、値が変わった際に実行されるJSのコードを設定できるイベント属性。
                   #ここでは、何か選択肢から選んだ時点でフォーム(this.form)を作成するようになっている
                  }
    %>
<% end %>

CSSの編集

.boards__searchForm {
  display: inline-block;
}

.boards__select {
  display: inline-block;
  width: auto;
}

これによって、掲示板一覧画面の上部にタグのセレクトボックスが表示されます。
ここでタグを選ぶことによって、選んだタグのtag_idをindexアクションに送ることができる。
ただし、現状のコントローラーにはviewから渡ってきたtag_idを使って検索する機能がないので、コントローラーを編集します。

tag_idを用いた検索機能の実装(controller)

tag_idに該当するタグの取得 => タグオブジェクトから関連する掲示板を取得する。

boards_controller.rb
def index
  @boards = params[:tag_id].present? ? Tag.find(params[:tag_id]).boards : Board.all
  # tag_idがあるかを確認(present? ?)
  # tag_idがある場合には、tag_idのパラメータを持つタグを検索(.find)し、
  # 該当するタグにhas_manyでアソシエーションされている掲示板のリストを取得している(親モデル.<has_manyアソシエーション名>)
  # tag_idがない場合は(present? ? 存在する場合の処理 : 存在しない場合の処理)全ての掲示板(Board.all)を表示する
  # @boardsを参照するまではBoard.allを取得することはない。
  @boards = @boards.page(params[:page])
  # 上記で定義された@boardsに対して、page内に表示する件数の制限をかけてから、取得する。
  # 例えば10件とした場合、10件分だけのSQLが発行される。ためBoard.allとしてもパフォーマンスに悪影響しない
end

ヘッダーメニューの追加

ブートストラップのデザインを使用し(もちろんCSSでもいい)viewを作成し、ページを遷移しても常に表示されるメニューを作成する。

touch app/views/application/_header.html.erb

を編集していく。
ここではブートストラップのナビゲーションバーのデザインを使用し、部分的に修正している。
修正箇所にコメントを付記する。
独自のヘルパーを用いる部分があるので、この後ヘルパーを定義する。

_header.html.erb
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
  <a class="navbar-brand" href="#">BoardApp</a> #タイトル名を変更
  <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-        controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
    <span class="navbar-toggler-icon"></span>
  </button>

<div class="collapse navbar-collapse" id="navbarSupportedContent">
  <ul class="navbar-nav mr-auto">
   # リンク部分の修正。header_link_iteというへルパーを作って使用する。
   # link_toとしないのは、現在表示中のページのみをアクティブ(リンクが白くなる)にするために設定を加えたいから。
   # header_link_item('表示名', リンク先のパス) とすることでリンクを張れる。
    <%= header_link_item('Home', root_path) %> #root_pathはrootingを追加しないと存在しないので後で追加します。ホーム画面です。
    <%= header_link_item('Boards', boards_path) %> 
    <li class="nav-item dropdown">
      <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
        Dropdown
      </a>
      <div class="dropdown-menu" aria-labelledby="navbarDropdown">
        <a class="dropdown-item" href="#">Action</a>
        <a class="dropdown-item" href="#">Another action</a>
        <div class="dropdown-divider"></div>
        <a class="dropdown-item" href="#">Something else here</a>
     </div>
    </li>
    <li class="nav-item">
      <a class="nav-link disabled" href="#">Disabled</a>
    </li>
  </ul>
  <form class="form-inline my-2 my-lg-0">
    <input class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search">
    <button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button>
    </form>
  </div>
</nav>

ヘルパーメソッドの作成

今回はグローバルメニューに使う=全ページで使うヘルパーになるので

app/helpers/application_helper.rb

の中に設定する。
特定のコントローラーでしか使用しない場合は、

boards_helper.rb

のように区別した方が用途がわかりやすくて間違いにくい。

application_helper.rb
module ApplicationHelper
  def header_link_item(name, path)
    class_name = 'nav-item' # ブートストラップのデザインに合わせるためのクラス
    class_name << ' active' if current_page?(path) # 表示中のパスと引数のパスが同一であるかを判定する
    # 表示するパスと引数のパスが同じであれば、クラスの属性に「active」を追加する、というもの。

    content_tag :li, class: class_name do
    # content_tag(HTMLタグ名, HTMLクラス)で任意のHTMLタグを作成できる。
    # ここでは上で作成した「class_name」を指定している。
      link_to name, path, class: 'nav-link'
      # content_tagのブロック内に書いたHTMLが作成したタグの中で展開される。
      # ここでは仮引数で受け取ったとおりにリンクを貼るアンカータグを埋め込むことになる。
    end
  end
end

root_pathの作成

次に、ルーティングを追加する。
ここではまだページは作成していない。

routes.rb
root 'home#index'
# パス名 'コントローラー名#アクション名'

ヘッダーのview(パーシャル)の作成

ヘッダーのパーシャルをrenderで呼び出すだけ。

application.html.erb
<%= render 'header' %>

この後認証機能の学習に進みますが、かなりジャンルが変わるのでここで一旦投稿します。

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

【Ruby】=と==、&と&&、|と||の違い

RubySilverの勉強でつまづいたのでメモします。

=と==の違い

=が左辺に代入
==が同じ
です。

example.html.slim
- dinner = @dinner.vegetables? ? "食べたくない" : "食べる"
= dinner
example.rb
def hoge
  return false if @dinner.vegetables == "きゅうり"
  return false if @dinner.vegetables == "レタス"
  return false if @dinner.vegetables == "なす"

  true
end

&と&&の違い

&が積集合(共通する要素を取り出す)
&&がtrue/falseを返す(全部true(存在する)ならtrue出力)
です。

a = [1, 2, 3]
b = [2, 3, 4]

# 共通する要素を出力
a & b
=> [2, 3]

# 最後に評価されたb(true)を出力
a && b
=> [1, 2, 3]

# aが偽のためnil(右辺を見ない)
a = nil

a && b
=> nil

|と||の違い

・|が和集合
・||がtrue/falseを返す(true(存在する)の時点でtrue出力)
です。

a = [1, 2, 3]
b = [2, 3, 4]

# いずれかに含まれる要素を出力
a | b
=> [1, 2, 3, 4]

# 最初に評価されたa(true)を出力
a || b
=> [1, 2, 3]

# aが偽のためbを出力(右辺まで見る)
a = nil

a || b
=> [2, 3, 4]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

carrierwave と fog を使ってasset hostを決める

やりたいこと

取得する画像のURLをホスティングしているサーバーのURL付きで欲しい。

現状、localhost:3000(バックエンド)とlocalhost:4000(フロントエンド)で分けて開発しているが、

画像のURLを取得すると、以下のようになっている。

{ url: "uploads/user/profile_image/1/adcb24fe3b95a9f70fa81f2cf5c6e8ec.jpg" }

ので、以下のように変更したい。

{ url: "http://localhost:3000/uploads/user/profile_image/1/adcb24fe3b95a9f70fa81f2cf5c6e8ec.jpg" }

tl:dr

config/initializers/carrierwave.rbを作成し、asset_hostを設定する。

Gemのインストール

carrierwaveuploader/carrierwave

※本番環境では、GCPを使うので、あらかじめこのようんな設定にしています。

Using Google Storage for Developers

Fog is used to support Google Storage for Developers. Ensure you have it in your Gemfile:

gem "fog-google"
gem "google-api-client", "> 0.8.5", "< 0.9"
gem "mime-types"

設定

Railsプロジェクトにconfig/initializers/carrierwave.rbを作成する。

~/config/initializers/carrierwave.rb
CarrierWave.configure do |config|
  config.fog_provider = "fog/google"
  if Rails.env.production?
    config.fog_credentials = {
      provider: "Google",
      google_storage_access_key_id: ENV["GCS_ACCESS_KEY_ID"],
      google_storage_secret_access_key: ENV["GCS_ACCESS_KEY"],
      path_style: true,
    }

    config.fog_directory = ENV["GCS_BUCKET"]
  end
  config.asset_host = ENV["ASSET_HOST_IMG"]
  config.fog_attributes = { "Cache-Control" => "max-age=3600" }
end

ローカルのenv設定

ASSET_HOST_IMG=http://localhost:3000
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails 爆速で円グラフを実装する[5分]

はじめに

今回は'chartkick'を使って爆速で円グラフを実装します。

Gemの導入

Gemfile
gem "chartkick"
gem 'chartable'
ターミナル
bundle install

javascriptライブラリの読み込み

application.js
//= require Chart.bundle
//= require chartkick

application.jsに上記を記載。

インスタンスメソッドの定義

tweets_controller.rb
def index
配列の場合
  @chart = [['国語', 10], ['算数', 20],['理科',30]['社会',40]]
ハッシュの場合
  @chart = {"国語" => 10, "算数" => 20, "理科" => 30, "社会" => 40}
end

配列でもハッシュでも可能です!!

viewページ

tweets/index.html.haml
= pie_chart @chart

この一行を追加するだけ!!

スクリーンショット 2019-10-12 15.40.17.png

こんなかんじですね。

備考

コントローラーの変数を変えればどんなグラフも作れます。
モデルから値を取得することが多いと思うので参考に載せておきます。

def index
  @chart = Post.order('created_at ASC').group(:name)
end

こんな感じです。

おわりに

めちゃめちゃ簡単です。
ビューページに一行書けば実装できます:relaxed::relaxed:
では

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

Mac OS Catalina にアップグレードしたら bundle install でコケる

Mac OS Catalina へアップグレード後に、Gemfile で Rails のみを指定して bundle install したらコケたので備忘です。

エラー内容

$ bundle install --path vendor/bundle
Fetching gem metadata from https://rubygems.org/.............
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies...
Fetching rake 13.0.0
Installing rake 13.0.0
Fetching concurrent-ruby 1.1.5
Installing concurrent-ruby 1.1.5
Fetching i18n 1.7.0
Installing i18n 1.7.0
Fetching minitest 5.12.2
Installing minitest 5.12.2
Fetching thread_safe 0.3.6
Installing thread_safe 0.3.6
Fetching tzinfo 1.2.5
Installing tzinfo 1.2.5
Fetching zeitwerk 2.2.0
Installing zeitwerk 2.2.0
Fetching activesupport 6.0.0
Installing activesupport 6.0.0
Fetching builder 3.2.3
Installing builder 3.2.3
Fetching erubi 1.9.0
Installing erubi 1.9.0
Fetching mini_portile2 2.4.0
Installing mini_portile2 2.4.0
Fetching nokogiri 1.10.4
Installing nokogiri 1.10.4 with native extensions
Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

current directory:
./vendor/bundle/ruby/2.6.0/gems/nokogiri-1.10.4/ext/nokogiri
~/.rbenv/versions/2.6.3/bin/ruby -I 
~/.rbenv/versions/2.6.3/lib/ruby/2.6.0 -r
./siteconf20191011-4979-1ke4qzu.rb extconf.rb
checking if the C compiler accepts  -I /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/libxml2... ***
extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of necessary
libraries and/or headers.  Check the mkmf.log file for more details.  You may
need configuration options.

Provided configuration options:
    --with-opt-dir
    --without-opt-dir
    --with-opt-include
    --without-opt-include=${opt-dir}/include
    --with-opt-lib
    --without-opt-lib=${opt-dir}/lib
    --with-make-prog
    --without-make-prog
    --srcdir=.
    --curdir
    --ruby=~/.rbenv/versions/2.6.3/bin/$(RUBY_BASE_NAME)
    --help
    --clean
~/.rbenv/versions/2.6.3/lib/ruby/2.6.0/mkmf.rb:467:in `try_do': The compiler failed to generate an
executable file. (RuntimeError)
You have to install development tools first.
    from ~/.rbenv/versions/2.6.3/lib/ruby/2.6.0/mkmf.rb:585:in `block in try_compile'
    from ~/.rbenv/versions/2.6.3/lib/ruby/2.6.0/mkmf.rb:532:in `with_werror'
    from ~/.rbenv/versions/2.6.3/lib/ruby/2.6.0/mkmf.rb:585:in `try_compile'
    from extconf.rb:138:in `nokogiri_try_compile'
    from extconf.rb:162:in `block in add_cflags'
    from ~/.rbenv/versions/2.6.3/lib/ruby/2.6.0/mkmf.rb:643:in `with_cflags'
    from extconf.rb:161:in `add_cflags'
    from extconf.rb:416:in `<main>'

To see why this extension failed to compile, please check the mkmf.log which can be found here:

./vendor/bundle/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0-static/nokogiri-1.10.4/mkmf.log

extconf failed, exit code 1

Gem files will remain installed in
./vendor/bundle/ruby/2.6.0/gems/nokogiri-1.10.4 for inspection.
Results logged to
./vendor/bundle/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0-static/nokogiri-1.10.4/gem_make.out

An error occurred while installing nokogiri (1.10.4), and Bundler cannot continue.
Make sure that `gem install nokogiri -v '1.10.4' --source 'https://rubygems.org/'` succeeds before bundling.

In Gemfile:
  rails was resolved to 6.0.0, which depends on
    actioncable was resolved to 6.0.0, which depends on
      actionpack was resolved to 6.0.0, which depends on
        actionview was resolved to 6.0.0, which depends on
          rails-dom-testing was resolved to 2.0.3, which depends on
            nokogiri

$ gem install nokogiri -v '1.10.4' --source 'https://rubygems.org/'
Building native extensions. This could take a while...
ERROR:  Error installing nokogiri:
    ERROR: Failed to build gem native extension.

    current directory: ~/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/nokogiri-1.10.4/ext/nokogiri
~/.rbenv/versions/2.6.3/bin/ruby -I ~/.rbenv/versions/2.6.3/lib/ruby/2.6.0 -r ./siteconf20191011-5067-7lnwuv.rb extconf.rb
checking if the C compiler accepts  -I /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/libxml2... *** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of necessary
libraries and/or headers.  Check the mkmf.log file for more details.  You may
need configuration options.

Provided configuration options:
    --with-opt-dir
    --without-opt-dir
    --with-opt-include
    --without-opt-include=${opt-dir}/include
    --with-opt-lib
    --without-opt-lib=${opt-dir}/lib
    --with-make-prog
    --without-make-prog
    --srcdir=.
    --curdir
    --ruby=~/.rbenv/versions/2.6.3/bin/$(RUBY_BASE_NAME)
    --help
    --clean
~/.rbenv/versions/2.6.3/lib/ruby/2.6.0/mkmf.rb:467:in `try_do': The compiler failed to generate an executable file. (RuntimeError)
You have to install development tools first.
    from ~/.rbenv/versions/2.6.3/lib/ruby/2.6.0/mkmf.rb:585:in `block in try_compile'
    from ~/.rbenv/versions/2.6.3/lib/ruby/2.6.0/mkmf.rb:532:in `with_werror'
    from ~/.rbenv/versions/2.6.3/lib/ruby/2.6.0/mkmf.rb:585:in `try_compile'
    from extconf.rb:138:in `nokogiri_try_compile'
    from extconf.rb:162:in `block in add_cflags'
    from ~/.rbenv/versions/2.6.3/lib/ruby/2.6.0/mkmf.rb:643:in `with_cflags'
    from extconf.rb:161:in `add_cflags'
    from extconf.rb:416:in `<main>'

To see why this extension failed to compile, please check the mkmf.log which can be found here:

  ~/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/extensions/x86_64-darwin-18/2.6.0-static/nokogiri-1.10.4/mkmf.log

extconf failed, exit code 1

Gem files will remain installed in ~/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/nokogiri-1.10.4 for inspection.
Results logged to ~/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/extensions/x86_64-darwin-18/2.6.0-static/nokogiri-1.10.4/gem_make.out

解決策

$ xcode-select --install

でビルドできるようになりました。

参考
MacOS SierraでSassがインストール出来ない時の解決策

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

文系大学生がアプリ開発よりも機械学習の方が自学自走しやすいと考える理由について

はじめに

最近の風潮として、アプリの開発を一人で自学自走は頑張れば可能と言う考えが蔓延っていて、機械学習はなんか難しそうみたいな考え方があるように見受けられます。確かにアプリの開発を一人で自学自走する人はとてもすごいと思いますが、自分はアプリ開発を一人でやりきるのはとても難しく、また機械学習の自学自走はそれに比べると難しくないと考えています

自分の感覚で言うと
・アプリ開発自走→天才じゃないと無理ゲー
・機械学習自走→頑張れば可能
という感じです(これは自分の感覚なので、個人差はあると思います)。

その理由について述べていきたいと思います。

自分のそれぞれの経験について

まずその前に自分の(お前の経歴なんか興味ねえよって人はすっ飛ばして見て下さい)

アプリ開発

・実際に去年の3月にtechcampさんの方に通って勉強→3ヶ月ほどやるもそこで挫折
・今年の3月にpreogate→railsチュートリアルの流れで勉強をするも、railsチュートリアルで挫折

機械学習

・去年の末から初めて今のところ挫折なし
詳しくは以下を参照
https://qiita.com/HayatoYamaguchi/items/1c20595c5e6dac4530dc

こんな感じでやっています。確かに機械学習の方が本腰を入れて行なっていたという面だったり実際に一度アプリ開発でプログラミングを学んだ後だからというものもあるかもしれませんが、それを加味しても自分は機械学習の方が自学自走しやすいと考える理由をいかに述べたいと思います。

1、参照するページが多く、さらに自分がどこまで反映させたのかがわかりにくい

プログラミング初心者あるあるとして、まずは写生をしますがこの写生内容は往々にして「,」と「.」のミスなどの文字やスペルミスや、括弧やendの位置などのミスが起こりやすいです。その中でエラーメッセージを見てみると、そのメッセージが起きたサイトに言ってもエラーの根本的な原因はそのページではないなどのことが往々に発生します(例えばビューファイルのエラーなのに実際はコントローラーの中の変数の受け取りに問題があるなど)。そのためエラーの発掘が自分では困難になり、挫折しやすいです。一方機械学習の場合は最初はクラス分けを行わなければjupyter notebookなどの一つのファイルで完結します。そのため、エラーの行を見ればその行や周辺を直せば大体のエラーは解決します。

2、アプリ開発には必要な言語や概念が多すぎる

機械学習においてプログラミング面で必要なのはpythonやそれに付随する各種ライブラリ(numpy,pandas,sklearnなど)のみです。
しかし、アプリ開発を学ぼうとすると、例えばrailsだったらrailsチュートリアルを完遂して自分でポートフォリオを作ろうとすると、

・htmlcss
・ruby
・javascript
・SQL
・Git

などが必要で、データベースとの連携方法やさらにテストの実装についても理解も必要なことがあります。そのため何もやったことがない初心者が始めようとすると、「なんかごちゃごちゃしてるけど今は何をやればいいんだっけ、、、」という状態になりやすいです。

ここまで見ると機械学習の方がはるかに簡単そう、、、って思いそうなので、一応機械学習の方が難しい点も述べたいと思います。

機械学習の方が難しい点について

ある程度の数学や統計が必要である

アプリ開発にはほぼ一切数学の知識は求められません。
一方機械学習ではそうはいかず、ある程度アルゴリズムの中身を理解するとなると、大学基礎レベルの数学は必要になります。そのため数学アレルギーの人には厳しいです。

論理的な式がより求められる

機械学習ではモデルを作る前にデータの変形などをする必要があります。そのため、「この条件の時にここを変形する」などの少し論理的なコードを書く力が必要です。例えばこれは自分の昔の記事から引っ張ってきたものですが、条件を指定して削除するなどの少し複雑なものになっています。これは簡単な例ですが、論理的な式を求められることが多いです。」

Xmat = Xmat.drop(Xmat[(Xmat['TotalSF']>5) & (Xmat['SalePrice']<12.5)].index)

終わりに

これはあくまでもサンプル数1の考え方ですが、実際にこのような考え方を投稿している人がいなかったので投稿させてもらいました。何をやるかを考えている人には参考にしてもらえると嬉しいです。

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

Rails on GitHub Actions チュートリアル

Github Actions 使ってますか?
まだβ版ですし本稼働させている人は多くないと思いますが、2019年11月中旬頃にようやくキャッシュ機構が実装されるそうなので、 CircleCI 非課金勢としては非常に楽しみにしています。

Rails の CI を Github Actions で動かすにあたって、システムテスト周りで少しハマったので、導入方法をまとめてみました。

Rails プロジェクトの初期化

いつも通り rails new していきます。
今回は Rails 標準の minitest を使っていますが RSpec の場合でもほぼ変わりません。

$ mkdir rails_on_github_actions
$ bundle init
Gemfile
# frozen_string_literal: true
source "https://rubygems.org"
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
gem "rails"
$ bundle
$ bundle exec rails new .
$ bin/rails db:migrate

テストをするために user リソースを追加しておきます。

$ bin/rails g scaffold users
$ bin/rails db:migrate
$ bin/rails test

Workflow の作成

Github Actions でテストを走らせるために、 workflow を作っていきます。
scaffold によってシステムテストも作られていますが、一旦ここではブラウザを使わないテストだけ扱います。

.github/workflows/test.yml
name: Test
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest
    container:
      image: ruby:2.6.4
    steps:
    - uses: actions/checkout@v1
    - name: Set up node and yarn
      run: |
        curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
        echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
        curl -sL https://deb.nodesource.com/setup_12.x | bash -
        apt install -y nodejs yarn
    - name: Build and setup
      run: |
        bundle -j 4
        bin/rails yarn:install db:setup assets:precompile
        bin/rails test

これを push すると workflow が自動的に走ります。

System Test

Github Actions で system test を実行するため、 Chrome をインストールするスクリプトを workflow に追加しています。

.github/workflows/test.yml
name: Test
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest
    container:
      image: ruby:2.6.4
    steps:
    - uses: actions/checkout@v1
    - name: Set up YARN and NodeJS
      run: |
        curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
        echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
        curl -sL https://deb.nodesource.com/setup_12.x | bash -
        apt install -y yarn nodejs
    - name: Install chrome
      run: |
        wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | apt-key add -
        echo 'deb http://dl.google.com/linux/chrome/deb/ stable main' | tee /etc/apt/sources.list.d/google-chrome.list
        apt update -y
        apt install -y google-chrome-stable
    - name: Build
      run: |
        bundle -j 4
        bin/rails yarn:install db:setup assets:precompile
    - name: Run test
      run: |
        bin/rails test
    - name: Run system test
      run: |
        bin/rails test:system

ローカル環境なら driven_by :selenium_chrome_headless だけで Headless Chrome を使ってテストできますが、 Github Actions のような Docker 環境だと /dev/shm のサイズ割り当てが小さいため、それを使わないようにするため disable-dev-shm-usage という起動オプションを追加しています。また、 no-sandbox は Docker 環境のように root 権限で Chrome を立ち上げるのに必要なオプションです。

test/application_system_test_case.rb
require "test_helper"

class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  driven_by :selenium, using: :headless_chrome, screen_size: [1400, 1400] do |driver_options|
    driver_options.add_argument('--disable-dev-shm-usage')
    driver_options.add_argument('--no-sandbox')
  end
end

これを push すると Github Actions で system test も無事に成功します。

MySQL

実際のアプリケーションでは SQLite を使うことはほぼ無いと思うので、 Github Actions でも MySQL を使ってみます。

Gemfile に mysql2 を追加

Gemfile
gem 'mysql2'

database.yml を MySQL 用に更新します。ほぼデフォルトですが、ホスト名とパスワードを環境変数で設定できるようにしています。

config/database.yml
default: &default
  adapter: mysql2
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  charset: utf8mb4
  collation: utf8mb4_bin
  encoding: utf8mb4
  password: <%= ENV.fetch('MYSQL_ROOT_PASSWORD', '') %>
  host: <%= ENV.fetch('MYSQL_HOST', 'localhost') %>

development:
  <<: *default
  database: rails_on_github_actions_development
test:
  <<: *default
  database: rails_on_github_actions_test
production:
  <<: *default
  database: rails_on_github_actions_production

テストが正常に動くかローカルで確認しておきます。

$ bin/rails db:create
$ bin/rails test
$ bin/rails test:system

workflow の services に MySQL を追加します。root のパスワードは環境変数で設定します。
services で追加したコンテナのホスト名はサービス名と同じになるようです。

.github/workflows/test.yml
name: Test
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest
    services:
      mysql:
        image: mysql:5.7
        env:
          MYSQL_ROOT_PASSWORD: password
    container:
      image: ruby:2.6.4
      env:
        MYSQL_HOST: mysql
        MYSQL_ROOT_PASSWORD: password
    steps:
    - uses: actions/checkout@v1
    - name: Set up yarn and node
      run: |
        curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
        echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
        curl -sL https://deb.nodesource.com/setup_12.x | bash -
        apt install -y yarn nodejs
    - name: Install chrome
      run: |
        wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | apt-key add -
        echo 'deb http://dl.google.com/linux/chrome/deb/ stable main' | tee /etc/apt/sources.list.d/google-chrome.list
        apt update -y
        apt install -y google-chrome-stable
    - name: Build
      run: |
        bundle -j 4
        bin/rails yarn:install db:setup assets:precompile
    - name: Run test
      run: |
        bin/rails test
    - name: Run system test
      run: |
        bin/rails test:system

これを push すると Github Actions で MySQL を使ったテストが無事に成功します。
services と環境変数を書き換えれば postgres などの他のデータベースも使用できるかと思います。

今回作ったサンプルプロジェクトは Github に置いてますので良かった参考にしてみてください。
https://github.com/d-mato/rails_on_github_actions

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

order("id ASC") <=> order("id DESC)の切り替え Rails

備忘録及びアウトプットの練習のために、自分が学んだこと・工夫したこと・苦労した事などを書いていきたいと思います。

今回、orderメソッドを使うことにより、テーブルが呼び出したデータのソートができるということで、
画面上のスイッチをクリックするだけで、投稿内容の降順・昇順を切り替えれるようにしてみました。

1.ビューの実装

ビューファイルにスイッチ用のコードを書く

= link_to "↕️", "switch", class: "switch", method: :get

画面上の↕️をクリックしたら、"switch"に移動するようにしています。
個別にcssを適用できるように、"switch"というクラスもつけています。

2.ルーティングの設定

1でswitchというパスを指定したので、対応するルーティングを設定します

get 'switch' => 'tweets/#switch'

パス’switch'からtweetsコントローラのswitchアクションを実行するようにしています

3.コントローラの設定

2で指定したswitchアクションを作ります。

@@order = 1

def index
 if @@order == 1
   @tweets = Tweet.order("id ASC")
 else
   @tweets = Tweet.order("id DESC")
 end
end 

def switch
 @@order *= -1
 redirect_to :action => "index"
end

切り替えに使用するクラス変数@@orderを定義します。

indexアクション内で、@@orderが1かそうでないかで、order("id ASC")かorder("id DESC")かが変わるようなif文を書きます。

次に、@@orderを切り替えるためのswitchアクションを定義します。
switchアクションを実行するたび、
つまりビュー上の↕️をクリックするたびに、クラス変数@@orderに-1をかけるようにします。
redirect_to :action => "index"とすることで、スイッチを押すたびに別画面に遷移することがなくなるようにします。

これで、投稿の順番を変えるスイッチの実装が出来ました。
ルーティングの設定やクラス変数の利用など、色んな項目の復習になりました。
記事の内容は少しづつ更新していこうと思います。

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

ワンクリックでorder("id ASC") <=> order("id DESC)の切り替え

はじめに

備忘録及びアウトプットの練習のために、自分が学んだこと・工夫したこと・苦労した事などを書いていきたいと思います。

今回、orderメソッドを使うことにより、テーブルが呼び出したデータのソートができるということで、
画面上のスイッチをクリックするだけで、投稿内容の降順・昇順を切り替えれるようにしてみました。

1.ビューの実装

ビューファイルにスイッチ用のコードを書く

= link_to "↕️", "switch", class: "switch", method: :get

画面上の↕️をクリックしたら、"switch"に移動するようにしています。
個別にcssを適用できるように、"switch"というクラスもつけています。

2.ルーティングの設定

1でswitchというパスを指定したので、対応するルーティングを設定します

get 'switch' => 'tweets/#switch'

パス’switch'からtweetsコントローラのswitchアクションを実行するようにしています

3.コントローラの設定

2で指定したswitchアクションを作ります。

@@order = 1

def index
 if @@order == 1
   @tweets = Tweet.order("id ASC")
 else
   @tweets = Tweet.order("id DESC")
 end
end 

def switch
 @@order *= -1
 redirect_to :action => "index"
end

切り替えに使用するクラス変数@@orderを定義します。

indexアクション内で、@@orderが1かそうでないかで、order("id ASC")かorder("id DESC")かが変わるようなif文を書きます。

次に、@@orderを切り替えるためのswitchアクションを定義します。
switchアクションを実行するたび、
つまりビュー上の↕️をクリックするたびに、クラス変数@@orderに-1をかけるようにします。
redirect_to :action => "index"とすることで、スイッチを押すたびに別画面に遷移することがなくなるようにします。

終わりに

これで、投稿の順番を変えるスイッチの実装が出来ました。
ルーティングの設定やクラス変数の利用など、色んな項目の復習になりました。
記事の内容は少しづつ更新していこうと思います。

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

webpacker の check_yarn_integrity オプションについて調べてみた

はじめに

Rails アプリで webpacker を利用するケースが増えていると思います。Rails 5.1以上のアプリでは、rails new myapp --webpack というオプションをつける形でも webpacker をインストールできるようになっていますよね。

しかし個人的に、使い方を理解しきれていないことが多いです。そこで理解するためのとっかかりとして、config/webpacker.yml のオプションを調べてみようと思い、その中の check_yarn_integrity というオプションについて手始めに調べてみました。

何をするオプションなのか

webpacker の README を抜粋します。

By default, in development, webpacker runs a yarn integrity check to ensure that all local JavaScript packages are up-to-date.
This is similar to what bundler does currently in Rails, but for JavaScript packages.
If your system is out of date, then Rails will not initialize.
You will be asked to upgrade your local JavaScript packages by running yarn install.

こちらを意訳してみました。

  • development 環境では、ローカルの全ての JavaScript パッケージが最新であるかを yarn integrity check する
  • Rails の bundler がやっていることと似てるが、これは JavaScript 用
  • システムが古くなっていると、Rails は初期化されない
  • yarn install でローカルの JavaScript パッケージをアップグレードするか尋ねられるようになる

実施していること

実際関連しているコード部分を抜粋すると以下になります。

https://github.com/rails/webpacker/blob/8845f37bb038ad0adff813326a7d6a034b9b9a81/lib/webpacker/railtie.rb#L16-L51

config.webpacker.check_yarn_integrity = true にしておけば、rails crails s といった Rails アプリケーションを起動する際の初期化時に、 yarn check --integrity を実行し、エラーが出たら以下のようなエラーメッセージを出力してくれるようです。

========================================
  Your Yarn packages are out of date!
  Please run `yarn install` to update.
========================================


To disable this check, please add `config.webpacker.check_yarn_integrity = false`
to your Rails development config file (config/environments/development.rb).

不要な場合は config.webpacker.check_yarn_integrity = false にすれば OK のようです。
パッケージが古くなっていたらエラーで知らせてくれるということなので、パッケージを最新の状態に保つ手助けになりそうですね。

おわりに

JavaScript のパッケージ管理は複雑で理解が難しいですね。根気よく学習していきたいと思います。

参考

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

Railsのトランザクションと例外処理

Railsのトランザクションと例外処理がよく分からなかったので、調べました。

app/controllers/user_controller.rb
def create
  ActiveRecode::Base.transaction do
    # createではなく、create!にすると保存できなかったときに例外が発生します。
    @user = User.create!(user_params)

  recue => e 
  # バリデーションエラーだけ拾いたい場合は次の行
  # (ActiveRecord:RecodeInvalid => e) 
    # error処理
  end

end

recueはphpでいうとexceptionみたいな感じでした。

参考サイト

rails save! create! update!のバリデーション例外を捕捉する - Qiita

【Rails】例外処理の書き方(begin, rescue, raise,retry, ensure) - Qiita

rubyの例外についてまとめてみた - Qiita

ActiveRecord::Base.transactionで囲うタイミング - Qiita

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

Railsのhas_oneで関連テーブルの保存

has_oneで関連させて保存させる方法につまったので書いておきます。
他のやり方(createを2回使う)もあったのですが、create!しても例外をキャッチできない?感じだったので、こちらの書き方にしました。

app/controllers/users_controller.rb
def create
    @user = User.create(user_params)
    # この行のcreate_articleは状況に応じてprofile部分を読みかえて使います。
    @profile = @user.create_profile(profile_params)
end
app/models/user.rb
has_one :profile
app/models/profile.rb
belongs_to :user

参考サイト
Active Record の関連付け - Rails ガイド

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