20190607のRailsに関する記事は19件です。

Railsチュートリアル3日目:1章クリア!

今日やったこと

・1章初めからやり直し
・1章クリア!

初めからやり直したら、Gitの設定がうまくいった!

昨日はマージをしようとしたら、.c9/metadata...というファイルが邪魔をしてマージ出来ませんでした。.gitのファイル位置がおかしいことに気づき、今日はgit initする時のディレクトリに注意しながら、最初から1章をやり直してみました!

昨日の気づき(.gitの位置がおかしい)
無題2.png

その結果、無事にマージすることが出来ました!

ec2-user:~/environment/hello_app (master) $ git branch
* master
  modify-README
ec2-user:~/environment/hello_app (master) $ git merge modify-README
Updating f807c10..fd0d9f8
Fast-forward
 README.md | 9 +++++++++
 1 file changed, 9 insertions(+)

その後は順調に進んで、1章をクリアすることができました!
(写真撮り損ねた…)

ただ、また疑問が出てきて、cloud9を閉じてからもう一回herokuに入ろうとすると、

ec2-user:~/environment/hello_app (master) $ heroku --versionbash: heroku: command not found

あれ?herokuのバッシュが消えてる?
まだcloud9の仕様がわからず、一度閉じたら何がリセットされて何がそのままの設定で残っているのか、全然分かっていない気がします。
明日はこの辺りをググってみてcloud9やバッシュなど全体的に学んでいきたいと思いました。

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

rails モデルについて

modelの作成

$ rails g model モデル名

上記をを実行するとマイグレーションファイルが作成される。  
これを使うことでSQL文を書かずして、railsからmysqlなどのデータベースを操作することができる。作成するだけでは操作できない。  

マイグレーションファイルの実行

$ rails db:migrate

このコマンドでデータベースにモデル名のテーブルが作成される。
railsのルールとして一度実行されたファイルに変更をしてはならない。  
データベースに変更を加えたい場合、rails db:migrate:resetでもう一度作り直すかカラムを追加する方法がある。

カラムの追加

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

テーブル名はモデル名の小文字複数形ですが、上記ではキャメルケース記法なので大文字になります。

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

[error]rails aborted! I18n::MissingTranslationData: translation missing: en.faker.name.first_name

rails aborted!I18n::MissingTranslationData: translation missing: en.faker.name.first_name

bundle exec rails db:resetしようとしたら、上記のエラーがでた

解決方法

下記をコピペ*日本語での設定とかそこらへん

local.rb
require 'i18n'
I18n.locale # => :en
I18n.locale = :ja
I18n.locale # => :ja
require 'faker'
Faker::Config.locale # => :ja
Faker::Internet.email # => ".@.com"

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

[rails error]rails aborted! I18n::MissingTranslationData: translation missing: en.faker.name.first_name

rails aborted!I18n::MissingTranslationData: translation missing: en.faker.name.first_name

bundle exec rails db:resetしようとしたら、上記のエラーがでた

解決方法

下記をコピペ*日本語での設定とかそこらへん

local.rb
require 'i18n'
I18n.locale # => :en
I18n.locale = :ja
I18n.locale # => :ja
require 'faker'
Faker::Config.locale # => :ja
Faker::Internet.email # => ".@.com"

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

RailsでTwitterログイン認証を実装

RailsアプリでTwitterログインを実装する方法をまとめます。

deviseの導入

メールアドレスやSNSアカウントを用いてのログインを、簡単に実装するGem「devise」と、OAuth認証に必要なGem「omniauth」「omniauth-twitter」を導入します。

Gemfile.rb
gem 'devise'
gem 'omniauth'
gem 'omniauth-twitter'

続いて、bundle installを行います。

bundle install

※ライブラリはサーバーの起動時に読み込まれるので、ここでいったんサーバーを停止し、再起動しましょう。

deviseの設定

アプリケーションにdeviseをインストールします。

rails g devise:install

1から4までのステップが書かれた英文が表示されるので、指示通りにコーディングします。

デフォルトURL

config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }をconfig/environments/development.rbに追加します。

config/environments/development.rb
Rails.application.configure do
  # Settings specified here will take precedence over those in config/application.rb.

・・・(:場所はどこでも良いです)

  config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
end

