20190215のRailsに関する記事は25件です。

Rubyでnilか空文字を判定する

nil、空文字の判定方法

nilの判定方法

check_nil.rb
str = nil

if str.nil?
  puts "strはnilです。"
end

# strはnilです。

空文字の判定方法

check_empty.rb
str = ""

if str.empty?
  puts "strは空文字です。"
end

# strは空文字です。

nilまたは空文字を判定する

nilと空文字を同時に判定する方法

is_nil_or_empty.rb
def is_nil_or_empty?(str)
  if str.nil? || str.empty?
    return true
  else
    return false
  end
end

check_array = Array.new
check_array.push("")
check_array.push(nil)

check_array.each do |str|
  if is_nil_or_empty?(str)
    puts "strはnilまたは空文字です。"
  end
end

#strはnilまたは空文字です。
#strはnilまたは空文字です。

注意点

if式の条件式は str.nil? || str.empty? とすべきです。
理由は、||演算子は左から右に順に条件式を評価し、結果が真になった時点でif式の中に入りますが、
empty?から先に評価した場合、対象の文字列がnilであればNoMethodErrorを返すためです。
empty?は文字列、配列、ハッシュのみに対応しています。nilには対応していないため、NoMethodErrorが返ってきます。
nil?は全てのオブジェクトに対応しているため、if式の条件式はnil?を左に書くべきです。

no_method_error.rb
def is_nil_or_empty?(str)
  if str.empty? || str.nil?
    return true
  else
    return false
  end
end

check_array = Array.new
check_array.push("")
check_array.push(nil)

check_array.each do |str|
  if is_nil_or_empty?(str)
    puts "strはnilまたは空文字です。"
  end
end

#undefined method `empty?' for nil:NilClass (NoMethodError)

余談

to_sメソッドを使って文字列型に変換することで、nilと空文字を判定することも出来ます。

use_to_s.rb
def is_nil_or_empty?(str)
  if str.to_s.empty?
    return true
  else
    return false
  end
end

check_array = Array.new
check_array.push("")
check_array.push(nil)

check_array.each do |str|
  if is_nil_or_empty?(str)
    puts "strはnilまたは空文字です。"
  end
end

#strはnilまたは空文字です。
#strはnilまたは空文字です。

ただし、UTF-8 でしか動かない処理に投入する場合は、上記の判定方法は使ってはいけない。
https://qiita.com/scivola/items/1f6704f81aba18df9012

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

後から名前空間を切ったらPG::UndefinedTable with namespaced Modelが発生した

API作ったが、後から名前空間を別に切り出すことになった

API完成したものの、各モジュールの名前が煩雑になりすぎたので後から名前空間を切り出すことになった。

一通り変えたけどDBでエラー吐いてる

一通り変えて、念のためテストを走らす。
ちゃんとモデル名も変更してるし、DBと合ってるな。

。。。

PG::UndefinedTable with namespaced Model

DBがエラー吐いてるやん!

原因

prefixを定義してなかったのが原因でした。
例えば以下の様に名前空間をHogeで切っていて、その中にUserというモデルを定義しており、テーブル名はhoge_usersで定義されていた場合。

module Hoge
  class User < ApplicationRecord
  end
end

DBに問い合わせるにはtable_name_prefixというメソッドを定義してやると、ちゃんと頭にhogeを付けてDBに問い合わせてくれるので、テーブルが見つからないと言われなくなります。

module Hoge
  def self.table_name_prefix
    'hoge_'
  end
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RailsでModelをOrderできない時に確認すべきこと

少しハマってしまったのでメモ。

例えばUserモデルなどのようなModelの順番を並び替えたい時にうまく並び替えられない時がある。

User.order(:name)

変わらない、、

解決策と原因

app/model配下で自分のモデルで

default_scope -> { order(created_at: :desc) }

などのように記述しているとorderメソッドを持ちいてもorderされない。

この設定を保持したままorderも使いたい時は

User.reorder(:name)

reorderを使えば並び替えることができる。

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

rails sできない時の対処法

※初学者向けです。

Ruby on Railsで Rails serverコマンドをした時にサーバーが立ち上がらなかった時に対処したことをメモ。

エラー内容

