20200122のRailsに関する記事は22件です。

railsサイトマップをサーチコンソールに送る方法

route.rb

get 'sitemap', to: redirect('https://s3-ap-northeast-1.amazonaws.com/バケット名/sitemaps/sitemap.xml.gz')

sitemap.rb

SitemapGenerator::Sitemap.default_host = "サイトのurl"
SitemapGenerator::Sitemap.sitemaps_host = "https://s3-ap-northeast-1.amazonaws.com/#{ENV['S3_BUCKET_NAME']}"
SitemapGenerator::Sitemap.sitemaps_path = 'sitemaps/'
SitemapGenerator::Sitemap.adapter = SitemapGenerator::AwsSdkAdapter.new(
  ENV['S3_BUCKET_NAME'],
  aws_access_key_id: ENV['AWS_ACCESS_KEY_ID'],
  aws_secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'],
  aws_region: 'ap-northeast-1'
)

SitemapGenerator::Sitemap.create do

  add posts_path, changefreq: 'daily'

  posts = Post.published
  posts.find_each do |post|
    add post_path(post), lastmod: post.updated_at
  end
end

herokuに環境変数を設定する。

search console
ドメイン/sitemapで登録

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

JSON Web Token(JWT)を用いたAPIの認証の実装(Rails)

解決する課題

従来のWebアプリケーションでは、認証情報をCookiesを利用して保持し、ログイン状態を保つことを実現しています。

APIではブラウザを介さないことからCookiesを使用したセッションの保持は行えないため、認証時に生成したトークンを用いて認証済み(ログイン状態)であることを表現できます。

JSON Web Tokenの利用

認証時に生成するトークンは慣例通りJSON Web Token(JWT)を利用します。
Railsでは"jwt"というGemがあるので、簡単に扱うことができます。

アプリケーションによりますが、controller/concernsに認証用モジュールを設け、そこにJWT関連の処理をまとめて記載すると扱いやすいです。

暗号化・復号化の実装

手始めに、暗号化・復号化の実装例は下記のようになります。
app/controller/concerns下に作成しています。

jwt_authenticator.rb
module JwtAuthenticator
  require "jwt"

  SECRET_KEY = Rails.application.secrets.secret_key_base

  # 暗号化処理
  def encode(user_id)
    expires_in = 1.month.from_now.to_i # 再ログインを必要とするまでの期間を1ヶ月とした場合
    preload = { user_id: user_id, exp: expires_in }
    JWT.encode(preload, SECRET_KEY_BASE, 'HS256')
  end

  # 復号化処理
  def decode(encoded_token)
    decoded_dwt = JWT.decode(encoded_token, SECRET_KEY_BASE, true, algorithm: 'HS256')
    decoded_dwt.first
  end

end

暗号化処理では、ユーザーのIDをカスタムクレームとして含め、jwt標準のクレームのexpと合わせてPayloadを作成しています。
これを秘密鍵を用いて、HS256のアルゴリズムで暗号化します。

復号化処理では、秘密鍵を用いて復号化を行います。
第三引数のTrueはトークンの検証を行うかどうかなので、Trueにしましょう。
復号化を行うと、配列が返却され、1つ目の要素がPayloadとなっているので、それを返却しています。

ちなみに、もしPayloadに各種標準クレームを含めた場合、第四引数以降には標準クレームの検証を行うかどうか、verify_iat: trueのように指定できます。
この辺りの詳細はJWTについての資料を参考にしてください。
個人的にはこのサイトを参考にさせていただきました。
https://ruby-rails.hatenadiary.com/entry/20181118/1542521671

ログインの実装

上記の暗号化処理を用いて生成したトークンをレスポンスヘッダに含めて渡します。
ログインを行うコントローラの実装例は下記です。

sessions_controller.rb
class SessionsController < ApplicationController
  include JwtAuthenticator

  def create
    # ログインIDを元にユーザーを取得
    @current_user = User.find_by(sign_in_id: params[:sign_in_id])
    # パスワードによる認証
    if @current_user&.authenticate(params[:password])
      # jwtの発行
      jwt_token = encode(@current_user.id)
      # レスポンスヘッダーにトークンを設定
      response.headers['X-Authentication-Token'] = jwt_token
      # 任意のレスポンスを返す
      render json: @current_user
    else
      raise UnableAuthorizationError.new("ログインIDまたはパスワードが誤っています。") 
    end
  end
end

ID、パスワードによる認証ができた場合、レスポンスヘッダに生成したトークンを設定します。
例えばこのAPIを利用したアプリケーションでは、このヘッダからトークンを取得して、以降のAPIに含めて実行します。

JWTの認証

リクエストヘッダーに含まれてきたJWTを認証する実装例は下記の通りです。
先ほどのモジュールに処理を追加しています。

jwt_authenticator.rb
module JwtAuthenticator
  require "jwt"

  SECRET_KEY = Rails.application.secrets.secret_key_base

  # ヘッダーの認証トークンを復号化してユーザー認証を行う
  def jwt_authenticate
    raise UnableAuthorizationError.new("認証情報が不足しています。") if request.headers['Authorization'].blank?
    # 下記のようにヘッダーに設定されているとして、トークンをヘッダーから取得する。
    # headers['Authorization'] = "Bearer XXXXX..."
    encoded_token = request.headers['Authorization'].split('Bearer ').last
    # トークンを復号化する(トークンが復号できない場合、有効期限切れの場合はここで例外が発生します。)
    payload = decode(encoded_token)
    # Payloadから取得したユーザーIDでログインしているユーザー情報を取得
    @current_user = User.find_by(id: payload[:user_id])
    raise UnableAuthorizationError.new("認証できません。") if @current_user.blank?
    @current_user
  end

  # 暗号化処理
  def encode(user_id)
    ...
  end

  # 復号化処理
  def decode(encoded_token)
    ...
  end

end

この認証処理を各コントローラで、before_actionなどで実装します。

users_controller.rb
class UsersController < ApplicationController
  include JwtAuthenticator
  before_action :jwt_authenticate, except: :create

  # 非ログイン状態で行うユーザー登録処理
  def create
    user = User.new(user_params)
    if user.save
      render json: user
    else
      render json: user.errors
    end
  end

  # ログイン中でないと実行してはいけない処理
  def show
    render json: @current_user
  end

  private

  def user_params
    params.require(:user).permit(:name, :sign_in_id, :password)
  end
end

応用

全てのControllerにbefore_actionを設定するのは面倒なので、
認証用のモジュールを下記のように実装すれば、includeして、jwt_authenticateを記述するだけでactionの前に認証を行うようにできます。

jwt_authenticator.rb
module JwtAuthenticator
  require "jwt"
  # ↓を必ずextendする
  extend ActiveSupport::Concern

  # jwt_authenticateを記載したコントローラにprepend_before_actionを定義する
  module ClassMethods
    def jwt_authenticate(**options)
      class_eval do
        prepend_before_action :jwt_authenticate!, options
      end
    end
  end

  ...
end

コントローラ側

users_controller.rb
class UsersController < ApplcationController
  include JwtAuthenticator
  jwt_authenticate except: :create

  ...
end