root_urlを指定

ルートURLにアクセスした際のページを作成します。

rails g controller Pages index

Pagesコントローラーには、indexアクションを、ビューファイルindex.html.erbを追加しています。

フラッシュメッセージを指定

ログインが成功した際に表示する、フラッシュメッセージを、application.html.erbの

下に指定します。
app/views/layouts/application.html.erb
・・・()
</head>
<body>
  <p class="notice"><%= notice %></p>
  <p class="alert"><%= alert %></p>

    <%= yield %>
</body> 

devise用のviewを作成

rails g devise:views

app/views/devise/以下にファイルが作成されます。

Userモデルを作成

rails g devise User

※モデル名なので単数形

これにより、dbフォルダ下にマイグレーションファイルが作成されます。

ユーザーモデルを編集

user.rbのdevise:以下に「:omniauthable」を追加します。

model/user.rb
devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable, :omniauthable

データベースにカラムを追加

deviseはデフォルトでメールアドレスとパスワードした扱うことができません。ツイッターログインで扱うデータのカラムをあらかじめ追加しておく必要があります。

以下のカラムを追加します。

  • ユーザーID(uid)
  • プロバイダー(provider)
  • アカウント名(name)
  • アカウントID(nickname)
  • 場所(location)
  • 画像(image)
rails g migration AddColumnsToUsers uid:string provider:string name:string nickname:string location:string image:string

ここまで終わったら、以下マイグレーションを実行します。

rails db:migrate

Twitter Developerの登録

API Keyと、Secret Keyを取得します。(省略)

取得した2つのキーを貼り付け

config/initializer/devise.rb
Devise.setup do |config|
・・・()
  config.omniauth :twitter, 'API key(各自取得したキーを入れてください)', 'API secret(各自取得したキーを入れてください)'
end

Userモデルを編集する

model/user.rb
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable, :omniauthable


    def self.find_for_oauth(auth)
    user = User.where(uid: auth.uid, provider: auth.provider).first

    unless user
      user = User.create(
        uid:      auth.uid,
        provider: auth.provider,
        email:    User.dummy_email(auth),
        password: Devise.friendly_token[0, 20],
        image: auth.info.image,
        name: auth.info.name,
        nickname: auth.info.nickname,
        location: auth.info.location
      )
    end

    user
  end

  private

  def self.dummy_email(auth)
    "#{auth.uid}-#{auth.provider}@example.com"
  end

end

Teitter認証の際に取得した各データを、データベースに格納しています。メールアドレスがない場合はdevise側でエラーが発生してしまうので、ダミーのアドレスを作成しています。

コールバックの設定

app/controllers/以下に「users」フォルダを作成し、その下に「omniauth_callbacks_controller.rb」ファイルを作成します。

omniauth_callbacks_controller.rbは以下のように編集します。

omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def twitter
    callback_from :twitter
  end

  private
  def callback_from(provider)
    provider = provider.to_s

    @user = User.find_for_oauth(request.env['omniauth.auth'])

    if @user.persisted?
      print("persisted true")
      flash[:notice] = I18n.t('devise.omniauth_callbacks.success', kind: provider.capitalize)
      sign_in_and_redirect @user, event: :authentication
    else
      print("persisted false")
      session["devise.#{provider}_data"] = request.env['omniauth.auth']
      redirect_to controller: 'sessions', action: 'new'
    end
  end
end

ルーティングの設定

config/route.rbのdevise_forを、以下のように編集します。

config/route.rb
devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks' }

ビューファイルを整える

ビューファイルを以下のように整えます。

index.html.rb
<% if user_signed_in? %>
  <%=current_user.email%>
  <%=current_user.name%>
  <%=current_user.nickname%>
  <%=current_user.image%>
  <%=current_user.location%>
  <%= link_to "ログアウト", destroy_user_session_path, method: :delete %>
<% else %>
  <%= link_to "ログイン", new_user_session_path %>
  <%= link_to " Twitterでログイン/登録", user_twitter_omniauth_authorize_path %>
<% end %>

以上でツイッター認証ログインが可能になりました。

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

Ruby on Railsを開発する時にいれておきたいAtomのパッケージ

ファイル検索・移動系

Rails Transporter

Controller/Model/View間の移動をコマンド一つできる。