rails sとしてローカル(http://192.168.33.10:3000/ )にアクセスすると,

ActiveRecord::ConnectionNotEstablished
No connection pool with 'primary' found.

といったようなメッセージが出て上手く動いていない。

ActiveRecordとはRuby on Railsにおいてデータベースとのやりとりに使われているフレームワーク?です。
なのでこのエラーはデータベース関連のエラーであると推測されます。

Rails db:migrateで確認

ターミナルを見返すとそもそもdb:migrateが上手くいってません

rails db:migrate
とすると
Gem::LoadError: can't activate sqlite3 (~> 1.3.6), already activated sqlite3-1.4.0. Make sure all dependencies are added to Gemfile.
というエラー。

内容はGemのロードエラー。具体的にはsqlite3 (~> 1.3.6)がアクティベートできないけどsqlite3 (1.4.0)はアクティベートされてるよ、というもの。

おそらくRailsが最新版のsqlite3(1.4.0)を受け付けてないと推測。
ちなみに(~>1.3.6)とは '1.3.6以上1.4.0未満'という意味。

対処法:Gemfileの更新

Rails new したフォルダにあるGemfileの内容を変えましょう。
(初学者の私にとっては「???」状態でしたが。。。)

Gemfile
の中にある
gem 'sqlite3'
という部分を
gem 'sqlite3', '~> 1.3.6'
に変更しましょう。

これで
bundle update
bundle install
というコマンドをターミナル上で実行し、
rails s

晴れてRuby on Railsのデフォルト画面が現れるはず。

初学者でいきなり詰まったところだったのでメモを残します。

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

【Rails】Facebookでユーザー認証する

はじめに

FacebookでOmniAuthでユーザー認証しましょう。かっこいいからです。
似たような記事はたくさんありますが、Facebookの仕様変更などで、記事単体では実装が完了しないものばかりだったので改めて書くことにしました。

まずdeviseのみで認証機能を実装

以下のサイトを参考につくります。
【Railsのアプリ開発】初心者でもわかる!deviseでログイン機能を実装する使い方

暇があれば上の内容も自分でしっかり解説して書きたいところですが、「暇があれば」などと言うのは大体やらない兆しです。期待しないでください。

ユーザー一覧ページの作成

認証ページだけ作っても寂しいので、ユーザー一覧ページを作っておくと良いでしょう。

$ rails g controller users

これで作成されるファイルのうち、コントローラーとビューを以下のように変更して、ユーザー一覧ページを作成します。

app/controllers/users_controller.rb
class UsersController < ApplicationController
  before_action :authenticate_user!
  def index
    @users = User.all
  end
end
app/views/users/index.html.erb
<% @users.each do |u| %>
  <div class="user_field">
    <%= link_to u.email, destroy_user_session_path, method: :delete %>
  </div>
<% end %>

一覧表示されたユーザーのメールアドレスをクリックするとサインアウトするようにしました。

最後に、このusers#indexアクションへのルーティングを設定しておきます。

config/routes.rb
Rails.application.routes.draw do
  # 省略

  resources :users, only: [:index]
  root "users#index"
end

サインイン後にはルートパスにリダイレクトされるので、簡単にサインインできたかどうかの確認ができるように、"users#index"をルートパスに設定しました。
Facebookでのサインインに成功すれば、ユーザー一覧のページに遷移し、そこに自分がFacebookに登録したメールアドレスが表示されるはずです。

omniauthを実装

再び以下のサイトを参考に。
【開発メモ】RailsアプリでFacebookログインの認証機能を実装させる方法

暇があれば。

Facebookの変更に対応

2019/2/15時点で、これだけではまだ足りません。

Facebook側の仕様変更で、以下が必須になりました。

  • アプリケーションにhttpsでアクセスするように設定する
  • プライバシーポリシーを載せたURLを指定する

順にやっていきます。

アプリケーションにhttpsでアクセスできるように設定する

httpsでアクセスできるようにするには、アプリケーションサーバーがSSL証明書というものを持っていないといけません。SSL証明書にはグレードがあり、グレードの高いものほどサイトの安全性を保証できるのですが、それらは発行するのに認証局にたくさんお金を払わなければなりません。
しかしグレードの低いもので良ければ、自分で作成することができます。自分で作成といってもよくわからないので、SSL証明書の作成はmkcertを使って楽にやってしまいましょう。

また、RailsのアプリケーションサーバーとしてPumaを使えば、SSL証明書に関するなんやかんやをいい感じにやってくれます。
PumaはRails5では標準のアプリケーションサーバーなので、これに関しては特にややこしいことはありませんが、何かこだわりや制約があって他のアプリケーションサーバーを使う場合はブラウザバックです。

mkcertでSSL証明書を作成

公式GitHub: FiloSottile/mkcert

READMEを読んで、mkcertをインストールします。
僕のパソコンはmacなので、Homebrewを使います。

$ brew install mkcert
$ mkcert -install

自分のRailsアプリケーションの適当な場所にSSL証明書を置くディレクトリを作り、SSL証明書を作成します。

$ mkdir config/certs && cd config/certs
$ mkcert localhost

これで、config/certsディレクトリ内にlocalhost.pemlocalhost-key.pemという名前のSSL証明書ができます。
以下のように配置されていればOKです。

スクリーンショット 2019-02-15 22.12.19.png

.gitignoreでSSL証明書を無視

SSL証明書はgitで管理したくないので、.gitignoreに追記します。

(省略)

/config/certs/*

pumaの設定ファイルでSSL証明書を指定する

config/puma.rbの12行目くらいに、httpでのpumaのサーバーを3000番のポートで起動するという設定の一行があります(port ENV.fetch("PORT") { 3000 })。
httpsでアクセスできるようにしたい今、これはもはや必要ないのでコメントアウトし、以下のように3000番の座を乗っ取りましょう。

config/puma.rb
(省略)

# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
#
# port        ENV.fetch("PORT") { 3000 }

ssl_bind "0.0.0.0", "3000", {
  cert: "config/certs/localhost.pem",
  key:  "config/certs/localhost-key.pem"
}

アクセスしてみる

あとはいつもどおりサーバーを立ち上げてhttps://localhost:3000にアクセスするだけです(httpではなくhttpsです!)。

$ rails server

プライバシーポリシーを載せたURLを指定する

プライバシーポリシーとは、収集した情報をこれこれこういう目的で使いますよという旨が書かれた文書のことです。とりあえず開発時にはアクセスできるサイトであればなんでもよいです。アプリケーションを公開する段階になったら、忘れずに自分のアプリケーション内にプライバシーポリシーを載せたページを作り、それを指定しましょう。

facebookの開発者用管理ページに以下のようにURLを設定しておきます。

スクリーンショット 2019-02-15 21.08.24.png

プライバシーポリシーのURLの設定は以上です!簡単!

確認

https://localhost:3000にアクセスすると、サインインのページに遷移し、そこに「Sign in with Facebook」というリンクが表示されているはずです。これをクリックするとFacebookの認証画面が現れて…。自分のメールアドレスが表示されたでしょうか?

できたはずです。きっと…。

[+α] deviseで使われるビューファイルを確認する

deviseで作成される諸々のユーザー認証のページは、そのままでは見ることもできません。
しかし以下の設定とコマンドで、ユーザー認証まわりのビューファイルを確認し、カスタマイズすることができるようになります。

config/initializers/devise.rbの238行目を以下のように書き換えます。

config/initializers/devise.rb
config.scoped_views = true

config/initializersディレクトリの中に入っているファイルを変更したので、もしサーバーを立ち上げていたらサーバーを再起動してください。このディレクトリの中のファイルはサーバー立ち上げ時にだけ読み込まれるからです。

続いて、ビューファイルを作成します。

$ rails g devise:views users

こうしてできたビューファイルのうち、app/views/users/shared/_links.html.erbを見てみると、下の方に以下のような記述があります。これが Sign in with Facebookなどと表示してくれる部分です。

app/views/users/shared/_links.html.erb
<%- if devise_mapping.omniauthable? %>
  <%- resource_class.omniauth_providers.each do |provider| %>
    <%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider) %><br />
  <% end %>
<% end %>

deviseで作成したモデルにomniauthableというオプションがついていれば、この部分が表示され、サインインボタンが表示されるようになっています。今回はデザインまではいじりませんが、かっこいいユーザー認証ページを作成したければここを変えればいいということを覚えておいてください。

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

RailsでFacebookログインする

はじめに

FacebookでOmniAuthでユーザー認証しましょう。かっこいいからです。
似たような記事はたくさんありますが、Facebookの仕様変更などで、記事単体では実装が完了しないものばかりだったので改めて書くことにしました。

まずdeviseのみで認証機能を実装

以下のサイトを参考につくります。
【Railsのアプリ開発】初心者でもわかる!deviseでログイン機能を実装する使い方

暇があれば上の内容も自分でしっかり解説して書きたいところですが、「暇があれば」などと言うのは大体やらない兆しです。期待しないでください。

ユーザー一覧ページの作成

認証ページだけ作っても寂しいので、ユーザー一覧ページを作っておくと良いでしょう。

$ rails g controller users

これで作成されるファイルのうち、コントローラーとビューを以下のように変更して、ユーザー一覧ページを作成します。

app/controllers/users_controller.rb
class UsersController < ApplicationController
  before_action :authenticate_user!
  def index
    @users = User.all
  end
end
app/views/users/index.html.erb
<% @users.each do |u| %>
  <div class="user_field">
    <%= link_to u.email, destroy_user_session_path, method: :delete %>
  </div>
<% end %>

一覧表示されたユーザーのメールアドレスをクリックするとサインアウトするようにしました。

最後に、このusers#indexアクションへのルーティングを設定しておきます。

config/routes.rb
Rails.application.routes.draw do
  # 省略

  resources :users, only: [:index]
  root "users#index"
end

サインイン後にはルートパスにリダイレクトされるので、簡単にサインインできたかどうかの確認ができるように、"users#index"をルートパスに設定しました。
Facebookでのサインインに成功すれば、ユーザー一覧のページに遷移し、そこに自分がFacebookに登録したメールアドレスが表示されるはずです。

omniauthを実装

再び以下のサイトを参考に。
【開発メモ】RailsアプリでFacebookログインの認証機能を実装させる方法

暇があれば。

Facebookの変更に対応

2019/2/15時点で、これだけではまだ足りません。

Facebook側の仕様変更で、以下が必須になりました。

  • アプリケーションにhttpsでアクセスするように設定する
  • プライバシーポリシーを載せたURLを指定する

順にやっていきます。

アプリケーションにhttpsでアクセスできるように設定する

httpsでアクセスできるようにするには、アプリケーションサーバーがSSL証明書というものを持っていないといけません。SSL証明書にはグレードがあり、グレードの高いものほどサイトの安全性を保証できるのですが、それらは発行するのに認証局にたくさんお金を払わなければなりません。
しかしグレードの低いもので良ければ、自分で作成することができます。自分で作成といってもよくわからないので、SSL証明書の作成はmkcertを使って楽にやってしまいましょう。

また、RailsのアプリケーションサーバーとしてPumaを使えば、SSL証明書に関するなんやかんやをいい感じにやってくれます。
PumaはRails5では標準のアプリケーションサーバーなので、これに関しては特にややこしいことはありませんが、何かこだわりや制約があって他のアプリケーションサーバーを使う場合はブラウザバックです。

mkcertでSSL証明書を作成

公式GitHub: FiloSottile/mkcert

READMEを読んで、mkcertをインストールします。
僕のパソコンはmacなので、Homebrewを使います。

$ brew install mkcert
$ mkcert -install

自分のRailsアプリケーションの適当な場所にSSL証明書を置くディレクトリを作り、SSL証明書を作成します。

$ mkdir config/certs && cd config/certs
$ mkcert localhost

これで、config/certsディレクトリ内にlocalhost.pemlocalhost-key.pemという名前のSSL証明書ができます。
以下のように配置されていればOKです。

スクリーンショット 2019-02-15 22.12.19.png

.gitignoreでSSL証明書を無視

SSL証明書はgitで管理したくないので、.gitignoreに追記します。

(省略)

/config/certs/*

pumaの設定ファイルでSSL証明書を指定する

config/puma.rbの12行目くらいに、httpでのpumaのサーバーを3000番のポートで起動するという設定の一行があります(port ENV.fetch("PORT") { 3000 })。
httpsでアクセスできるようにしたい今、これはもはや必要ないのでコメントアウトし、以下のように3000番の座を乗っ取りましょう。

config/puma.rb
(省略)

# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
#
# port        ENV.fetch("PORT") { 3000 }

ssl_bind "0.0.0.0", "3000", {
  cert: "config/certs/localhost.pem",
  key:  "config/certs/localhost-key.pem"
}

アクセスしてみる

あとはいつもどおりサーバーを立ち上げてhttps://localhost:3000にアクセスするだけです(httpではなくhttpsです!)。

$ rails server

プライバシーポリシーを載せたURLを指定する

プライバシーポリシーとは、収集した情報をこれこれこういう目的で使いますよという旨が書かれた文書のことです。とりあえず開発時にはアクセスできるサイトであればなんでもよいです。アプリケーションを公開する段階になったら、忘れずに自分のアプリケーション内にプライバシーポリシーを載せたページを作り、それを指定しましょう。

facebookの開発者用管理ページに以下のようにURLを設定しておきます。

スクリーンショット 2019-02-15 21.08.24.png

プライバシーポリシーのURLの設定は以上です!簡単!

確認

https://localhost:3000にアクセスすると、サインインのページに遷移し、そこに「Sign in with Facebook」というリンクが表示されているはずです。これをクリックするとFacebookの認証画面が現れて…。自分のメールアドレスが表示されたでしょうか?

できたはずです。きっと…。

[+α] deviseで使われるビューファイルを確認する

deviseで作成される諸々のユーザー認証のページは、そのままでは見ることもできません。
しかし以下の設定とコマンドで、ユーザー認証まわりのビューファイルを確認し、カスタマイズすることができるようになります。

config/initializers/devise.rbの238行目を以下のように書き換えます。

config/initializers/devise.rb
config.scoped_views = true

config/initializersディレクトリの中に入っているファイルを変更したので、もしサーバーを立ち上げていたらサーバーを再起動してください。このディレクトリの中のファイルはサーバー立ち上げ時にだけ読み込まれるからです。

続いて、ビューファイルを作成します。

$ rails g devise:views users

こうしてできたビューファイルのうち、app/views/users/shared/_links.html.erbを見てみると、下の方に以下のような記述があります。これが Sign in with Facebookなどと表示してくれる部分です。

app/views/users/shared/_links.html.erb
<%- if devise_mapping.omniauthable? %>
  <%- resource_class.omniauth_providers.each do |provider| %>
    <%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider) %><br />
  <% end %>
<% end %>

deviseで作成したモデルにomniauthableというオプションがついていれば、この部分が表示され、サインインボタンが表示されるようになっています。今回はデザインまではいじりませんが、かっこいいユーザー認証ページを作成したければここを変えればいいということを覚えておいてください。

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

rails generate controllerで同時にいくつ作れるのか気になった話

オレ generate スキ

コマンド一つで必要なものがザッと作れるgenerate、ほんと好き。
生産性が高いってこういうことなんですかね?と入門したばかりなのに楽しくて仕方がない。
そこでふと気になったんですよ、「アレっていくつくらい同時に作れるのかなぁ?」って。
そんな小学生のような疑問を大切にしていきたい今日このごろ。

環境

  • cloud9
  • Rails 5.2.2

とりあえずアルファベットを1巡してみよう

ググって1ページ目にそれっぽいものがなかったのでとりあえずアルファベット並べてみました。

$ rails generate controller sample a b c d e f g h i j k l m n o p q r s t u v w x y z

こんなに同時に作るプロジェクトあるのかなぁ…。
知的好奇心を満たしたいだけなので疑問をスポイル。
流石に多すぎって怒られるかなぁ?と思いながらEnterぽちー!

$ rails generate controller sample a b c d e f g h i j k l m n o p q r s t u v w x y z
Running via Spring preloader in process 4430
      create  app/controllers/sample_controller.rb
       route  get 'sample/a'
get 'sample/b'
get 'sample/c'
get 'sample/d'
get 'sample/e'
get 'sample/f'
get 'sample/g'
  :
 (中略)
  :
      invoke    coffee
      create      app/assets/javascripts/sample.coffee
      invoke    scss
      create      app/assets/stylesheets/sample.scss

マ?
通っちゃったよ。
じゃあもう1巡、つまりアルファベット52文字なら?

$ rails generate controller sample a b c d e f g h i j k l m n o p q r s t u v w x y z aa bb cc dd ee ff gg hh ii jj kk ll mm nn oo pp qq rr ss tt uu vv ww xx yy zz                             
Running via Spring preloader in process 4570
      create  app/controllers/sample_controller.rb
       route  get 'sample/a'
get 'sample/b'
get 'sample/c'
get 'sample/d'
get 'sample/e'
get 'sample/f'
get 'sample/g'
  :
 (中略)
  :
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/sample.coffee
      invoke    scss
      create      app/assets/stylesheets/sample.scss

ウッソだろお前!
ということで、そこそこたくさんを一回でgenerateできそうです。

おまけ:最後の一撃はせつない

消す時はgenerateをdestroyに書き換えて実行。

$ rails destroy controller sample a b c d e f g h i j k l m n o p q r s t u v w x y z aa bb cc dd ee ff gg hh ii jj kk ll mm nn oo pp qq rr ss tt uu vv ww xx yy zz

まぁブランチ切っといて、ブランチごと捨てる方が楽だけどさ

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

get post アクション

1. get postの意味

get   「取得」 情報をデータベースから取ってくる。 
post  「作成」/保存情報をデータベースに置いてくる

2. アクションの意味

index  一覧(←索引)
show  詳細(←見せる←明らかにする)

create 保存(newとややこしい)
new  新規作成

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

比較演算子

比較演算子


a == b # a と b が等しい
a != b # a と b が等しくない
a < b # a が b よりも小さい
a > b # a が b よりも大きい
a <= b # a が b 以下である
a >= b # a が b 以上である

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

変数展開

変数展開

変数展開とは...
変数を代入している値に置き換えて、文字列の中に含めること

name = '佐藤'
puts "こんにちは#{name}さん"

ダブルクォーテーション「""」じゃないと変数展開できないので注意!

数値と文字列を足し算で連結することはできないが、
変数展開を使えば、数値の入った変数と文字列を連結することができる。

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

Docker Rails pry-byebug で pry 入力できなかった時

*メモ書きです。

docker-compose.yml
  web:
  ...

  + tty: true
  + stdin_open: true

上記の tty: true stdin_opne: true を追加することで解決しました。

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

rails で セレクトタグの生成

Railsでセレクトタグの生成

今回、ECサイトの買い物かご的な物を再現してみようと思い作成していた所、少々分かりづらかった所があったので記事にしてみました。

cart.PNG

select_tag

Rails ドキュメント
http://railsdoc.com/references/select

ここに説明が載っているものの、僕には理解が難しかった・・・

select_tagの使い方

ドキュメントには、

select_tag(要素名, タグを表す文字列 [, オプション])

の様に書かれていました。

色々試してみた所、要素名の所で指定した値がname属性として送信され、

params[:要素名]

で値を取得できるようです。

値の作成

Railsドキュメント
http://railsdoc.com/references/options_for_select

セレクトタグの値を作成する場合は、options_for_selectを使います。

  <%= select_tag("num", options_for_select((タグの配列 or ハッシュ)) %>

とすることで値を設定できます。

今回は1から100までの値を設定したので、

  <%= select_tag("num", options_for_select((1..100)) %>

この様に記述しました。

selected設定

今回は、買い物かごの作成ということだったので、購入予定の個数設定をしておかなくてはならないためselected指定をする必要がありました。

これは、options_for_selectの:selectedオプションを使用することで可能になります。

そのため、options_for_selectの第2引数に

  <%= select_tag("num", options_for_select((1..100) ,
      :selected => item.item_number) ) %>

とすることで、購入予定数をselected指定しました。

上記の様に、

:selected => 値

とすることで、指定ができます。

感想

ドキュメントを読むのは難しい・・・

言葉の定義など、理解があいまいな部分が多く、
用語を明確に覚える必要があると感じた。

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

【初学者】Ruby on Rails にて rails s できず Usage: rails new APP_PATH [options]と表示される場合 のメモ

*メモ書きです。

*ディレクトリが違うため下記コードが出力される

渡しの場合は cd で 1階層下に行くことで解决しました。
ディレクトリをもう1度見直すことで解決できるかもしれません。

comandline.tools
Usage:
  rails new APP_PATH [options]

Options:
      [--skip-namespace], [--no-skip-namespace]            # Skip namespace (affects only isolated applications)
  -r, [--ruby=PATH]                                        # Path to the Ruby binary of your choice
                                                           # Default: /Users/mu/.rbenv/versions/2.5.1/bin/ruby
  -m, [--template=TEMPLATE]                                # Path to some application template (can be a filesystem path or URL)
  -d, [--database=DATABASE]                                # Preconfigure for selected database (options: mysql/postgresql/sqlite3/oracle/frontbase/ibm_db/sqlserver/jdbcmysql/jdbcsqlite3/jdbcpostgresql/jdbc)
                                                           # Default: sqlite3
      [--skip-yarn], [--no-skip-yarn]                      # Don't use Yarn for managing JavaScript dependencies
      [--skip-gemfile], [--no-skip-gemfile]                # Don't create a Gemfile
  -G, [--skip-git], [--no-skip-git]                        # Skip .gitignore file
      [--skip-keeps], [--no-skip-keeps]                    # Skip source control .keep files
  -M, [--skip-action-mailer], [--no-skip-action-mailer]    # Skip Action Mailer files
  -O, [--skip-active-record], [--no-skip-active-record]    # Skip Active Record files
      [--skip-active-storage], [--no-skip-active-storage]  # Skip Active Storage files
  -P, [--skip-puma], [--no-skip-puma]                      # Skip Puma related files
  -C, [--skip-action-cable], [--no-skip-action-cable]      # Skip Action Cable files
  -S, [--skip-sprockets], [--no-skip-sprockets]            # Skip Sprockets files
      [--skip-spring], [--no-skip-spring]                  # Don't install Spring application preloader
      [--skip-listen], [--no-skip-listen]                  # Don't generate configuration that depends on the listen gem
      [--skip-coffee], [--no-skip-coffee]                  # Don't use CoffeeScript
  -J, [--skip-javascript], [--no-skip-javascript]          # Skip JavaScript files
      [--skip-turbolinks], [--no-skip-turbolinks]          # Skip turbolinks gem
  -T, [--skip-test], [--no-skip-test]                      # Skip test files
      [--skip-system-test], [--no-skip-system-test]        # Skip system test files
      [--skip-bootsnap], [--no-skip-bootsnap]              # Skip bootsnap gem
      [--dev], [--no-dev]                                  # Setup the application with Gemfile pointing to your Rails checkout
      [--edge], [--no-edge]                                # Setup the application with Gemfile pointing to Rails repository
      [--rc=RC]                                            # Path to file containing extra configuration options for rails command
      [--no-rc], [--no-no-rc]                              # Skip loading of extra configuration options from .railsrc file
      [--api], [--no-api]                                  # Preconfigure smaller stack for API only apps
  -B, [--skip-bundle], [--no-skip-bundle]                  # Don't run bundle install
      [--webpack=WEBPACK]                                  # Preconfigure for app-like JavaScript with Webpack (options: react/vue/angular/elm/stimulus)

Runtime options:
  -f, [--force]                    # Overwrite files that already exist
  -p, [--pretend], [--no-pretend]  # Run but do not make any changes
  -q, [--quiet], [--no-quiet]      # Suppress status output
  -s, [--skip], [--no-skip]        # Skip files that already exist

Rails options:
  -h, [--help], [--no-help]        # Show this help message and quit
  -v, [--version], [--no-version]  # Show Rails version number and quit

Description:
    The 'rails new' command creates a new Rails application with a default
    directory structure and configuration at the path you specify.

    You can specify extra command-line arguments to be used every time
    'rails new' runs in the .railsrc configuration file in your home directory.

    Note that the arguments specified in the .railsrc file don't affect the
    defaults values shown above in this help message.

Example:
    rails new ~/Code/Ruby/weblog

    This generates a skeletal Rails installation in ~/Code/Ruby/weblog.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rails rename column

rename_column :table_name, :before_column_name, :after_column_name

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

rails rename column migration

type command in shell

$ rails g migration rename_before_column_name_to_after_column_name_on_table_name

fix code

class RenameBeforeColumnNameToAfterColumnNameOnTableName < ActiveRecord::Migration[5.2]
  def change
    rename_column :table_name, :before_column_name, :after_column_name
  end
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

reCAPTCHAを中央寄せしたい

初期では左寄せされる

Railsで実装したので
= recaptcha_tagsをビューに記述するだけで表示されました。
しかし左寄せで表示されてしまい、不恰好なので中央寄せさせたいです。

スクリーンショット 2019-02-15 14.05.33.png

CSSで中央寄せにする

検証してみるとクラス g-recaptchaを持ったdivタグの下に配置されていることがわかったので、そのクラスにCSSを当てる。
下記のコードで中央寄せすることが可能。
中央寄せとは関係ないがパスワードの入力フォームと近かった為、margin-topで離している。

reCAPTCHA.css
.g-recaptcha {
  margin-right:auto;
  margin-left:auto;
  text-align: center;
  width:300px;
  margin-top:20px;
}

最終的に

このように中央寄せすることができた!
スクリーンショット 2019-02-15 14.16.46.png

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

メモ:RailsのActionMailerでテキストメールを自動生成する

この記事は

  • 技術メモです

やりたかったこと

  • RailsのActionMailerを使ってメールを送信しているサービスがあります
  • そこですでにHTMLメールを送信しているのですが、一部メーラでHTMLメールは受信できないという話がありました
  • そこで全メールについてTEXTとのマルチパートメールで送出する必要がでてきました

普通にやると

  • 既存のHTMLメールのテンプレートに加えて、TEXTメールのビューテンプレートも作成するとマルチパートで送信することが可能です
  • しかし、TEXT/HTMLのビューテンプレートを両方メンテナンスしていくのは結構きついです

actionmailer-text gem

  • actionmailer-textを使うと、自動的にHTMLメールからタグを除去していい感じにテキストメールを送出してくれます
  • こんな感じっぽく動きます
    • <br/>は改行に置換
    • リンクはリンク文字列(URL)みたいな感じに置換
    • そのほかの普通のタグは除去
    • このへんを見るとなんか一生懸命正規表現で置換してるっぽい

使い方

  • gemを入れます
Gemfile
gem 'actionmailer-text'
  • application_mailerに所定のモジュールをincludeします
application_mailer.rb
class ApplicationMailer < ActionMailer::Base
  # 追記
  include ActionMailer::Text
end
  • ちなみにdeviseを使っている場合、deviseから送られるメールはApplicationMailerを継承していないことがあるので、devise設定ファイルで指定します
config/initializers/devise.rb
config.parent_mailer = 'ApplicationMailer'
  • 以上で普通に全てのメールがTEXTとHTMLのマルチパートメールになって送信されます

終わりに

  • ガラケーとかのテキストしか受けられないメーラ爆発しろ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[rails] モデルに存在しない値をjsonフォーマットで返却する方法

概要

モデルに存在しない値をjsonフォーマットで返却したい場合どうするのか。
答えは、アクション関数のrender jsonする際にto_jsonを使いmethodsのオプションにモデルの任意の関数を指定してあげればいいです。

言葉では伝えづらいので、、実際にやってみた結果を御覧ください。

改修前

Reportのスキーマがどうなっているかは説明を省きますが、
コントローラーで以下のようなアクションの実装をしていて、reports.jsonにGETリクエストするとモデルの内容をjsonで出力してくれます。

reports_controller.rb
  # GET /reports
  # GET /reports.json
  def index
    @reports = Report.all

    respond_to do |format|
      format.html { render :index }
      format.json { render json: @reports, status: :ok }
    end
  end
reports.json
[
  {
    "id": 4,
    "text": "hoge",
    "created_at": "2019-02-13T11:16:23.000+09:00",
    "updated_at": "2019-02-13T11:16:23.000+09:00",
    "deleted_at": null
  }
]

改修後

では、実際にどう実装するかをやってみます。
まずは、モデル側に任意の関数を実装してください。以下の関数ではcreated_atの値を日本人がわかりやすい日時の文字列フォーマットに変換しています。

report.rb
class Report < ApplicationRecord
  # 省略

  def created_at_formatted
    self.created_at.strftime("%Y年%-m月%-d日")
  end
end

次に、アクションの実装のrender jsonしてあげている箇所を以下のように変更してあげます。

reports_controller.rb
  # GET /reports
  # GET /reports.json
  def index
    @reports = Report.all

    respond_to do |format|
      format.html { render :index }
      format.json { render json: @reports.to_json(methods: :created_at_formatted), status: :ok }
    end
  end
-      format.json { render json: @reports, status: :ok }
+      format.json { render json: @reports.to_json(methods: :created_at_formatted), status: :ok }

そうしますと以下のようにモデルには存在しない値をjsonで返却することができました。

reports.json
[
  {
    "id": 4,
    "text": "hoge",
    "created_at": "2019-02-13T11:16:23.000+09:00",
    "updated_at": "2019-02-13T11:16:23.000+09:00",
    "deleted_at": null,
    "created_at_formatted": "2019年2月13日"
  }
]

以上になります。

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

GAEでRailsのstaging環境を作った

概要

今関わっているプロジェクト(Rails)でGAE上にstaging環境を作ることになったが、
設定値周りでちょっと苦労したので構築にあたっての設定内容を残す。

Staging環境

Rails詳しくないのでstaging環境の作り方を調べたところ、大きく

  • Rails.envにstagingを追加(enviroments/staging.rb)
  • Rails.envはproductionで、設定値を切り替える

の2通りがある模様だが、1個目のRails.env追加の方は色々問題もあって(理由は備考に)、今回は2個目を採用。

2個目の方式の場合、production/stagingといった環境がRails.envとは独立した概念になるため、envという単語がどちらを指してるのが分かりづらい。

ここではproduction/stagingなどで区別するenvをproject_envと呼ぶことにする。

project_envの設定値管理

設定ファイルの管理方法自体は大きく3通りあった。

  1. 設定ファイル
  2. 環境変数
  3. credentials (Rails5.2より導入)

色々検討した結果「1. 設定ファイル」の方式を採用することに(長くなったので検討は備考に)

今回はgemのrailsconfig/configを使用する。

config/以下にsettings.ymlというファイルを用意するが、大元のsettings.yml以外にも

config/settings/development.yml
config/settings/production.yml

といった環境ごとのファイルが用意できる他、

config/settings.local.yml
config/settings/development.local.yml
config/settings/production.local.yml

などローカル(というかファイルが置いてあるプロジェクト)用の設定ファイルも読み込んでくれる。

ちなみに設定は以下の順序で、下の方が優先になるとのこと。

config/settings.yml
config/settings/#{environment}.yml
config/environments/#{environment}.yml

config/settings.local.yml
config/settings/#{environment}.local.yml
config/environments/#{environment}.local.yml

基本的には、

  • 全体やenvごとに固定したい値は、localじゃないファイルに記載してcommit
  • その環境(project_env)でのみ使いたい値は、localファイルに記載しつつ.gitignoreに追加してcommitはしない
  • 秘密情報はlocalファイルに加える。env共通だったとしてもproject_envごとに管理する

といった方針で管理。

3番目の秘密情報については、運用上あまりenv共通になることはなかったのでproject_envごとで問題はなかった。

デプロイ

GAEを使っているのでCloudBuildでデプロイを行う。

以下のようなcloudbuild.yamlを用意しつつ、CloudBuild側でトリガー設定を行う。

steps:
  - name: 'gcr.io/cloud-builders/gsutil'
    args: ['cp', 'gs://${_BUCKET}/config/*', './config/']
  - name: 'gcr.io/cloud-builders/gcloud'
    args: ['app', 'deploy', 'app-${_PROJECT_ENV}.yaml']
substitutions:
  _PROJECT_ENV: dev

1. gsutil

  - name: 'gcr.io/cloud-builders/gsutil'
    args: ['cp', 'gs://${_BUCKET}/config/*', './config/']

CloudBuildのトリガー設定を行うと、cloudbuild.yaml上では紐づけたbranchのコードがすでに展開された状態になっているので、その状態に対しての操作を書くことができる。

このため、GCSにsettings.local.ymlを配置しておきgsutilで配布することで、コミットしていないファイルもrails側に配布することができる。

今回は他にも配布したいファイルがあったため、それらまるごとrailsのconfig以下に配布するようにした。

配置したGCSのバケット名は、トリガーの代入変数の_BUCKETで設定しておく。

また、cloudbuildのサービスアカウントがgcsに触れる必要があるので、IAMでストレージのオブジェクト閲覧権限も設定しておく。

2. deploy

  - name: 'gcr.io/cloud-builders/gcloud'
    args: ['app', 'deploy', 'app-${_PROJECT_ENV}.yaml']

gsutilでrails環境ができたので、gcloud app deployコマンドを実行。

共通のapp.yaml一個で複数にデプロイしてもよいが、運用上はproject_envごとにapp.yamlを分けて作成した方が便利なことが多そう。
開発はしょぼいインスタンスで、本番はちゃんとしたスペックのもの、とかを切り分ける場合にはapp.yamlでの切り分けが必要になってくる。

上記のコマンドの場合、app-stg.yamlなどのファイルを用意しておき、トリガーの代入変数にも_PROJECT_ENVを設定しておく。

あとはトリガー設定がうまく行っていれば、branchをpushして

まとめ

ということで、GAEでRailsのstaging環境を作成したときのメモでした。

基本的には

  • settings.local.ymlを用意
  • GCSに配置
  • CloudBuildでgcsからファイルを持ってくる処理を追加

でうまくいく気がする。

あとは秘密情報に関してはバケット権限を考慮しつつ、どうしても回避が難しければCloud KMSを使っていく感じか。

いつも文章多めなので、図とかもっと増やしたい....

備考1: Rails.env追加の問題点

Railsでenvを追加してみると、自分でロジックを追加するのは簡単だが、Rails自体が他のenvを想定していないことが多い。

一例を上げるとsecret_key_base取得は以下のような実装になっていて、development/testとそれ以外で挙動が分かれていたりする。

def secret_key_base
  if Rails.env.test? || Rails.env.development?
    secrets.secret_key_base || Digest::MD5.hexdigest(self.class.name)
  else
    validate_secret_key_base(
      ENV["SECRET_KEY_BASE"] || credentials.secret_key_base || secrets.secret_key_base
    )
  end
end

database.yml等はenvごとに接続設定を管理するので、同じdevelopmentでもローカル用と共有用で項目を分けるため、localというenvを追加した。

上記のsecret_key_baseのようなRails本体で分岐しているところは流石にどうしようもなく、都度env対応をする羽目に。

stagingを作る目的は本番相当の環境での動作確認なので、ロジックが違う事態は避けたい。
ということでRails.envはいじらずに、別途project_envの概念を持ち込むことに。

備考2. 設定ファイルの管理方式検討

1. 設定ファイル

設定値をyml等で管理して、コード側でそれを呼び出す簡易的な変数管理

railsconfig/configというgemが有名のようで、

  • Settings.hoge.fugaなど階層データを扱え、呼び出しもdotつなぎで楽
  • settings.yml他、envごとのymlやsettings.local.ymlというローカルのみ有効な設定ファイルも持てる

など色々利点があった。

今回はconfigを採用し、project_envごとにsettings.local.ymlを用意してGCS等に配置し、コードと一緒に配ることで反映する方式を採用することに。

2. 環境変数

システム環境変数(ENV)を使う方式
gemとしてはdotenvが有名とのこと

ただ、設定を階層構造にしづらく(_でつなぐとかで無理やりやる?)、設定管理が大変になりそうなので断念。

3. credentials

rails5.2から導入された機能。
1や2の方式では、接続用の鍵情報やDBパスワードなどの秘密情報をどう管理するかが懸念だが、これは設定ファイルを暗号化することでコミットできるので、git上などで履歴管理ができる。

公式はproductionにしか使えないが、Rails.envごとに分けられるrails-env-credentialsというgemも登場していて、Rails.envごとに設定を分けられる。

ただ今回はproject_envが違う設定ファイルを用意したいので、Rails.envが同じ場合にファイル分けが別途必要になってしまう。

結局暗号化されたファイルをgit上で管理できる利点よりも、暗号鍵や設定ファイルの用意や編集の手間がかかる欠点が大きいと判断し断念。

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

Rails × Mountain View(マウンテンビュー)で作るCSSスタイルガイド&コンポーネント

はじめまして。

Mountain View導入の経緯

普段 Ruby On Railsでプロダクトを開発しており、gemで完結するCSSのスタイルガイドを探していました。

有名なgemをいくつか素振りしてみたのですが、LivingStyleGuideはスタイルガイドを簡単にカテゴリ分けできず、Hologramはメンテナンスコストが高そうに感じました。

そんな時に、Railsのビューコンポーネントをそのままスタイルガイド化できるMountainViewと言うgemを知りました。

Mountain Viewとは

https://github.com/devnacho/mountain_view

With Mountain View you create reusable components for your Rails frontend, while generating a living style guide.

image.png

Mountain Viewを使用すると、スタイルガイドを生成しつつ、Railsフロントエンド用の再利用可能なコンポーネントを作ることができます。

この記事で紹介すること

紹介しておいてなんですが、私はパフォーマンス上の理由からMountain Viewのコンポーネント機能をそれほど使っていません。
(コンポーネントはRailsのパーシャルに任せれば済む話で、どちらかと言うとスタイルガイドとしての機能が欲しい...)

この記事では私がMountain Viewを導入し、コンポーネントとして使用することを諦め、スタイルガイドとして使用することに落ち着くまでに試行錯誤した内容をまとめています。

javascriptを使ったモダンなフロントエンド開発からは遠い話になりますのでご容赦ください。

導入 ~ スタイルガイドの作成

Rails 5.2.1環境で公式のREADMEだけを参考に導入することができました。

まずは Gemfilemountain_view を追加します。

# コンポーネント機能を利用する場合はグローバルに読み込みます
gem 'mountain_view'

bundle installを実行し、routes.rb ファイルに次の行を追加します。

mount MountainView::Engine => "/mountain_view" if Rails.env.development?

下記のコマンドでMountain Viewの新しいコンポーネントを作成します。

rails generate mountain_view:component button

すると、次のようなディレクトリとファイルが作成されます。

app/
  components/
    button/
      _button.html.erb
      button.css
      button.js
      button.yml

拡張子にscssやslimなど、Railsで使っているプリプロセッサを使うことができます。

rails s でサーバーを立ち上げてみましょう。
ローカル環境からbuttonコンポーネントのスタイルガイドのページが作成されていることが確認できると思います。
http://localhost:3000/mountain_view

i18n対応

i18n対応で日本語化(default_localeをjaに変更)していた場合、Mountain Viewにja.ymlが存在しないためtitleタグの表示で下記のようなエラー出ているかもしれません。

<title>&lt;span class="translation_missing" title="translation missing: ja.mountain_view.layout.styleguide_title"&gt;Styleguide Title&lt;/span&gt;</title>

私はこちらの en.ymlファイルの内容をコピペした config/locales/mountain_view.ja.yml のようなファイルを作成しました。

buttonコンポーネントを作ってみる

ここでは bootstrap を使用してコンポーネント作りを試してみます。
Gemfile に以下を追加して bundle install します。

gem 'bootstrap'

buttonコンポーネントのscssでbootstrapを読み込みます。

components/button/button.scss
@import "bootstrap";

bootstrapのbuttonコンポーネントをMountainViewに反映させた例です。

app/components/button/_button.html.erb
<button type="button" class="btn <%= properties[:modifire] %>">
  <%= properties[:title] %>
</button>
app/components/button/button.yml
-
  :modifire: btn-primary
-
  :modifire: btn-secondary
-
  :modifire: btn-success
-
  :modifire: btn-danger
-
  :modifire: btn-warning
-
  :modifire: btn-info
-
  :modifire: btn-light
-
  :modifire: btn-dark

image.png

キャッシュの問題

Mountain ViewではCSSの更新やコンポーネントの作成を行った後にキャッシュが残ってしまい、下記のコマンドを実行しないとコンポーネントのデザインがうまく反映されないことがありました。

bin/rake tmp:cache:clear

buttonコンポーネントを使ってみる

定義したコンポーネントはrender_component メソッドを使用することで使うことができます。
第一引数にコンポーネント名、第二引数にハッシュ値を渡して使います。

<%= render_component("button", { title: "Btn Primary", modifire: "btn-primary" }) %>

コンポーネントにブロックを渡し、properties[:yield]で読み込んで使用することもできます。

app/components/button/_button.html.erb
<button type="button" class="btn <%= properties[:modifire] %>">
  <%= properties[:yield] %>
</button>
使い方.html.erb
<%= render_component("button", {modifire: "btn-primary" }) do %>
  Btn Primary
<% end %>

プレゼンターを使ってみる

コンポーネントの階層に{コンポーネント名}_component.rbファイルを追加してMountainViewコンポーネント用のプレゼンターを定義することができます。

app/
  components/
    button/
      _button.html.erb
      button.css
      button.js
      button.yml
      + button_component.rb

MountainView::Presenterを継承して使います。
プロパティのデフォルト値なども使用できます。

app/components/button/button_component.rb
class ButtonComponent < MountainView::Presenter
  properties :modifire, :title
  property :element, default: 'btn'

  def modifire_title
    title || properties[:modifire].titleize
  end
end

定義したメソッドやプロパティをコンポーネントのパーシャルで利用することができます。

app/components/button/_button.html.erb
<button type="button" class="<%= element %> <%= modifire %>">
  <%= title %>
</button>

Railsのコンポーネント管理がMountainViewで完結して最高!
...と思ったのですが、このコンポーネント機能には、後述するパフォーマンス上の問題があるようです。

MountainViewのボトルネック

MountainViewのrender_component機能をeachすると、パフォーマンスがとても残念なことになってしまいます。
これを避けるために公式READMEではMountainViewのプレゼンターでrenderメソッドをオーバーライドする方法を紹介しています。

しかし、個人的にはそれよりも素直にRailsのパーシャルを使用した方がメリットが大きいなのではないかと思います。特にrender_component機能ではパーシャルのコレクション機能が使えないのが痛いので、私はeachする要素でMountain Viewのrender_component機能は使いません。

例えば、Mountain Viewを

app/components/list_item/_list_item.html.erb
<%= properties[:list_item].title %>

こんな風に定義するよりも、Railsのパーシャルで

app/views/components/_list_item.html.erb
<%= list_item.title %>

このように定義しておけば、renderをキャッシュしてくれますし、collection機能でn+1対策もできます。

使い方.html.erb
<% @lists = List.all %>

# @listsが100個あったら100回render
<% @lists.each do |list_item| %> 
  <%= render_component("list_item", {list_item: list_item}) %>
<% end %>

# @listsの結果が100個あっても1回のrenderで済む
<%= render partial: 'components/list_item', collection: @lists, as: :list_item %>

最高のビューコンポーネントはRailsのパーシャルでした...?
と、言うわけで私はMountain ViewをRailsコンポーネント管理用のスタイルガイドとして割り切って使うことにしました。

MountainViewをスタイルガイドとして使用する

MountainViewはデフォルトではスタイルガイドのHTMLタグが表示されません。
デフォルトで prism.js が使われているので、views/mountain_view/styleguide/show.html.erbをオーバーライドして、render_componentの記述の上部に下記のコードを追記しただけでスタイルガイドっぽくなってくれます。

app/views/mountain_view/styleguide/show.html.erb
...
<div class="mv-component__description__properties" style="margin-bottom: 20px;">
  <code class="language-html"><%= CGI::pretty("#{render_component(@component.name, component_stub.properties.clone)}") %></code>
</div>
...

image.png

また、MountainViewのサイドメニューがレスポンシブ表現に邪魔なので、メディアクエリで非表示にしてみました。

image.png

app/assets/stylesheets/mountain_view/layout.scss
.mv-main {
  @media screen and (max-width: 768px) {
    width: 100%;
    padding: 30px 0;
  }
}

.mv-sidebar {
  @media screen and (max-width: 768px) {
    display: none;
  }
}

イニシャライザでMountain Viewでグローバルに読み込みたいCSSを指定することができます。

config/initializers/mountain_view.rb
MountainView.configure do |config|
  config.included_stylesheets = ["mountain_view/layout"]
end

image.png

CSS読み込みの問題

MountainViewはディレクトリ名のコンポーネントをmountain_view.css.erbでディレクトリ名と同名のcssをまとめて読み込んでいるようです。

例えばbuttonコンポーネントでbootstrapをimportした後、別のコンポーネントを作ろうとしてみたところ、button.scssでしかimportしていないbootstrapが既に読み込まれています。

MountainViewの動作がCSS設計の方針と違った場合、私はマニュフェストファイルレイアウトファイルをオーバーライドして調整するようにしています。

プレゼンターをスタブとして使ってしまう

Tipsとして、私は既存のコードをコンポーネント化する際、一旦Mountain Viewのプレゼンターをスタブとして一旦置いてみる方法をとっています。

app/components/post/post_component.rb
class PostComponent < MountainView::Presenter
  # コンポーネント作りに必要なデータを取得
  def current_user
    User.find_by(email: 'necessary-user@exapmle.com')
  end
end

こんな感じでプレゼンターのメソッドを使うと既存のビューをコンポーネントでリプレースする工程がスムーズにいって便利です。

最後に

Railsのgemで完結するスタイルガイドを探したところ、Mountain Viewを改造しながら使っていくと言う道に辿り着きました。
もっとオススメのgemや、Mountain Viewの便利な使い方をご存知の方がいらっしゃいましたらぜひコメント欄で教えてください...?

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

Symbolオブジェクト

Symbolオブジェクト

文字列の前にをつけたもの。

:name

Symbolオブジェクトにすると、文字列ではなく整数として扱われる。
⇒処理速度が早くなる。

*Symbolでは同じSymbol名であれば同じオブジェクトとなる

*以下、ターミナルでpryを起動して実行
symbol1 = :name
symbol2 = :name

*以下2つは、同じIDが出力され、同一のオブジェクトであると分かる
symbol1.object_id
symbol2.object_id
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

オブジェクト指向の強みは変更可能性にこそあると思った。

オブジェクト指向って再利用できるから良いってどこの記事、本にもまとめられています。
そんなの誰でもわかってるけど、それだけではいまいちピンと来ていなかった。

たぶんそれは僕がオブジェクト指向ネイティブだから。
オブジェクト指向が普及していなかった時にプログラミングをしていた人からするとオブジェクト指向は画期的だったのだろう。でもそんなことは知ったこっちゃない、そうしろとプログラミング言語に言われるのだから。誰かに「あーしろ」「こーしろ」と言われるわけではない。そうしないと基本的に動かなかったり、意味不明な挙動をするのだ。

無意識にできていることは素晴らしい事ではあるが、なぜそれが良いのか、どうしてそうするのかを知っていないとエンジニアとは言えない。

そんなある日クライアントにデモを見せるタイミングがあって、デモ中にこんな風にできない?あんな風だといいよね?みたいな言葉が出る。

そんなときに

「あー、それなら秒でできますよ。... リロードしてください。」

「おー!!」

みたいな体験があるとかっこいい。

オブジェクト指向で書いていると挙動の簡単な変更はすぐにできる。
しかもオプションでいろいろ変更できるようにしているとなおさら。
Javascriptのプラグインなどは基本的にoptionで簡単な挙動は変更できる。
クライアントは基本的に表面の動きを見ているので、表面の変えたいところが目の前で変わるのを見れば魔法か何かだと思ってしまうのも無理がない。

エンジニアにとっては当たり前であるが、クライアント(非エンジニア)にとっては魔法に見えることが多くある。その芸を支えるのがオブジェクト指向だと思った。

単に書けるエンジニアではなく、デモで魅せられるエンジニアは強いとおもった。

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

Wercker × parallel_tests

why

Wercker上でのspecテスト完遂に35分ほど要するが、その時間を短縮して開発効率を改善したい。

※Wercker:Oracle提供のCIツール

what

テストを並列実行する。

how

環境
Ruby:2.2.3
Rails:5.0.0.1
RSpec:3.5.2
sqlite:1.3.13 

local開発環境での実行

テスト並列実行用のgem "parallel_tests" を導入。

gemfile に parallel_test を追加。

Gemfile
 group :development, :test do
...
  gem 'parallel_tests'
  ...
end

gemパッケージをインストール。

$ bundle install

config/database.yml に追加

config/database.yml
test:
  database: yourproject_test<%= ENV['TEST_ENV_NUMBER'] %>

テスト用DB:yourproject_testの複製

config/database.yml の設定に応じてDBを作成。

$ bundle exec rake parallel:create RAILS_ENV=test

schemaコピー

$ bundle exec rake parallel:prepare[4]

rpsec_test実行

$ bundle exec rake parallel:spec[4]

[]は並列実行させるプロセス数

Wercker環境での実行

Wercker の docker内で複数DB作成

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

[WIP] Wercker × parallel_tests

why

Wercker上でのspecテスト完遂に35分ほど要するが、その時間を短縮して開発効率を改善したい。

※Wercker:Oracle提供のCIツール

what

テストを並列実行する。

how

環境
Ruby:2.2.3
Rails:5.0.0.1
RSpec:3.5.2
MYSQL2:0.4.5 

local開発環境での実行

テスト並列実行用のgem "parallel_tests" を導入。

gemfile に parallel_test を追加。

Gemfile
group :development, :test do
  gem 'parallel_tests'
end

gemパッケージをインストール。

$ bundle install

config/database.yml に追加

config/database.yml
test:
  database: yourproject_test<%= ENV['TEST_ENV_NUMBER'] %>

テスト用DB:yourproject_testの複製

config/database.yml の設定に応じてDBを作成。

$ bundle exec rake parallel:create RAILS_ENV=test

schemaコピー

$ bundle exec rake parallel:prepare[4]

rspec_test実行

$ bundle exec rake parallel:spec[4]

[]は並列実行させるプロセス数

Wercker環境での実行

WerckerのdockerコンテナにDB作成

wercker.yml
services:
  - id: mysql:<version>
    env:
      MYSQL_USER: *** # DBのuser名
      MYSQL_PASSWORD: *** # DBのpassword
      MYSQL_DATABASE: *** # DBの名前

後の rails-database-yml コマンドは services/env プロパティで指定した内容の database.yml (DB名、ユーザー名、パスワードのDB)を作成。

build:
  steps:
    - rails-database-yml

Werckerの同一dockerコンテナでDB複製

Initializing a fresh instance
When a container is started for the first time, a new database with the specified name will be created and initialized with the provided configuration variables. Furthermore, it will execute files with extensions .sh, .sql and .sql.gz that are found in /docker-entrypoint-initdb.d. Files will be executed in alphabetical order. You can easily populate your mysql services by mounting a SQL dump into that directory and provide custom images with contributed data. SQL files will be imported by default to the database specified by the MYSQL_DATABASE variable.

コンテナーが初めて開始されると、指定された名前の新しいデータベースが作成され、提供されている構成変数で初期化されます。 さらに、/ docker-entrypoint-initdb.dにある拡張子.sh、.sql、および.sql.gzのファイルを実行します。 ファイルはアルファベット順に実行されます。

というわけで、 /docker-entrypoint-initdb.d にSQLファイルを置いたりすればいい感じにできます。
が、SQLファイルでは、 MYSQL_DATABASE に指定したDBに対してダンプファイルを流すだけですので、他のDBを作ったりすることはできません。

参考

  1. docker-composeでmysql使うとき初回起動時に複数のDBを作る方法
  2. MySQL Docker Official Images
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue on RailsでActive Storageを使って画像を保存する

はじめに

Rails API モードと Vue.js で作成した 自作ブログ で Active Storage を使う際に、画像の受け渡しでハマったので実装方法を残します。
実装するのは Active Storage を使って、eyecatch (アイキャッチ画像) 付きの Post (記事) を投稿できるようなサンプルです。

環境

$ ruby -v
ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-darwin16]

$ bundle exec rails --version
Rails 5.2.2

実装方法

API 部分 (Rails)

Ⅰ. サンプルアプリケーション新規作成

Rails API モードで作成します。

$ bundle exec rails new sampleapp --database=mysql --api --force
$ cd sampleapp/
$ bundle exec rails db:create

Ⅱ. Active Storage インストール

Active Storage をインストールします。

$ bundle exec rails active_storage:install
$ bundle exec rails db:migrate

Ⅲ. Post リソースの作成

今回利用する Post モデルとコントローラを作成します。

$ bundle exec rails g resource post title body:text
$ bundle exec rails db:migrate

Ⅳ. 各種ファイルの修正

各 Post に eyecatch を設定できるようにモデルを修正します。
追加する eyecatch= メソッドで、Active Storage に画像を保存します。

このメソッドは、Base64 形式で受け取った image データをエンコードし、一時的に /tmp 配下に画像ファイルを作成、作成した画像ファイルをアタッチ、その後画像ファイルを削除します。

app/models/post.rb
class Post < ApplicationRecord
  has_one_attached :eyecatch
  attr_accessor :image

  def eyecatch=(image)
    if image.present?
      prefix = image[/(image|application)(\/.*)(?=\;)/]
      type = prefix.sub(/(image|application)(\/)/, '')
      data = Base64.decode64(image.sub(/data:#{prefix};base64,/, ''))
      filename = "#{Time.zone.now.strftime('%Y%m%d%H%M%S%L')}.#{type}"
      File.open("#{Rails.root}/tmp/#{filename}", 'wb') do |f|
        f.write(data)
      end
      eyecatch.detach if eyecatch.attached?
      eyecatch.attach(io: File.open("#{Rails.root}/tmp/#{filename}"), filename: filename)
      FileUtils.rm("#{Rails.root}/tmp/#{filename}")
    end
  end
end

モデルで追加した eyecatch= メソッドに POST で受け取る画像のパラメータを渡すように修正します。

app/controllers/posts_controller.rb
class PostsController < ApplicationController
  def create
    post = Post.new(post_params)

    if post.save
      post.eyecatch = post_params[:image]
      render json: post, status: :created
    else
      render json: post.errors, status: :unprocessable_entity
    end
  end

  private

  def post_params
    params.require(:post).permit(:title, :body, :image)
  end
end

Ⅴ. 動作確認

アプリケーションを起動し、curl コマンドでアイキャッチ付き Post が作成できるか確認します。

$ bundle exec rails s

$ curl -v -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{"post": {"title": "Sample title.", "body": "Sample body.", "image": "data:application/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAYAAACALL/6AAAACXBIWXMAADXU\nAAA11AFeZeUIAAAAgUlEQVQYlZWQMQ6DMAxFnyMGJFZCc4be/yisRYyVmDN0\niTuQULCCoH9z/PLtb/hTsi8SaA1yO85ZWHxgAqb8HuVoJAX+iKPtB17L++D+\nyN6drpOa0mj7AYBg1pmz99NmSKDiVzzmKTM/uOTYMjgQNetYuKoEqj7oCHp2\nteqn2/CVvuDZJy0n3DrVAAAAAElFTkSuQmCC\n"}}' http://localhost:3000/posts

curl コマンドで作成後、Rails Console でアイキャッチが正常に追加できているか確認します。

$ bundle exec rails c
irb(main):001:0> Post.find_by(title: "Sample title.").eyecatch.attached?
=> true

# 画像のパスは以下のように取得できます。
# irb(main):002:0> app.url_for(Post.find_by(title: "Sample title.").eyecatch)
# => "画像のパス"

フロント部分 (Vue.js)

Ⅰ. Webpacker をインストール

webpacker gem を追加する。

Gemfile
gem 'webpacker', '~> 3.5'

Webpacker をインストールします。

$ bundle
$ bundle exec rails webpacker:install

Ⅱ. Vue.js をインストール

Webpacker で Vue.js をインストールします。

$ bundle exec rails webpacker:install:vue

Ⅲ. Home ページを作成

Vue.js を返すための Home ページを作成します。

$ bundle exec rails g controller Pages Home

Root パスに Home ページを設定します。

config/routes.rb
Rails.application.routes.draw do

  root 'pages#home'

end

標準の JSON ではなく、ERB を返すために ActionController::Base に修正します。

app/controllers/pages_controller.rb
class PagesController < ActionController::Base
  def home
  end
end

Home ページに利用する View を作成します。

$ mkdir -p app/views/pages/
$ touch app/views/pages/home.html.erb

Ⅳ. Vue.js を利用

Vue.js を利用するための設定を行います。

app/views/pages/home.html.erb
<%= javascript_pack_tag 'main' %>

利用する各種ファイルを作成します。

$ touch app/javascript/packs/main.js
$ touch app/javascript/packs/App.vue
app/javascript/packs/main.js
import Vue from 'vue'
import App from './App.vue'

document.addEventListener('DOMContentLoaded', () => {
  const el = document.body.appendChild(document.createElement('main'))
  new Vue({
    el,
    render: h => h(App)
  })
})
app/javascript/packs/App.vue
<template>
  <div id="app">
    <p>投稿フォーム</p>
  </div>
</template>

Ⅴ. 投稿フォームを作成

API コールに利用する axios をインストールします。

$ yarn add axios

画像投稿するフォームを用意します。
画像は POST する前に Base64 にデコードしています。

app/javascript/packs/App.vue
<template>
  <div id="app">
    <p>投稿フォーム</p>
    <form v-on:submit.prevent="postItem()">
      <p>
        <label>Title</label>
        <input name="post.title" type="text" v-model="post.title"><br />
      </p>
      <p>
        <label>Body</label>
        <input name="post.body" type="text" v-model="post.body"><br />
      </p>
      <p>
        <label>画像</label>
        <input name="uploadedImage" type="file" ref="file" v-on:change="onFileChange()"><br />
      </p>
      <input type="submit" value="Submit">
    </form>
  </div>
</template>

<script>
import axios from 'axios'

export default {
  data() {
    return {
      post: {},
      uploadedImage: ''
    }
  },
  methods: {
    onFileChange() {
      let file = event.target.files[0] || event.dataTransfer.files
      let reader = new FileReader()
      reader.onload = () => {
        this.uploadedImage = event.target.result
        this.post.image = this.uploadedImage
      }
      reader.readAsDataURL(file)
    },
    postItem() {
      return new Promise((resolve, _) => {
        axios({
          url: '/posts',
          data: {
            post: this.post
          },
          method: 'POST'
        }).then(res => {
          this.post = {}
          this.uploadedImage = ''
          this.$refs.file.value = ''
          resolve(res)
        }).catch(e => {
          console.log(e)
        })
      })
    }
  }
}
</script>

Ⅵ. 動作確認

アプリケーションを起動し、投稿フォームから画像を投稿します。
http://localhost:3000/

$ bundle exec rails s

img1.png

Rails Console でアイキャッチが正常に追加できているか確認します。

$ bundle exec rails c
irb(main):001:0> Post.last.eyecatch.attached?
=> true

参考記事

https://qiita.com/ozin/items/5ec81a4b126b8ebf7a96

最後に

読んでいただいてありがとうございます。
間違っている点などがありましたら、ご指摘いただけると喜びます!

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