さらに、ApplicationControllerに"include JwtAuthenticator"を記載すれば、省くこともできます。
都合に合わせて実装しましょう。

あとがき

実際に動作させている処理を元に記事を書きましたが、はしょったり部分的に切り出して書いた例なので、引用する際はご注意ください。

認証処理はアプリ開発最初の壁だと思います。
また、なあなあにしていたら後々に響いてくる部分でもあると思います。
少しでも参考になれば幸いです。

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

Herokuへのデプロイでエラー発生 ⇨ ExecJS::RuntimeError: SyntaxError: Unexpected token punc «;», expected punc «)»

はじめに

  • RailsチュートリアルでHerokuにデプロイする時にハマったエラー。
  • エラー発生箇所の特定に役立つ知識が得られた



ではエラー内容を見てみましょう。


エラー内容 1

コンソール
:
:
remote:  !
remote:  !     Precompiling assets failed.
remote:  !
remote:  !     Push rejected, failed to compile Ruby app.
:
:

「アセットのプリコンパイルが失敗してますよ〜」

とのこと。


またエラーを上から見ていくと、こんなことも書かれている

エラー内容 2

コンソール
:
:
rake aborted!
ExecJS::RuntimeError: SyntaxError: Unexpected token punc «;», expected punc «)»
:
:

なるほど。どこかのJavascriptファイル内で構文エラーが発生しているらしい。

でもどのファイルかは教えてくれてないのね(T T)

じゃあ自分で調べて見ますか〜



エラー発生箇所の特定

Railsコンソール

JS_PATH = "app/assets/javascripts/**/*.js";
Dir[JS_PATH].each do |file_name|
  puts "\n#{file_name}"
  puts Uglifier.compile(File.read(file_name), harmony: true)
end

Railsコンソールを立ち上げて上のコードを実行します。

あーら不思議。エラーが発生しているファイルが表示される(はず)

あとは
➡︎ 特定したファイルを開く
➡︎ エラー箇所を修正
➡︎ Herokuへ push!

私はこの方法でエラー解消しました。

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

RESTfulとは。。。

Ruby on Rails初心者です。
RESTfulという言葉をよく耳にするので自己理解も兼ねてまとめたいと思います。

RESTfulとは

RESTの規約に従った、Webサービスのこと。
このRESTは以下のような特徴を持っています。

◉アドレス可能性
リソースをURIとして一意に指し示すことができる

◉ステートレス性
一つ一つのリクエストに処理に必要な情報を全て含める性質(自己完結型)

◉統一インターフェース
URIで指し示したリソースに対する操作を、GETやPOSTといった8つメソッドに絞り込み、限定的なインターフェースを行う

◉接続性
リソースをリンクで接続し、一つの情報に他のリンクの情報なども含めることができること

RESTfulにすることのメリット

○ URIの部分などで、美しくわかりやすいインターフェースを表現できる
○ 他の開発者が設計を理解しやすくなる
○ いちいちURLやHTTPメソッドを考えないで済む
○ サーバがクライアントの情報を共有しないことにより、スケーラビリティが向上する。
(サーバがクライアントの情報を記憶するようにしてしまうと(ステートフル)、クライアントが増えていった際にサーバを増加させる必要がありますが、その際にクライアントの情報をサーバ間で同期する必要があり、オーバヘッドが発生してしまいます。)

まとめ

RESTfulな設計をしていくことで、わかりやすいWebアプリケーションをつくることができるようになる(と感じました)

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

Ruby on Railsとは、なんなのか簡単に説明してみる(自分なりの解釈)

Ruby on Railsは自動調理器

自分が作りたいサービス(料理)を、
必要な機能(材料)を入力(自動調理器にぶち込む)するだけで、
ほぼ完成させることが出来るツールのようなもの。