RubyMineにもそんな機能があったからAtomにもないかなと思って探したら発見。
ControllerからViewを探すのは結構めんどくさいからかなり重宝している。

atom-fuzzy-grep

高速簡易グレップ。

Atomの標準でプロジェクト内全体の検索はできるが、このfuzzy-grepはより早く検索したい時におすすめ。

advanced-open-file

現在いるファイルから別のファイルを検索したい時に使える。

RailsだとViewでpartialの中身を確認したくて、別のファイルに移動する時に使える。
Viewって似たようなファイル名が多いから、grepしても検索結果が多くて判別できないため、これを使っている。

goto-definition

関数定義場所のファイルにジャンプする。

私はatom-fuzzy-grepでも十分かなと思うが、直接関数に飛んでくれるのでたまに使う。

Git系

git-blame

行毎に最終変更者が確認できる。

その名の通り、誰かのせいしたい時に使う。(笑)

git-time-machine

ファイルの過去の変更履歴を時系列で確認できる。

git-blameでは追えない犯人を追う時に使う。

その他

platformio-ide-terminal

Atom上にターミナルを開く。

初回起動の場所をプロジェクトのrootパス等に設定できるため、gitのコマンド等使いたい時に使用。
ちょっと重い。。

(更新中。。)

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

cloud9→ローカルに移したら写真投稿ができなくなった

はじめに

SNS風の写真投稿アプリを久々にローカルで確認してみたところ、肝心の写真投稿がバリデーションに引っかかり、投稿はもちろん、テストなども通らなくなってしまっていました。

原因

結果を言ってしまうと、Minimagickを使うのに必要なImageMagickを、ローカルでインストールしていませんでした。

cloud9時代にImageMagickをインストールした自体あまり覚えていなかったので、気付くのが遅れてしまいました。

$ brew install imagemagick

これで今まで通り、問題なく写真投稿できるようになりました!

参考

Rails gem MiniMagick を利用して画像ファイルをリサイズする
RubyでImageMagick使うにはMiniMagickの方がよい

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

resize_to_limit(fit) 投稿されたメッセージをビューに表示させる際に、messageとimageが異常に大きい

今回の問題

chat-space/app/views/messages/_message.html.hamlに

.main-content__chat--contents
  .chat-message
    .chat-message--name
      = message.user.name
    .chat-message--time
      = message.created_at.strftime("%Y/%m/%d %H:%M")
    .chat-message--comment
    - if message.content.present?
      %p.lower-message__content
        = message.content
    = image_tag message.image.url, class: 'lower-message__image' if message.image.present?

と記述し、ビューを表示させると、

スクリーンショット 2019-06-07 12.32.28.png

imageも大きいが、messageもimageと同じ大きさで表示されてしまう。

やりたいゴール

ちょうどいい大きさで、messageのみの時は、それ相応の大きさにする

スクリーンショット 2019-06-07 12.34.33.png

原因

app/assets/stylesheets/_groups.scssに高さを指定してしまっていた。

&__chat--contents{
    background-color: #fafafa;
    height: 500px;
    padding: 10px 20px;
    overflow: scroll; 

heightをコメントアウトするとmessageのみの表示がちょうどいい大きさに変化した。

スクリーンショット 2019-06-07 12.54.42.png

しかし画像が馬鹿でかすぎる

process resize_to_limit: [XXX, XXX]

resize_to_fitは縦横比を維持したまま、width, heightをXXXpxにリサイズしてくれる

app/uploaders/image_uploader.rbに

  process resize_to_limit: [200, 200]

を記述する

結果

スクリーンショット 2019-06-07 17.36.18.png

ちょうどいい感じに表示されるようになりました!

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

Decorator

Decorator の使い方の備忘録です。

インストール

Gemfile
gem 'active_decorator'

Decoratorを生成

$ rails g decorator user

使い方

app/decocators/user_decorator.rb
module UserDecorator
  def full_name
    "#{family_name} #{given_name}"
  end
end

参考

Decorator

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

[Rails error]raise LoadError, "Unable to autoload constant #{qualified_name}, expected #{file_path} to define it" unless from_mod.const_defined?(const_name, false)

errpr!

raise LoadError, "Unable to autoload constant #{qualified_name}, expected #{file_path} to define it" unless from_mod.const_defined?(const_name, false)

原因

controllerの名称間違いだった

tests_controller.rb
TestController < ApplicationController
  def index
  end

解決

上記のTestController < ApplicationControllerTestsController < ApplicationControllerに直したら治った

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

railsでhas_manyなmodelを条件付きで取ってるのをあらかじめ定義しておく

近況
転職してRailsマンに戻りました。(しばらくpythonおじさんだった)

やりたいこと

has_manyしているテーブルをselectするとき、コントローラの中でfindとか書かずに予めmodelの中に定義しておきたい。
(見た目スッキリさせたいとか、変数当てる必要がないから定義済みにしといた方が安全とか色々理由はあると思う)

どう書く?

lambdaを使うのが良いみたい。

app/models/user.rb
class User < ApplicationRecord
    # Userに紐づく予定を記録したSchedulesテーブルがあり、そこから稼働日に該当する予定だけを抜き出したい
    has_many :work_day, lambda { where not_work_day: True }, class_name: Schedules.name
end

コントローラからはこんな感じで書く。

app/models/user.rb
def work_day
    @result = User.find(id).includes(:work_day)
end

参考: https://blog.falconsrv.net/articles/415

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

ブラウザから S3 へダイレクトアップロード

はじめまして、streampackチームのminsuです。

やりたいこと

S3へファイルをアップロードする際に、Railsサーバを通すことなくブラウザからS3へのダイレクトアップロードを実装してみます。
ブラウザからS3にファイルを直接アップロードすることにより、余分な負荷を削減できるメリットがあります。
また、Railsのgem aws-sdkを利用して生成したpresigned POSTを利用することでブラウザにaws credentialsを持たせる事なくアップロードを行えます。

AWSリソースの準備

まず、AWSアクセスキーを作成してACCESS_KEY_ID, SECRET_ACCESS_KEYを取得してください。

次にS3のバケットの作成します。
作成したバケットのCORSの設定を行い、外部からのPOSTを許可します。

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
  <CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedMethod>PUT</AllowedMethod>
    <AllowedMethod>POST</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    <AllowedHeader>*</AllowedHeader>
  </CORSRule>
</CORSConfiguration>

AllowedOrigin、AllowedHeaderはワイルドカードを設定しましたが、環境に合わせて変更してください。

Railsでpresigned POSTを返すアクションを設定

まずは環境変数に必要な値を持たせておきます。

.env
AWS_ACCESS_KEY_ID=your-key-id
AWS_SECRET_ACCESS_KEY=your-secret-key
BUCKET=your-bucket-name

次にGemfileに

Gemfile
gem `aws-sdk', '~3'

を追加して

$ bundle install

そして環境変数に保存した値を使ってS3のインスタンスを作成します。

config/initializers/aws.rb
Aws.config.update({
    region: 'ap-northeast-1',
    credentials: Aws::Credentials.new(ENV['AWS_ACCESS_KEY_ID'], ENV['AWS_SECRET_ACCESS_KEY']),
})

S3_BUCKET = Aws::S3::Resource.new.bucket(ENV['S3_BUCKET'])

GET 要求に対して、 ブラウザから S3 へ POST するために必要な情報を返すアクションを実装します。
今回は video モデルのコントローラーにアクションを追加しました。

VideosController < ApplicationController

  def upload
    filename = params[:filename]
    filetype = params[:filetype]

    post = S3_BUCKET.presigned_post(
      key: "upload_video/#{filename}",
      acl: 'public-read',
      content_type: filetype,
      metadata: {
        'original-filename' => filename
      }
    })
    render json: {url: post.url,fields: post.fields}
  end

end

バケット内の保存先はkey:で指定するので、この値をDBに保存してモデルと紐づけることが可能です。

GET リクエストで filename,filetype パラメータ受け取ったuploadアクションは以下のpresigned POSTとして次のjsonを返します。

{
    "url": "https://your-bucket-name.s3.ap-northeast-1.amazonaws.com",
    "fields": {
        "key": "upload_video/test.mp4",
        "acl": "public-read",
        "Content-Type": "video/mp4",
        "x-amz-meta-original-filename": "test.mp4",
        "policy": "eyJleHBpc...",
        "x-amz-credential": "oiMjAxO...",
        "x-amz-algorithm": "AWS4-HMAC-SHA256",
        "x-amz-date": "20190607T004657Z",
        "x-amz-signature": "mF0aW9uIj..."
    }
}

ブラウザページの作成

動作としては

  • RailsにGETリクエストを送ってpresigned POSTを受け取る
  • presigned POSTを使ってS3へPOST
  • 実装はfetch api

です

<!DOCTYPE html>
<html>
  <head>
    <title>S3 POST Form</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  </head>

  <body>
    <input type="file" id="up_file">
    <br><input type="button" id="send" onclick="upload();" value="アップロード">
    <!-- fetch api -->    
    <script>
      function upload(){
        const up_files = document.getElementById('up_file');
        const up_file = up_files.files[0];
        if (up_files.value === "") {
          return false;
        }
        const url= 'http://localhost:3000/api/v1/video_upload/get_post_fields?filename=' + up_file.name + "&filetype=" + up_file.type;
        // Rails に GET
        console.log("GET 開始");
        fetch(
          url, 
          {method: 'GET'}
        ).then(response => {
          if(response.ok){
            console.log("GET 成功");
            return response.json();
          }
        }).then((data)=>{
          formdata = new FormData()
          for (key in data.fields) {
            formdata.append(key,data.fields[key]);
          }
          formdata.append("file",up_file);
          const headers = {
          "accept": "multipart/form-data"
          }
          // S3 に POST
          console.log("POST 開始");
          fetch(
            data.url,
            {
              method: 'POST',
              headers,
              body: formdata
            }
          ).then((response) => {
            if(response.ok){
              console.log("POST 成功");
              return response.text();
            }
          })
        });
      }
    </script>
  </body>
</html>

これでブラウザからのS3へのダイレクトアップロードを実装することができました。

参考

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

ブラウザからS3へのダイレクトアップロード

はじめまして、streampackチームのminsuです。

やりたいこと

S3へファイルをアップロードする際に、Railsサーバを通すことなくブラウザからS3へのダイレクトアップロードを実装してみます。
ブラウザからS3にファイルを直接アップロードすることにより、余分な負荷を削減できるメリットがあります。
また、Railsのgem aws-sdkを利用して生成したpresigned POSTを利用することでブラウザにaws credentialsを持たせる事なくアップロードを行えます。

AWSリソースの準備

まず、AWSアクセスキーを作成してACCESS_KEY_ID, SECRET_ACCESS_KEYを取得してください。

次にS3のバケットの作成します。
作成したバケットのCORSの設定を行い、外部からのPOSTを許可します。

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
  <CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedMethod>PUT</AllowedMethod>
    <AllowedMethod>POST</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    <AllowedHeader>*</AllowedHeader>
  </CORSRule>
</CORSConfiguration>

AllowedOrigin、AllowedHeaderはワイルドカードを設定しましたが、環境に合わせて変更してください。

Railsでpresigned POSTを返すアクションを設定

まずは環境変数に必要な値を持たせておきます。

.env
AWS_ACCESS_KEY_ID=your-key-id
AWS_SECRET_ACCESS_KEY=your-secret-key
BUCKET=your-bucket-name

次にGemfileに

Gemfile
gem `aws-sdk', '~3'

を追加して

$ bundle install

そして環境変数に保存した値を使ってS3のインスタンスを作成します。

config/initializers/aws.rb
Aws.config.update({
    region: 'ap-northeast-1',
    credentials: Aws::Credentials.new(ENV['AWS_ACCESS_KEY_ID'], ENV['AWS_SECRET_ACCESS_KEY']),
})

S3_BUCKET = Aws::S3::Resource.new.bucket(ENV['S3_BUCKET'])

GET 要求に対して、 ブラウザから S3 へ POST するために必要な情報を返すアクションを実装します。
今回は video モデルのコントローラーにアクションを追加しました。

VideosController < ApplicationController

  def upload
    filename = params[:filename]
    filetype = params[:filetype]

    post = S3_BUCKET.presigned_post(
      key: "upload_video/#{filename}",
      acl: 'public-read',
      content_type: filetype,
      metadata: {
        'original-filename' => filename
      }
    })
    render json: {url: post.url,fields: post.fields}
  end