見た目や味の調整の為に、整える。(サイトの細かい構成をいじる

自動調理器ってなんなんだ。。。

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

[Ruby on Rails]データベースの作成、カラムの追加

いつも忘れるのでRubyonRailsでのデータベースの作成、カラムの追加についてメモ

データベース作成

まずはデータベースへの変更指示を記述するマイグレーションファイルを作成する
モデル(User)も同時に作成される。カラムは(パラメーター名):(変数の型)で設定
コマンドラインでカラム設定しないでファイルに直書きでもOK

terminal
rails g model User name:text address:text age:integer

次にマイグレーションファイル内容を実行する

terminal
rails db:migrate

上のコマンドの代わりにこっちでも実行できるけど違いは分かりません(調べます)

terminal
bundle exec rake db:migrate

カラム追加

まずマイグレーションファイルを任意の分かりやすいファイル名で作成

terminal
rails g migration add_occupation_to_users

次に作成したマイグレーションファイルに変更内容を書き込む

add_occupation_to_users.rb
class AddOccupationToUsers < ActiveRecord::Migration[5.2]
  def change
    add_column :users, :occupation, :string
  end
end

そしたら再びマイグレーションファイル実行

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

指定のRailsをインストールする際のやり方

gem install rails

これでは最新のRailsがインストールされてしまいます。

特定のRailsをインストールするには

gem install -v 5.2.3 rails

とします

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

[Rails]ModelやControllerの削除

Modelの削除

まずはModelを作成

$ rails g model hoge name:string address:string
      invoke  active_record
      create    db/migrate/20200122093003_create_hoges.rb
      create    app/models/hoge.rb
      invoke    test_unit
      create      test/models/hoge_test.rb
      create      test/fixtures/hoges.yml

作成したモデルを削除

$ rails destroy model hoge
      invoke  active_record
      remove    db/migrate/20200122093003_create_hoges.rb
      remove    app/models/hoge.rb
      invoke    test_unit
      remove      test/models/hoge_test.rb
      remove      test/fixtures/hoges.yml

Controllerの削除

まずはコントローラーを作成

$ rails g controller foobar
      create  app/controllers/foobar_controller.rb
      invoke  erb
      create    app/views/foobar
      invoke  test_unit
      create    test/controllers/foobar_controller_test.rb
      invoke  helper
      create    app/helpers/foobar_helper.rb
      invoke    test_unit
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/foobar.coffee
      invoke    scss
      create      app/assets/stylesheets/foobar.scss

作成したコントローラーを削除

rails destroy controller foobar
      remove  app/controllers/foobar_controller.rb
      invoke  erb
      remove    app/views/foobar
      invoke  test_unit
      remove    test/controllers/foobar_controller_test.rb
      invoke  helper
      remove    app/helpers/foobar_helper.rb
      invoke    test_unit
      invoke  assets
      invoke    coffee
      remove      app/assets/javascripts/foobar.coffee
      invoke    scss
      remove      app/assets/stylesheets/foobar.scss

scaffoldで一括作成したものを削除

まずはscaffoldで作成

$ rails g scaffold Hoge name:string email:string                        
      invoke  active_record
      create    db/migrate/20200122093732_create_hoges.rb
      create    app/models/hoge.rb
      invoke    test_unit
      create      test/models/hoge_test.rb
      create      test/fixtures/hoges.yml
      invoke  resource_route
       route    resources :hoges
      invoke  scaffold_controller
      create    app/controllers/hoges_controller.rb
      invoke    erb
      create      app/views/hoges
      create      app/views/hoges/index.html.erb
      create      app/views/hoges/edit.html.erb
      create      app/views/hoges/show.html.erb
      create      app/views/hoges/new.html.erb
      create      app/views/hoges/_form.html.erb
      invoke    test_unit
      create      test/controllers/hoges_controller_test.rb
      invoke    helper
      create      app/helpers/hoges_helper.rb
      invoke      test_unit
      invoke    jbuilder
      create      app/views/hoges/index.json.jbuilder
      create      app/views/hoges/show.json.jbuilder
      create      app/views/hoges/_hoge.json.jbuilder
      invoke  test_unit
      create    test/system/hoges_test.rb
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/hoges.coffee
      invoke    scss
      create      app/assets/stylesheets/hoges.scss
      invoke  scss
      create    app/assets/stylesheets/scaffolds.scss

scaffoldによって一括作成されたものを削除する

$ rails destroy scaffold hoges                                          
      invoke  active_record
      remove    db/migrate/20200122093732_create_hoges.rb
      remove    app/models/hoge.rb
      invoke    test_unit
      remove      test/models/hoge_test.rb
      remove      test/fixtures/hoges.yml
      invoke  resource_route
       route    resources :hoges
      invoke  scaffold_controller
      remove    app/controllers/hoges_controller.rb
      invoke    erb
      remove      app/views/hoges
      remove      app/views/hoges/index.html.erb
      remove      app/views/hoges/edit.html.erb
      remove      app/views/hoges/show.html.erb
      remove      app/views/hoges/new.html.erb
      remove      app/views/hoges/_form.html.erb
      invoke    test_unit
      remove      test/controllers/hoges_controller_test.rb
      invoke    helper
      remove      app/helpers/hoges_helper.rb
      invoke      test_unit
      invoke    jbuilder
      remove      app/views/hoges
      remove      app/views/hoges/index.json.jbuilder
      remove      app/views/hoges/show.json.jbuilder
      remove      app/views/hoges/_hoge.json.jbuilder
      invoke  test_unit
      remove    test/system/hoges_test.rb
      invoke  assets
      invoke    coffee
      remove      app/assets/javascripts/hoges.coffee
      invoke    scss
      remove      app/assets/stylesheets/hoges.scss
      invoke  scss

既にmigrateしてある場合は、データベースにテーブルが残ってしまうので、下記の手順も必要。
まずはマイグレーションファイルを作成する。

$ rails g migration hoges

作成したマイグレーションファイルにhogesテーブルの削除命令を書く。

class Hoges < ActiveRecord::Migration[5.1]
  def change
    drop_table :hoges
  end
end

あとはmigrateして完了

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

Railsチュートリアル 第2章

この章では、scaffold機能を使ってコードを自動生成する方法などを学んだ。

scaffoldとは

本来、Railsでアプリケーションを作成するには、モデル、コントローラー、ビュー等を作り、ルーティングも作成する必要があるが、それをコマンド一つで一括で作成してくれるのがscaffold。

試しにscaffoldを試してみると

今回は、ユーザー情報(名前、メールアドレス)の登録・更新・削除が行える画面を作ってみる。

$ rails generate scaffold User name:string email:string
  invoke  active_record
  create    db/migrate/20200122063729_create_users.rb
  create    app/models/user.rb                  ⇨モデル作成
  invoke    test_unit
  create      test/models/user_test.rb
  create      test/fixtures/users.yml
  invoke  resource_route                      ⇨ルーティング作成
   route    resources :users
  invoke  scaffold_controller
  create    app/controllers/users_controller.rb        ⇨コントローラー作成
  invoke    erb
  create      app/views/users                   ⇨ビュー作成
  create      app/views/users/index.html.erb
  create      app/views/users/edit.html.erb
  create      app/views/users/show.html.erb
  create      app/views/users/new.html.erb
  create      app/views/users/_form.html.erb
  invoke    test_unit
  create      test/controllers/users_controller_test.rb
  invoke    helper
  create      app/helpers/users_helper.rb
  invoke      test_unit
  invoke    jbuilder
  create      app/views/users/index.json.jbuilder
  create      app/views/users/show.json.jbuilder
  create      app/views/users/_user.json.jbuilder
  invoke  test_unit
  create    test/system/users_test.rb
  invoke  assets
  invoke    coffee
  create      app/assets/javascripts/users.coffee
  invoke    scss
  create      app/assets/stylesheets/users.scss
  invoke  scss
  create    app/assets/stylesheets/scaffolds.scss

config/routes.rb に「resources :users」が追加された事で、Railsで定義されている7つのアクションがセットで追加されていることがわかります。

$ rails routes
   Prefix Verb   URI Pattern               Controller#Action
    users GET    /users(.:format)          users#index
          POST   /users(.:format)          users#create
 new_user GET    /users/new(.:format)      users#new
edit_user GET    /users/:id/edit(.:format) users#edit
     user GET    /users/:id(.:format)      users#show
          PATCH  /users/:id(.:format)      users#update
          PUT    /users/:id(.:format)      users#update
          DELETE /users/:id(.:format)      users#destroy
     root GET    /                         

正確に言うと、updateが2つ存在し、8つのアクションが作成されているが、PUTリクエストの方は昔の名残?か何かで実際にはPATCHが使われる。

また、7つのアクションが作成されると言ったが、usersコントローラーを見ると、7つ以上のアクションが自動で作成されているが、このcreate,update,destroyはページを出力せずベータベース上のユーザー情報の操作するだけのため。

一括で作成できたが、問題もたくさん

scaffoldコマンド一つで、ユーザーの新規登録、更新、削除が行える画面が作成できたが、この時点では下記のような問題が残っている。

  • データの検証が行われていないので、空情報を登録できてしまう
  • ユーザー認証が行われていないので、誰でも自由に登録や削除ができてしまう
  • テストが書かれていない
  • レイアウトが整っていない

scaffoldの取り消し

間違った名前などで作成してしまったなどした時の取り消し手順

$ rails destroy scaffold users

但し、これだけではデータベースにまだテーブルが残ってしまうので、マイグレーションファイルを作成する。

rails g migration users

作成したマイグレーションフィアるにusersテーブルの削除命令を書く。

class Users < ActiveRecord::Migration[5.1]
  def change
    drop_table :users
  end
end

あとはマイグレートして無事完了

$ rails db:migrate

@記号で始まる変数

@で始まる変数をインスタンス変数と呼び、コントローラー内で宣言されたインスタンス変数はビューでも使えるようになる。
つまり、逆に@が付いていない変数はビューでは使えない。

def index
    @users = User.all
end

この章で失敗したこと

試しにscaffoldしたファイル群を削除するのをしたときにusersテーブルの削除するマイグレーションファイルが残ったまま、herokuにpushしてしまい、migrateしてもエラーが出ているのを5分ぐらい気づけなかった。。

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

houstonを使ってrailsサーバーからプッシュ通知を送る時に設定できる値のメモ

1. 概要

"houston"というgemを使って、ruby on railsのサーバーからiOSアプリにプッシュ通知を送るようにしました。

今回は本文・SE・バッジ数だけの一番シンプルな通知を実装しましたが、GitHubのUsageを参照すると、通知データに対して他にも設定できる項目があるようです

将来細かい設定が必要になった時に忘れないように、調べたことを書いておきます。

2. メモ

Usage.rb
require 'houston'

# 定数の宣言はメソッド外でする
APN = Houston::Client.development
APN.certificate = File.read('/path/to/apple_push_notification.pem')

# 通知を送りたいデバイスのdevice tokenを代入
token = '<ce8be627 2e43e855 16033e24 b4c28922 0eeda487 9c477160 b2545e95 b68b5969>'

# houstonのインスタンスを生成
notification = Houston::Notification.new(device: token)
# 通知メッセージを設定
notification.alert = 'Hello, World!'

# アプリアイコンの右上に表示するバッジの数字を設定(この数字がそのまま表示されるので、加算するための計算はこの前に行っておく)
notification.badge = 57
# 通知を受信した時の音声ファイル(カスタムする場合は事前に用意)
notification.sound = 'sosumi.aiff'

# これをtrueにしないと通知を受信しない
notification.content_available = true

# リッチ通知を実装する際はtrueを設定
notification.mutable_content = true
# リッチ通知を実装する際は、XcodeのプロジェクトからUNNotificationContentExtensionのInfo.plistのcategoryを以下の値と同じにする
notification.category = 'INVITE_CATEGORY'

# 通知の設定は"AnyHashable("aps"): {...}"の値として送信されるが、それ以外にデータを付け足したい場合はここを設定する
notification.custom_data = { foo: 'bar' }

# 以下の値は"AnyHashable("aps"): {...}"の値に含むことができるが、使い道がわからない……
notification.url_args = %w[boarding A998]
notification.thread_id = 'notify-team-ios'

# 通知を送信する
APN.push(notification)

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

houstonを使ってrailsサーバーからプッシュ通知を送る時に設定できる値のメモ、あと多言語対応

1. 概要

"houston"というgemを使って、ruby on railsのサーバーからiOSアプリにプッシュ通知を送るようにしました。

今回は本文・SE・バッジ数だけの一番シンプルな通知を実装しましたが、GitHubのUsageを参照すると、通知データに対して他にも設定できる項目があるようです

将来細かい設定が必要になった時に忘れないように、調べたことを書いておきます。

2. メモ

Usage.rb
require 'houston'

# 定数の宣言はメソッド外でする
APN = Houston::Client.development
APN.certificate = File.read('/path/to/apple_push_notification.pem')

# 通知を送りたいデバイスのdevice tokenを代入
token = '<ce8be627 2e43e855 16033e24 b4c28922 0eeda487 9c477160 b2545e95 b68b5969>'

# houstonのインスタンスを生成
notification = Houston::Notification.new(device: token)
# 通知メッセージを設定
notification.alert = 'Hello, World!'

# アプリアイコンの右上に表示するバッジの数字を設定(この数字がそのまま表示されるので、加算するための計算はこの前に行っておく)
notification.badge = 57
# 通知を受信した時の音声ファイル(カスタムする場合は事前に用意)
notification.sound = 'sosumi.aiff'

# これをtrueにしないと通知を受信しない
notification.content_available = true

# リッチ通知を実装する際はtrueを設定
notification.mutable_content = true
# リッチ通知を実装する際は、XcodeのプロジェクトからUNNotificationContentExtensionのInfo.plistのcategoryを以下の値と同じにする
notification.category = 'INVITE_CATEGORY'

# 通知の設定は"AnyHashable("aps"): {...}"の値として送信されるが、それ以外にデータを付け足したい場合はここを設定する
notification.custom_data = { foo: 'bar' }

# 以下の値は"AnyHashable("aps"): {...}"の値に含むことができるが、使い道がわからない……
notification.url_args = %w[boarding A998]
notification.thread_id = 'notify-team-ios'

# 通知を送信する
APN.push(notification)

3. (追記) 通知の多言語対応

プッシュ通知の多言語対応について書いた記事があまり見当たらなかったので、ついでにメモしておきます。

以下の方法を用いると、アプリ内の表示と同じように端末の言語設定に応じた通知の多言語対応をすることができます。

Localizable.strings
"GAME_PLAY_REQUEST_FORMAT" = "%@ and %@ have invited you to play Monopoly";
Usage.rb
notification.alert = {
  # Localizable.stringsに記述したkeyを入れる
  "loc-key" : "GAME_PLAY_REQUEST_FORMAT",
  # 変数を設定した場合は配列で指定する
  "loc-args" : [ "Jenna", "Frank"]
}

引用元:Creating the Remote Notification Payload

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

jiraのapi

jiraのapi

basic認証とoauth認証があるみたいだが、oauth認証はaccess tokenを取得して、再取得するのが面倒そうなので、basic認証を使うことに。

が、Deprecatedになったみたい?
https://community.atlassian.com/t5/Confluence-questions/Trying-to-access-Confluence-using-Basic-Authentication/qaq-p/1268998

api_tokenを使ってapi呼び出し

こっち見るとapi_tokenというのを使えばいいみたい
https://community.atlassian.com/t5/Confluence-questions/Trying-to-access-Confluence-using-Basic-Authentication/qaq-p/1268998

ということでrubyだと以下で取れる。
jira-rubyというgemがあるが今の所対応してないので、自分で取るしかないみたい

user_name = "hogehoge@hoge.com"
api_token = 'mogemogemoge'
jira_api_endpoint = "https://hogemogehoge.atlassian.net"

result_json = JSON.parse(`curl -u #{user_name}:#{api_token} #{jira_api_endpoint}`)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsチュートリアル 第1章

Railsチュートリアルを進めていく上での備忘録。

環境

PC:MacBook Pro
IDE:AWSCloud9
Rilsバージョン:5.1.6
Rubyバージョン:2.6.3

Cloud9を使用する場合、c9コマンドが使えると便利なので、インストールしておく。

$ npm install -g c9

作業しやすい様にbashrcも変更しておく。

bashrc
# .bashrc

export PATH=$PATH:$HOME/.local/bin:$HOME/bin

# load nvm
export NVM_DIR="$HOME/.nvm"
[ "$BASH_VERSION" ] && npm() { 
    # hack: avoid slow npm sanity check in nvm
    if [ "$*" == "config get prefix" ]; then which node | sed "s/bin\/node//"; 
    else $(which npm) "$@"; fi 
}
# [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"  # This loads nvm
rvm_silence_path_mismatch_check_flag=1 # prevent rvm complaints that nvm is first in PATH
unset npm # end hack


# User specific aliases and functions
alias python=python3.6

# modifications needed only in interactive mode
if [ "$PS1" != "" ]; then
    # Set default editor for git
    git config --global core.editor /usr/bin/nano

    # Turn on checkwinsize
    shopt -s checkwinsize

    # keep more history
    shopt -s histappend
    export HISTSIZE=100000
    export HISTFILESIZE=100000
    export PROMPT_COMMAND="history -a;"

    # Source for Git PS1 function
    git_type=$(type -t __git_ps1)
    if [ -z $git_type ] && [ -e "/usr/share/git-core/contrib/completion/git-prompt.sh" ]; then
        . /usr/share/git-core/contrib/completion/git-prompt.sh
    fi

    # Cloud9 default prompt
    _cloud9_prompt_user() {
        if [ "$C9_USER" = root ]; then
            echo "$USER"
        else
            echo "$C9_USER"
        fi
    }

    git_branch() {
        git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/(\1)/'
    }
    git_color() {
        [[ -n $(git status --porcelain=v2 2>/dev/null) ]] && echo 31 || echo 33
    }

    # PS1='\[\033[01;32m\]$(_cloud9_prompt_user)\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]$(__git_ps1 " (%s)" 2>/dev/null) $ '
    #PS1="╭─○ \[\033[01;32m\]${C9_USER}\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]$(__git_ps1 " (%s)")╰─○ "
    PS1="╭─○ \[\033[\$(git_color)m\]\$(git_branch)\[\033[00m\]\[\033[01;34m\]\w\[\033[00m\]$(__git_ps1 " (%s)")
╰─○ "
fi

[[ -s "$HOME/.rvm/environments/default" ]] && source "$HOME/.rvm/environments/default"
# Add RVM to PATH for scripting. Make sure this is the last PATH variable change.
export PATH="$PATH:$HOME/.rvm/bin"

peco-select-history() {
    declare l=$(HISTTIMEFORMAT= history | sort -k1,1nr | perl -ne 'BEGIN { my @lines = (); } s/^\s*\d+\s*//; $in=$_; if (!(grep {$in eq $_} @lines)) { push(@lines, $in); print $in; }' | peco --query "$READLINE_LINE")
    READLINE_LINE="$l"
    READLINE_POINT=${#l}
}
bind -x '"\C-r": peco-select-history'

#alias hub-pr="hub pull-request | open"
#alias git="hub"
alias ping-loop="while true; do ping www.google.com; sleep 3; done;"
alias gommit="git commit"
alias tag-gen="ripper-tags -e -R -f TAGS"
alias h="heroku"

alias la="ls -a"
alias lf="ls -F"
alias ll="ls -l"
alias lv="less"

alias e="emacs -nw"
alias g="git"
alias ga="git add -A"
alias gc="git commit -m"
alias gp="git push"
#alias gs="git status"
alias gd="git diff"
alias gl="git log --pretty='format:%Cblue[%ad] %Cgreen%an %Creset%s' --date=short"
alias gr="git remote -v"
alias v="vim"
alias r="rails"
alias bi="bundle install"
alias be="bundle exec"

変更したbashrcを反映する。

source ~/.bashrc

Gemとは

gem

Rubyで使用することのできる汎用性の高い機能をまとめたライブラリを管理するシステムの事を言う。また、それぞれのライブラリの事をgemという。

Bundler

Bundlerはgemを管理するためのgemです。

Gemfile

gemをインストールするための「設計図」のようなもの。
自身のアプリケーションに必要なgemを記述する。

Gemfile.lock

実施にgemをインストールした後の、「結果図」のようなもの。
gem同士は関連しあっていることが多いので、Gemfileに書いてあるgemの他にも必要なgemが生じます。Bundlerは、それらを自動でインストールし、Gemfile.lockに記述する。

bundle installとbundle updateの違い

bundle install

Gemfile.lockを元にgemのインストールを行う。
この時、Gemfile.lockに記述されていない、且つGemfileに記述されているgemが存在する場合、そのgemとそgemに関連するgemをインストール後、Gemfile.lockが更新される。

bundle update

Gemfileを元にインストールを行う。
その後、Gemfile.lockを更新する。

withoutオプション

$ bundle install --without 環境名  ⇨指定した環境のgemをインストールしなくできる

また、このオプションを一度実行すると、「.bundle/config」に設定が保存され、今後「bundle install」を実行する時に「--without」オプションを追加する必要がなくなる。

$ cat .bundle/config                                                     
---
BUNDLE_WITHOUT: "production"

githubへのプッシュ

$ git init    ⇨初期化
$ git add -A   ⇨プロジェクトのファイルをリポジトリに全て追加
$ git commit -m "Initialize repository"  ⇨リポジトリにコミット
↓公開鍵が作成されている事
$ git remote add origin git@github.com:s-yoshi210/hello_app.git  ⇨リポジトリ追加
$ git push -u origin master  ⇨リポジトリへのプッシュ

公開鍵作成方法

Herokuへのデプロイ

$ source <(curl -sL https://cdn.learnenough.com/heroku_install)   ⇨Herokuインストール
$ heroku login --interactive  ⇨Herokuログイン
$ heroku keys:add             ⇨SSHキー追加
$ heroku create               ⇨Herokuサーバーにアプリケーションの実行場所を作成
$ git push heroku master      ⇨Herokuにリポジトリをプッシュ

Herokuのアプリケーション名変更

$ heroku rename 任意のアプリ名  ⇨但し、既に誰かによって使われている名称は使用不可

Herokuのアプリケーション削除

$ heroku apps:destroy --app アプリ名 --confirm アプリ名
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsで作成した1対1のメッセージ機能をコードリーディング

はじめに

Railsで1対1のメッセージ機能を実装したアプリをコードリーディングします。Qiita初投稿のプログラミング初心者です!温かい目で見てください涙

開発環境

ruby: 2.6.5
rails: 5.2.4.1
postgresqlを使用

設計

  • テキスト形式で会話(メッセージのやり取り)ができる
  • 会話を取っている相手間でのみ情報が公開される
  • 会話を取っている相手間のメンバー毎に「会話ひとつ」が定義される

要件

  • ユーザ一覧、あるいはユーザ個別の画面からメッセージ画面を表示できる
  • メッセージ画面では相手と自分の名前が表示される
  • メッセージ画面には個々のメッセージの発言者と内容が時系列で表示される
  • メッセージ一覧リンクから過去に自分がやりとりしたメッセージすべてを見ることができる

テーブルの仕様スクリーンショット 2020-01-22 13.21.26.png

このアプリはログインできるUser機能を持ったアプリケーションに機能を追加して行く形で実装して行きました。

コード内容

route

config/route.rb
Rails.application.routes.draw do
  # 省略
  resources :conversations do
    resources :messages
  end
end

controller

app/controllers/conversations_controller.rb
class ConversationsController < ApplicationController
  before_action :authenticate_user
  def index
    @conversations = Conversation.all
  end

  def create
    if Conversation.between(params[:sender_id], params[:recipient_id]).present?
      @conversation = Conversation.between(params[:sender_id], params[:recipient_id]).first
    else
      @conversation = Conversation.create!(conversation_params)
    end
    redirect_to conversation_messages_path(@conversation)
  end

  private

  def conversation_params
    params.permit(:sender_id, :recipient_id)
  end
end
app/controllers/messages_controller.rb
class MessagesController < ApplicationController
  before_action do
    @conversation = Conversation.find(params[:conversation_id])
  end

  def index
    @messages = @conversation.messages

    if @messages.length > 10
      @over_ten = true
      @messages = Message.where(id: @messages[-10..-1].pluck(:id))
    end

    if params[:m]
      @over_ten = false
      @messages = @conversation.messages
    end

    if @messages.last
      @messages.where.not(user_id: current_user.id).update_all(read: true)
    end

    @messages = @messages.order(:created_at)
    @message = @conversation.messages.build
  end

  def create
    @message = @conversation.messages.build(message_params)
    if @message.save
      redirect_to conversation_messages_path(@conversation)
    else
      render 'index'
    end
  end

  private

  def message_params
    params.require(:message).permit(:body, :user_id)
  end
end

model

app/models/conversation.rb
class Conversation < ApplicationRecord
  belongs_to :sender, foreign_key: :sender_id, class_name: 'User'
  belongs_to :recipient, foreign_key: :recipient_id, class_name: 'User'

  has_many :messages, dependent: :destroy

  validates_uniqueness_of :sender_id, scope: :recipient_id

  scope :between, -> (sender_id,recipient_id) do
    where("(conversations.sender_id = ? AND conversations.recipient_id =?) OR (conversations.sender_id = ? AND  conversations.recipient_id =?)", sender_id,recipient_id, recipient_id, sender_id)
  end

  def target_user(current_user)
    if sender_id == current_user.id
      User.find(recipient_id)
    elsif recipient_id == current_user.id
      User.find(sender_id)
    end
  end
end
app/models/message.rb
class Message < ApplicationRecord
  belongs_to :conversation
  belongs_to :user

  validates_presence_of :body, :conversation_id, :user_id

  def message_time
    created_at.strftime("%m%d%y at %l:%M %p")
  end
end

view

app/views/conversations/index.html.erb
<table class="table table-hover">
  <thead>
    <h2>メッセージ一覧</h2>
  </thead>
  <tbody>
    <% @conversations.each do |conversation| %>
      <td>
        <% if conversation.target_user(current_user).present? %>
          <%= link_to conversation.target_user(current_user).name, conversation_messages_path(conversation)%>
        <% end %>
      </td>
    <% end %>
  </tbody>
</table>
app/views/messages/index.html.erb
<% if @over_ten %>
  <%= link_to '以前のメッセージ', '?m=all' %>
<% end %>

<div class="ui segment">
  <% @messages.each do |message| %>
    <% if message.body.present? %>
      <div class="item">
        <div class="content">
          <div class="header"><strong><%= message.user.name %></strong> <%= message.message_time %></div>
          <div class="list">
            <div class="item">
              <i class="right triangle icon"></i>
              <%= message.body %> /
              <% if message.user == current_user %>
                <%= message.read ? '既読' : '未読' %>
              <% end %>
            </div>
          </div>
        </div>
      </div>
    <% end %>
  <% end %>
</div>

<%= form_with(model: [@conversation, @message]) do |f| %>
  <div class="field">
    <%= f.text_area :body, class: "form-control" %>
  </div>
    <%= f.text_field :user_id, value: current_user.id, type: "hidden"  %>
  <div>
    <%= f.submit "メッセージを送る" %>
  </div>
<% end %>

コードリーディング

それではコードリーディングしてきます。
わかりにくかった部分を記載します。

わからなかった部分のコードその1

app/models/conversation.rb
scope :between, -> (sender_id,recipient_id) do
  where(["(conversations.sender_id = ? AND conversations.recipient_id = ?) OR (conversations.sender_id = ? AND conversations.recipient_id = ?)", sender_id ,recipient_id, recipient_id, sender_id])
end
app/controllers/conversations_controller.rb
def create
  if logged_in?
    if Conversation.between(params[:sender_id], params[:recipient_id]).present?
      @conversation = Conversation.between(params[:sender_id], params[:recipient_id]).first
    else
      @conversation = Conversation.create!(conversation_params)
    end
    redirect_to conversation_messages_path(@conversation)
  else  
    redirect_to root_path
  end 

# 省略
  private  

  def conversation_params
    params.permit(:sender_id, :recipient_id)
  end 
end

下記のように理解しました。その1

まずログインしているか確認して(ログインしてなかったらルートパスへ)、

if Conversation.between(params[:sender_id], params[:recipient_id]).present?
      @conversation = Conversation.between(params[:sender_id], params[:recipient_id]).first
    else
      @conversation = Conversation.create!(conversation_params)
    end

このコードでConversationテーブルに、送り主(メッセージボタンを押した人、ログインユーザー)の idにsender_idがあって、かつ、受け取り主(メッセージボタンを押された人)のidにrecipient_idがある、もしくは受け取り主のidにsender_idがあって、かつ、送り主のidにrecipient_idがある(つまり、チャットルームがある)場合とそうでない場合で分ける。

チャットルームがある場合、
そのチャットルームに必要な情報が紐づいている、Conversationの1レコードの情報を@conversationに代入。
なぜfirstクエリメソッドを使っているかというと、Conversationに「sender_idに送り主のid、recipient_idに受け取り主のid」の組み合わせと「sender_idに受け取り主のid、recipient_idに送り主のid」の組み合わせの2つパターンどちらかが存在するから。(2つは同じチャットルーム)

チャットルームがない場合は、Conversationに新規でsender_idrecipient_idに送り主と送り主のidで作成し、@conversationに代入。

そして、redirect_to conversation_messages_path(@conversation)@conversationとともにmessageのコントローラ、indexアクションに飛ぶ。

わからなかった部分のコードその2

app/controllers/messages_controller.rb
def index
  @messages = @conversation.messages


  if @messages.length > 10
    @over_ten = true
    @messages = Message.where(id: @messages[-10..-1].pluck(:id))
  end


  if params[:m]
    @over_ten = false
    @messages = @conversation.messages
  end


  if @messages.last
    @messages.where.not(user_id: current_user.id).update_all(read: true)
  end


  @messages = @messages.order(:created_at)
  @message = @conversation.messages.build
end

下記のように理解しました。その2

@messages = @conversation.messages

@messagesにconversations#createから送られてきた@conversation(そのチャットルーム)に、関連された全メッセージを代入。

if @messages.length > 10
    @over_ten = true
    @messages = Message.where(id: @messages[-10..-1].pluck(:id))
  end

もし、@messagesが10個より多かった場合、
10より大きいというフラグを有効にする。
@messages[-10..-1]で10件のみメッセージ情報を取得、しかしこの書き方だとArrayクラスになり、whereクエリメソッドなどが使えないため、pluckで一度配列でidのみを受けり、その配列をもとにwhereで検索して@messagesに代入。ちなみにmessages[-10..-1]は後ろから10番目と後ろから1番目の間のメッセージ情報という意味。

if @messages.last
    @messages.where.not(user_id: current_user.id).update_all(read: true)
  end

最新(最後)のメッセージが存在している場合、ログインユーザー以外のメッセージを全て抽出して、readカラムをtrueにする(既読にする)。ログインユーザーはread: falseのままで、チャット相手がログインしてmessages#indexに飛んでくれないとread: trueにならない。

@message = @conversation.messages.build

そのチャットルームに関連された、messageのインスタンスを生成。

わからなかった部分のコードその3

app/views/messages/index.html.erb
<% if @over_ten %>
  <%= link_to '以前のメッセージ', '?m=all' %>
<% end %>

<div class="ui segment">
  <% @messages.each do |message| %>
    <% if message.body.present? %>
      <div class="item">
        <div class="content">
          <div class="header"><strong><%= message.user.name %></strong> <%= message.message_time %></div>
          <div class="list">
            <div class="item">
              <i class="right triangle icon"></i>
              <%= message.body %> /
              <% if message.user == current_user %>
                <%= message.read ? '既読' : '未読' %>
              <% end %>
            </div>
          </div>
        </div>
      </div>
    <% end %>
  <% end %>
</div>

<%= form_with(model: [@conversation, @message]) do |f| %>
  <div class="field">
    <%= f.text_area :body, class: "form-control" %>
  </div>
    <%= f.text_field :user_id, value: current_user.id, type: "hidden"  %>
  <div>
    <%= f.submit "メッセージを送る" %>
  </div>
<% end %>

下記のように理解しました。その3

<% if @over_ten %>
  <%= link_to '以前のメッセージ', '?m=all' %>
<% end %>

このメッセージ数が10個以上の場合、以前のメッセージのリンクが表示され、クリックすると/conversations/:id/messages?m=allが送られ、

app/controllers/messages_controller.rb
if params[:m]
    @over_ten = false
    @messages = @conversation.messages
  end

の処理が実行される。なお、allの部分は任意の文字でよくて、とにかくmに変数を入れてあげればいい。

app/model/conversation.rb
def target_user(current_user)
    if sender_id == current_user.id
      User.find(recipient_id)
    elsif recipient_id == current_user.id
      User.find(sender_id)
    end
  end

ログインユーザーに関連している他のユーザーの情報を取得するメソッド。
ログインユーザーがチャットルームを作成したパターンと、ログインユーザーの相手がチャットルームを作成したパターンのどちらかで存在するから、どちらにも対応できるようになっている。

バリデーションに引っかからない、エラーを吐かない場合の全体の流れ 

user/index
↓メッセージボタン
conversations_controller(create)
↓redirect_to
massage_controller(index)

massages/index ※
↓メッセージを送るボタン
massages_controller(create)

※に行く

以上です。

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

各レコードの計算結果の合計を出す

配列を覚えると大変便利なことに気付いた。

1つの注文に対して複数の注文商品が登録されており、1つの注文の合計金額をorders/show画面に出したい。
使用するメソッドは今回.injectを使う。
関係は以下とする。

app/assets/models/order.rb
class Order < ApplicationRecord
 has_many :order_products, dependent: :destroy
end
app/assets/models/order.rb
class OrderProduct < ApplicationRecord
 belongs_to :order
end

OrderProductモデルにあるのは以下のカラム
・product_name(商品名)
・quantity(数量)
・price(単価)

orders_controller.rb
def show
 @order = Order.find(params[:id])
 @total = [] #配列の初期化
 @order.order_products.each do |order_product|
  @total << order_product.quantity * order_product.price
 end
 #@orderの持つorder_productの数だけ@totalという配列に各レコードの小計を<<でつっこんでいく
end
orders/show.html.erb
@total.inject{|sum, n| sum + n }

これだけ。最初配列をよく知らなくてめちゃくちゃ悩んだ。
送料でもつけたければ @total.inject{|sum, n| sum + n } + 送料 でもいい。
ただしそれをすると注文商品レコードが1つしか該当しない場合は反映されないので条件分岐が必要。

orders/show.html.erb
<% if @total.count = 1 %>
 <%= @total + 送料 %>
<% else %>
 <%= @total.inject{|sum, n| sum + n } + 送料 %>
<% end %>

こんな感じ。

これが例えば注文一覧に埋め込みたい場合は.findで定義付けできないのでhtmlにそのまま書き込む。

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

rails gするとundefined local variable or method `config' for main:Object (NameError)が出て困った話

はじめに

spring stopすればいいよ!とか書いてあって対処したもののさっぱり解決しなかったので、色々試行錯誤した時の話です。

どんなエラーが出たか

以下のようなエラーが出ました。
configがなんか変らしいです。

/Users/t-nakagawa/rails/config/application.rb:46:in `<top (required)>': undefined local variable or method `config' for main:Object (NameError)
        from /Users/t-nakagawa/rails/vendor/bundle/ruby/2.6.0/gems/spring-2.0.2/lib/spring/application.rb:92:in `require'
        from /Users/t-nakagawa/rails/vendor/bundle/ruby/2.6.0/gems/spring-2.0.2/lib/spring/application.rb:92:in `preload'
        from /Users/t-nakagawa/rails/vendor/bundle/ruby/2.6.0/gems/spring-2.0.2/lib/spring/application.rb:153:in `serve'
        from /Users/t-nakagawa/rails/vendor/bundle/ruby/2.6.0/gems/spring-2.0.2/lib/spring/application.rb:141:in `block in run'
        from /Users/t-nakagawa/rails/vendor/bundle/ruby/2.6.0/gems/spring-2.0.2/lib/spring/application.rb:135:in `loop'
        from /Users/t-nakagawa/rails/vendor/bundle/ruby/2.6.0/gems/spring-2.0.2/lib/spring/application.rb:135:in `run'
        from /Users/t-nakagawa/rails/vendor/bundle/ruby/2.6.0/gems/spring-2.0.2/lib/spring/application/boot.rb:19:in `<top (required)>'
        from /Users/t-nakagawa/.rbenv/versions/2.6.3/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'
        from /Users/t-nakagawa/.rbenv/versions/2.6.3/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'
        from -e:1:in `<main>'

該当箇所のコード

spring2.0.2のコードを確認します。
パス関連でエラーを起こしているようなので、configフォルダが怪しいということが分かります。

require Spring.application_root_path.join("config", "application")

解決しなかった対処方法

spring stopしてはダメでした。これはこれで立ち上げておくとrails gがエラーになるみたいなんで、停止しておいた方が無難だと思います。

解決した対処方法

この作業をする直前にやったconfig/application.rbの作業が良くなくて上手くconfigが処理出来なかったようです。
一旦git restoreで切り戻してrails gすることで無事コマンドが終了してファイルが作成できました。

終わりに

何か困った際は一旦切り戻しするの重要。
念の為、周りの人にspringのバージョンが同じであるかの確認とコマンドが成功しているかの確認は行いました。
みんな駄目ならこの環境自体が駄目で自分だけ駄目なら自分の変更による影響というのが分かるので、やっておきましょう。

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

docker-compose up出でるエラー

概要

いつものようにDocker-compose upするとサーバーが立ち上がらない。

具体的には、↓こんな感じでエラーが出ている。

スクリーンショット 2020-01-11 23.34.02.png

〈注意すべきポイント〉
bundler: failed to load command: rails (/usr/local/bundle/bin/rails)
Bundler::GemNotFound: Could not find ast-2.4.0 in any of the sources

解決策

docker-compose build
で解決!!

原因(ここ重要!)

原因によっては対処法が変わってくる。
今回の場合は、前回このアプリを利用した時にgemを追加して
bundle install
したあとに、サーバーの立ち上げしていなかった。

参考サイト

https://techracho.bpsinc.jp/ebi/2017_05_25/40438

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

Twitter APIで取得したアイコン画像の大きさを変える【Ruby on Rails】

はじめに

TwitterAPIの取得にomniauthを使っています。
またomniauthの基本的な使い方については省略しています。
omniauthの基本的な使い方については別記事を参照してください。

開発環境

Ruby 2.6.1
Ruby on Rails 6.0.1

Twitterアイコン画像を取得する

app/controllers/twitter_controller.rb
class TwitterController < ApplicationController
  # 基本的なomniauthの使い方は省略
  def twitter
    auth_hash = request.env["omniauth.auth"]
    @image_url = auth_hash[:info][:image]
  end
end

このままでは48x48にリサイズされたアイコン画像のURLを取得してしまいます。
小さい!当然CSSなどで拡大してもボヤボヤになってしまいます。

大きなTwitterアイコン画像の取得方法

omniauthの設定に使ったファイルを書き換えます。

config/initializers/omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
  provider :twitter,
  Rails.application.credentials.twitter[:key],
  Rails.application.credentials.twitter[:secret_key],
  # 以下を追加する
  {
    :image_size => "original"
  }
end

こうすることによって、本来のサイズでのアイコン画像のURLを取得できます。

参考

omniauth-twitter(GitHub):https://github.com/arunagw/omniauth-twitter

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

【Rails】 DataTables 検索結果の保持方法

はじめに

これまでに Rails + DataTable 関連の記事を書いてきましたが、別のページから戻ってきた時に検索結果を保持していて欲しいなどのユーザーの要望があるかと思いますので、そのような細かい設定の変更方法について今回はまとめさせていただきます。

関連リンク

関連リンクを下記に載せておくので、必要であれば参考にしてください。。

検索結果の保持方法

次の2つを設定するだけで、検索結果を保持してくれるようになります。

  • stateSave
  • stateDuration
app/assets/javascripts/concern/datatables.coffee
class @DataTables
  # *** 省略 ***
  drawTable: ->
    # *** 省略 ***

    # stateSave : 別のページから戻ってきた時に検索結果を保持するかどうかを設定可能(trueが保持)
    stateSave: true,

    # stateDuration : stateSave の検索結果の保持する時間を設定可能(1秒単位で設定可能)
    stateDuration: 60 * 15,   # sec * min

  # *** 省略 ***

まとめ

英語のドキュメントさえ読んで使いこなすことができれば、DataTablesはユーザーのニーズを柔軟に反映した高機能なテーブルを短時間で実装することができます。
Rails と組み合わせると爆速で開発が捗ると思っているので、ぜひ活用してみてください。

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

RSpec で ある要素のみが入った配列かどうかを確認したいときは contain_exactly を使う

概要

a,b,c のリソースがあるときに、a,c のみが順番は関係なく返されることをテストしたいときには、contain_exactly が便利です。

コード

let!(:user_a) { create(:user, name: 'a') }
let!(:user_b) { create(:user, name: 'b') }
let!(:user_c) { create(:user, name: 'c') }

it 'returns ac users do
  expect(User.hoge).to contain_exactly(user_a, user_c)
end

順番もテストするときは素直に eq matcher で配列を使う。

it 'returns ac users do
  expect(User.hoge).to eq [user_a, user_c]
end

参考:
contain_exactly matcher
https://relishapp.com/rspec/rspec-expectations/v/3-9/docs/built-in-matchers/contain-exactly-matcher

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

自動デプロイ(Capistrano)でエラー mkdir: ディレクトリ `/var/www' を作成できません: 許可がありません

Capistranoで自動デプロイをしていると、エラーが発生しました

Image from Gyazo

テキストだと下記ですね

mkdir stdout: Nothing written
mkdir stderr: mkdir: ディレクトリ `/var/www' を作成できません: 許可がありません
mkdir: ディレクトリ `/var/www' を作成できません: 許可がありません

このエラー"var/www/アプリ名"のディレクトリにしている場合は、パーミッションの問題等だと思いますが、、、

「 "var/www/アプリ名"使ってないんですけど!!!

 なんでエラーが出るんだよ!!!
 "www"が気に食わないから、別のフォルダ名にしてるんですけど!!!
 他の記述も確認したけど、全部"var/www/アプリ名"書いてねええよ!!! 」

という人向けに対処方法を解説します。

解決策

結論: Capistranoのデフォルト設定が"var/www/アプリ名"を読み込むので、オプション設定で変更する。

deploy.rb
# 下記を挿入してください
set :deploy_to, '/var/○○○(ディレクトリ名)/○○○(アプリ名)'

これを挿入することで、EC2にあるアプリの保存場所を正しく認識され、Capistranoで読み込みが可能となります。

あとは自動デプロイを実行しましょう

# アプリケーションのディレクトリで実行する
$ bundle exec cap production deploy

今度はうまくいくはずです。

[参考](https://capistranorb.com/documentation/getting-started/configuration/)

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

Capistranoによる自動デプロイエラー(Rails):Missing encryption key to decrypt file with. Ask your team for your master key and write it to

手動デプロイできたのに、自動デプロイができない!!!

そんでもって、master.keyがないエラーが表示されるんですけど、どうすればいいんだ!!!

となる方いると思います。

下記をすれば改善するので、早速取り組みましょう

Master.keyがないエラー( Missing encryption key to decrypt file with. Ask your team for your master key and write it to )

このエラーが表示されたということは、『 本番環境にあるmaster.keyをうまく読み込めていない 』ことを意味します。

rake stdout: Nothing written
rake stderr: Missing encryption key to decrypt file with. Ask your team for your master key and write it to /var/○○○(アプリを格納しているディレクトリ名)/○○○(アプリ名)/releases/20200121124714/config/master.key or put it in the ENV['RAILS_MASTER_KEY'].

なので現状として可能性は二つです。

  • master.keyを作成していない。
  • master.keyを作成する場所が間違っている。

ここまで作業を進めている人は,master.keyを作成しているはずです。
>つまり、master.keyの作成場所をまちがている可能性が高いです。
>作成していない人も解説しますので、このまま読み進めてください。

# 誤解が生まれやすいmaster.keyの作成場所

✖︎ アプリ名>config>master.key       #こっちだとエラー
○ アプリ名>shared>config>master.key  #正しい作成場所

おそらく、上記のようにmaster.keyの作成場所に誤りがある可能性が高いです。
ですから、

ターミナル(EC2)
[ec2-user@ip-172-31-23-189 <リポジトリ名>]$ cd shared 
[ec2-user@ip-172-31-23-189 shared ]$ cd config
[ec2-user@ip-172-31-23-189 config ]$ ls
>ここでmaster.keyがない場合、 master.keyを作成してください。

master.keyを作成する場合、、、

ターミナル(ローカル)
アプリ名 $ vi config/master.key
>master.keyを中身がわかります。ここでコピーしましょう
>間違っても編集しないようにしましょう

EC2にmaster.keyを作成しましょう

ターミナル(EC2)
[ec2-user@ip-172-31-23-189 <リポジトリ名>]$ cd shared/config
[ec2-user@ip-172-31-23-189 config ]$ vi master.key
>編集画面が出るので
>ローカルのmaster.keyをコピペします。
>:wpで保存しましょう

あとは、deploy.rbにmaster.keyを読み込ます設定をします。
下記を追加してください

deploy.rb
set :linked_files, fetch(:linked_files, []).push("config/master.key")

これで改善します

# アプリケーションのディレクトリで実行する
$ bundle exec cap production deploy
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む