end

バケット内の保存先はkey:で指定するので、この値をDBに保存してモデルと紐づけることが可能です。

GET リクエストで filename,filetype パラメータ受け取ったuploadアクションは以下のpresigned POSTとして次のjsonを返します。

{
    "url": "https://your-bucket-name.s3.ap-northeast-1.amazonaws.com",
    "fields": {
        "key": "upload_video/test.mp4",
        "acl": "public-read",
        "Content-Type": "video/mp4",
        "x-amz-meta-original-filename": "test.mp4",
        "policy": "eyJleHBpc...",
        "x-amz-credential": "oiMjAxO...",
        "x-amz-algorithm": "AWS4-HMAC-SHA256",
        "x-amz-date": "20190607T004657Z",
        "x-amz-signature": "mF0aW9uIj..."
    }
}

ブラウザページの作成

動作としては

  • RailsにGETリクエストを送ってpresigned POSTを受け取る
  • presigned POSTを使ってS3へPOST
  • 実装はfetch api

です

<!DOCTYPE html>
<html>
  <head>
    <title>S3 POST Form</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  </head>

  <body>
    <input type="file" id="up_file">
    <br><input type="button" id="send" onclick="upload();" value="アップロード">
    <!-- fetch api -->    
    <script>
      function upload(){
        const up_files = document.getElementById('up_file');
        const up_file = up_files.files[0];
        if (up_files.value === "") {
          return false;
        }
        const url= 'http://localhost:3000/api/v1/video_upload/get_post_fields?filename=' + up_file.name + "&filetype=" + up_file.type;
        // Rails に GET
        console.log("GET 開始");
        fetch(
          url, 
          {method: 'GET'}
        ).then(response => {
          if(response.ok){
            console.log("GET 成功");
            return response.json();
          }
        }).then((data)=>{
          formdata = new FormData()
          for (key in data.fields) {
            formdata.append(key,data.fields[key]);
          }
          formdata.append("file",up_file);
          const headers = {
          "accept": "multipart/form-data"
          }
          // S3 に POST
          console.log("POST 開始");
          fetch(
            data.url,
            {
              method: 'POST',
              headers,
              body: formdata
            }
          ).then((response) => {
            if(response.ok){
              console.log("POST 成功");
              return response.text();
            }
          })
        });
      }
    </script>
  </body>
</html>

これでブラウザからのS3へのダイレクトアップロードを実装することができました。

参考

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

ActiveRecordのinclude、joins、eager_load、preloadの表まとめ

表題の件について完全に自分用の備忘録としてまとめる

メソッド JOIN associationのキャッシュ クエリ数 その他
joins LEFT JOIN × 1 JOINしたテーブルで絞込ができる。
eager_load LEFT OUTER JOIN 1 JOINしたテーブルで絞込ができる。
preload - 複数 preloadしたテーブルによって絞り込もうとすると、例外を投げる。
includes - - - eager_load か preloadの動きをする

それぞれ使いどころがあると思うが、個人的には使っていてeager_loadが全体的に一番使いやすいのではと思う。

参考:https://qiita.com/k0kubun/items/80c5a5494f53bb88dc58

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

【Rails】バッテリーホイミ機能を実装しました

個人開発のWebアプリまちかどルート」v6.1に、名づけて《バッテリーホイミ機能》なるものを実装したときのメモです。

TL; DR

まちかどルートは

「道ばたのゴミを拾おう」
「お年寄りに席をゆずろう」
「〇〇に行って〇〇しよう」 など。

みんなの日常からちょっとした一日一善やおつかいなどをお題にしてサブクエストを作りクリアしあうことで世界がちょっと明るくなる、そんなリアルRPG系Webアプリです。

そしてユーザーには、レベルや経験値のほかHP (ヒットポイント:ドラクエのアレ)という属性値を持たせています。

Battery Status API

どうやらPCとAndroidのChrome限定のようですが、バッテリーの状態変化でJavaScriptを発火させることのできるBattery Status APIというものがあります。

今回はそれを応用してみました。

こんな流れです。
1. まちかどルートを開く
2. 充電を始める
3. APIが充電を検知したことを画面に表示
4. バッテリー残量値を取得
5. 「ホイミ!」ボタンを押す
6. バッテリー残量値をHPに加算してDBに保存

view / JavaScript

index.html.erb
<div id="posts">
 # ここに下記のJavaScriptからDOM要素が挿入されます
</div>
application.js
navigator.getBattery().then(function(battery) {
  // 充電を検知したら発火 
  battery.onchargingchange = function(){
    // 充電開始(true)のときだけ処理
    if (battery.charging === true) {
    // viewのid="posts"のところにDOM要素を挿入
    var element = document.getElementById("posts");
    element.insertAdjacentHTML("afterbegin", "
       <p>充電を検知</p>
       <form action='battery' accept-charset='UTF-8' method='get'>
       <input name='utf8' type='hidden' value='✓'>
          <input type='text' id='battery' name='battery' readonly=''>
          </input>
          <button name='button' id='battery_btn' type='submit' disabled>
            ホイミ!
          </button>
       </form>
     ");
    }
  }
  // 充電の残量が変化したら発火
  battery.onlevelchange = function(){
    // 充電の残量値を入力フォームに表示
    document.getElementById( "battery" ).value = battery.level * 100;
    // 入力フォームのdisabledを無効にして「ホイミ!」ボタンを押せるようにする
    document.getElementById( "battery_btn" ).disabled = "";
  }
 });

controllerでホイミ!

posts_controller.rb
    def battery
        # viewから送られてきたバッテリー残量値をHPに加算してDB保存
        if params[:battery] != nil
            battery = params[:battery]
            @current_user.hp += battery.to_i
            @current_user.save
            flash[:notice] = "HPが回復しました!"
            redirect_to root_path
        else
            flash[:error] = "呪文が失敗しました!"
            redirect_to root_path
        end
    end

ルーティング

config\routes.rb
  get 'battery' , as: 'battery', to: 'posts#battery'

あとがき

とてもザックリな感じになってしまいましたが、要点は上記のとおりです。HPが残り少なくなったらバッテリーを充電して「ホイミ!」

けっこう楽しいです。

オープンソース開発用のリポジトリを公開しました。今回の件、こちらにもアップしてあります。
https://github.com/west2538/machiroute_oss

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

rails s -b 0.0.0.0の意味

rails s: rails serverの略、サーバーを立ち上げるコマンド
-b 0.0.0.0: 立ち上げたサーバーを0.0.0.0にバインドする
→ 0.0.0.0は特別な値、マシンがもっているipアドレス全てに対してバインドすることになる。

この記事がわかりやすい。
https://keens.github.io/blog/2016/02/24/bind_addressnoimigayouyakuwakatta/

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

Rspecで変更が無い事を確認しようとしたらLint/AmbiguousBlockAssociationというLintエラーが発生した

テスト自体は通っていたが、プルリクを出す前にRuboCopを動かして検証したところ、Lint/AmbiguousBlockAssociationという以下のLintエラーが発生した。

Lint/AmbiguousBlockAssociation: Parenthesize the param change{ analysis.foo_count } to make sure that the block will be associated with the change method call.

該当のspecファイルの記述抜粋(一部、実態のコードから変更させてる部分があります)
require 'rails_helper'

RSpec.describe Dummy, type: :model do
  describe '.method' do
    subject { Dummy.method!(foo_analysis: analysis, foo_data: foo_data) }
    let(:analysis) { create :users_foo_analysis }
    context 'when foo_data is empty' do
      let(:foo_data) { [].to_json }
      it { is_expected.to eq Dummy.none }
      it { expect { subject }.to_not change{ analysis.foo_count } }
    end

動作しない前提で、試しに以下のように変更をかけてテストを動作させてみた。

変更前
■変更箇所
 it { expect { subject }.to_not change{ analysis.foo_count } }
変更後
 it { expect { subject }.to_not change analysis.foo_count }

すると、以下のようなエラーが表示された。

ArgumentError:
  `change` requires either an object and message (`change(obj, :msg)`) or a block (`change { }`). You passed an object but no message.

(change { })の形式ではRuboCopに怒られてしまっていたので、(change(obj, :msg))の形式に修正した形でテストを通しつつ、RuboCopに怒られないようにするためにどうすれば良いか調査してみた。

結論、以下のような記述であれば、テストを通しつつ、RuboCopに怒られずにすんだ。

最終形
  it { expect { subject }.to_not change{analysis, :foo_count) }

change(obj, :msg)の:msgって何だろう?と戸惑ったが、普通にメソッドを渡せば動きそうだなと思い、試しにやってみたら上手くいった。

:msgとかではなく、もっと分かりやすいエラーメッセージなら素早く解決できたのに。。。。

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

RSpecで変更が無い事を確認しようとしたらLint/AmbiguousBlockAssociationというLintエラーが発生した

テスト自体は通っていたが、プルリクを出す前にRuboCopを動かして検証したところ、Lint/AmbiguousBlockAssociationという以下のLintエラーが発生した。

Lint/AmbiguousBlockAssociation: Parenthesize the param change{ analysis.foo_count } to make sure that the block will be associated with the change method call.

該当のspecファイルの記述抜粋(一部、実態のコードから変更)
require 'rails_helper'

RSpec.describe Dummy, type: :model do
  describe '.method' do
    subject { Dummy.method!(foo_analysis: analysis, foo_data: foo_data) }
    let(:analysis) { create :users_foo_analysis }
    context 'when foo_data is empty' do
      let(:foo_data) { [].to_json }
      it { is_expected.to eq Dummy.none }
      it { expect { subject }.to_not change{ analysis.foo_count } }
    end

動作しない前提で、試しに以下のように変更をかけてテストを動作させてみた。

変更前
■変更箇所
 it { expect { subject }.to_not change{ analysis.foo_count } }
変更後
 it { expect { subject }.to_not change analysis.foo_count }

すると、以下のようなエラーが表示された。

ArgumentError:
  `change` requires either an object and message (`change(obj, :msg)`) or a block (`change { }`). You passed an object but no message.

(change { })の形式ではRuboCopに怒られてしまっていたので、(change(obj, :msg))の形式に修正した形でテストを通しつつ、RuboCopに怒られないようにするためにどうすれば良いか調査してみた。

結論、以下のような記述であれば、テストを通しつつ、RuboCopに怒られずにすんだ。

最終形
  it { expect { subject }.to_not change{analysis, :foo_count) }

change(obj, :msg)の:msgって何だろう?と戸惑ったが、普通にメソッドを渡せば動きそうだなと思い、試しにやってみたら上手くいった。

:msgとかではなく、もっと分かりやすいエラーメッセージなら素早く解決できたのに。。。。

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

コントローラに書くコードを意識する

自分がエンジニアバイトをしているところでは週1回Rails勉強会をしています。今回はそのやったところのまとめを残しておきます。
参考書:現場で使える Ruby on Rails 5速習実践ガイド

コードを書く場所を意識する

アプリケーションを作る上で何のコードをどこに書くかはとても重要になってきます。開発が進むにつれ、様々な機能を実装するためコードの量が増え複雑になっていきます。そこで少ないコストで全体を把握するには「 コードを書く場所を意識する」ことが重要になってきます。

コントローラに書くべきコード

RailではMVCを採用しています。それぞれに役割を持たせることで、どこが何をするところなのかを分けて管理しやすくしています。その中でコントローラの役割は送られてきたリクエストに対して、適切なレスポンスを返すのが仕事です。適切なレスポンスを返すためにModelやViewに対して働きかけます。

def index
 @user = User.all
end

開発初期の頃は上記のようにとてもシンプルなコントローラですが、開発が進むとModelに書くべきビジネスロジックが増え、どんどん複雑さが増してしまうことが多くなります。そのためコントローラはModelから値を取り出したり、保存したりする計算処理(ビジネスロジック)をあくまで呼び出す処理に集中する必要があります。

検索条件を書いたコード

User.where(name: "Bob")

のように(短いですが)DBから検索をかけるようなコードの場合、コントローラに書くのではなく,Modelにscopeとして書く必要があります。

オブジェクトのデータを変更させるようなコード

@user.name = "Tom"

上記のようなオブジェクトがもつデータの変更がおきているコードの呼び出しが多数ある場合、その部分はModelに移したほうがよいでしょう。例えば、「ユーザーの情報を変更するメソッド」のような統合的なメソッドをModelに作り、コントローラ側で呼び出すだけにするとコントローラはスッキリしたものになります。

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