20190811のRailsに関する記事は6件です。

【JS】動的なカテゴリーボックスの実装方法

はじめに

動的なセレクトボックスを実装するにあたり、非同期通信を用いたので備忘録としてここ記す。

テーブル

categoriesテーブル

Column Type Options
category-name string null false
ancestry string null false

ancestryのGemを使い多階層としている。
親、子、孫が一つのカラムの中に存在している。

実装

category.js
  $(function(){
  function appendOption(category){ // optionの作成
    var html = `<option value="${category.id}">${category.name}</option>`;
    return html;
  }
  function appendChidrenBox(insertHTML){ // 子セレクトボックスのhtml作成
    var childSelectHtml = '';
      childSelectHtml = `<div class='product-select-wrapper' id= 'children_wrapper'>
                        <div class='product_category-select'>
                        <select class="category_select-box" id="child_category" name="item[category_id]">
                        <option value="---">---</option>
                        ${insertHTML}
                        </select>
                        <i class='fa fa-chevron-down'></i>
                        </div>
                        <div class= 'product_select-grandchildren'>
                        </div>
                        </div>`;
    $('.product_select-children').append(childSelectHtml);
  }
  function appendgrandChidrenBox(insertHTML){ // 孫セレクトボックスのhtml作成
    var grandchildrenSelectHtml = '';
    grandchildrenSelectHtml = `<div class='product-select-wrapper' id= 'grandchildren_wrapper'>
                              <div class='product_category-select'>
                              <select class="category_select-box" id="grandchild_category" name="item[category_id]">
                              <option value="---">---</option>
                              ${insertHTML} 
                              </select>
                              <i class='fa fa-chevron-down'></i>
                              </div>
                              <div class= 'product_select-grandchildren'>
                              </div>
                              </div>`;
    $('.product_select-grandchildren').append(grandchildrenSelectHtml);
  }



  $(document).on('change', '#category_select', function(){  // 親セレクトボックスの選択肢を変えたらイベント発火
    var productcategory = document.getElementById('category_select').value; 
  // ↑ productcategoryに選択した親のvalueを代入
    if (productcategory != ''){
 // ↑ productcategoryが空ではない場合のみAjax通信を行う。選択肢を初期選択肢'---'に変えると、通信失敗となってしまうため。
      $.ajax({
        url: 'category_children',
        type: 'GET',
        data: { productcategory: productcategory },
        dataType: 'json'
      })
      .done(function(children){  // 送られてきたデータをchildrenに代入
        var insertHTML = '';
        children.forEach(function(child){  
  // forEachでchildに一つずつデータを代入。子のoptionが一つずつ作成される。
          insertHTML += appendOption(child); 
        });
        appendChidrenBox(insertHTML); 
        $(document).on('change', '#category_select', function(){
  // 通信成功時に親の選択肢を変えたらイベント発火。子と孫を削除。selectのidにかけるのではなく、親要素にかけないと残ってしまう
          $('#children_wrapper').remove(); 
          $('#grandchildren_wrapper').remove();
        })
      })
      .fail(function(){
        alert('カテゴリー取得に失敗しました');

      })
    }
  });


  // documentにしないとリロードしなければイベントが発火しない
  $(document).on('change', '#child_category', function(){
    var productcategory = document.getElementById('child_category').value;
    if (productcategory != ''){
    $.ajax ({
      url: 'category_grandchildren',
      type: 'GET',
      data: { productcategory: productcategory },
      dataType: 'json'
    })
    .done(function(grandchildren){
      var insertHTML = '';
      grandchildren.forEach(function(grandchild){
        insertHTML += appendOption(grandchild);
        });
        appendgrandChidrenBox(insertHTML);  
        $(document).on('change', '#child_category',function(){
          $('#grandchildren_wrapper').remove();
          })
        })  
        .fail(function(){
          alert('カテゴリー取得に失敗しました');
        })
    }
  });
});

routes.rb
Rails.application.routes.draw do
  devise_for :users
  root to: "items#index" 
  # ここから itemsコントローラのAjax通信でのアクション先指定
  resources :items do 
    collection do
      get 'category_children' 
      get 'category_grandchildren'
    end
  end
  # ここまで
end
items_controller.rb
  def new
    @category = Category.all.order("id ASC").limit(13) # categoryの親を取得
  end

  def category_children  
    @category_children = Category.find(params[:productcategory]).children 
    end
  # Ajax通信で送られてきたデータをparamsで受け取り、childrenで子を取得

  def category_grandchildren
    @category_grandchildren = Category.find(params[:productcategory]).children
    end
  # Ajax通信で送られてきたデータをparamsで受け取り、childrenで孫を取得。(実際には子カテゴリーの子になる。childrenは子を取得するメソッド)
category_children.json.jbuilder
json.array! @category_children do |child|
  json.id child.id
  json.name child.category_name
end
ategory_grandchildren.json.jbuilder
json.array! @category_grandchildren do |grandchild|
  json.id grandchild.id
  json.name grandchild.category_name
end
new.html.haml
.product_select-details
  .product_select-group
    .product_header
      カテゴリー
    .product_require
      必須
  .product_category-select
    = f.collection_select :category, @category, :id, :category_name, { prompt: "---" }, { class: "category_select-box", id: "category_select" }
    %i.fa.fa-chevron-down
  .product_select-children

さいごに

間違っているところがあればご指摘下さい

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

Dockerでrailsローカル開発環境構築

概要

  • Dockerを使用したRailsの開発環境を構築
  • DBはMySQLを使用
  • ローカル開発環境で構築(MySQLのユーザー名、パスワードは簡易なものを使用)

前提

  • Mac、Windows10のローカル開発環境で動作
  • 「Docker for Windows」か「Docker for Mac」がインストール済みであること

ディレクトリ構造

├─src
│  ├─Gemfile
│  └─Gemfile.lock
├─Dockerfile
└─docker-compose.yml

準備するファイル

ディレクトリ構造にある4つのファイルの内容は下記になります。

Dockerfile

Dockerfile
FROM ruby:2.5.3
ENV LANG C.UTF-8

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

RUN mkdir /app
RUN mkdir /app/src

ENV APP_ROOT /app/src
WORKDIR $APP_ROOT

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

RUN bundle install

ADD . $APP_ROOT

docker-compose.yml

docker-compose.yml
version: '2'
services:
  mysql:
    image: mysql:5.7
    command: mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci
    ports:
      - "3306:3306"
    volumes:
      - ./db/mysql_data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: 'root-pass'
    restart: always
    logging:
      options:
        max-size: 5m
        max-file: "10"
  app:
    build: .
    image: rails
    container_name: 'app'
    command: bundle exec rails s -p 80 -b '0.0.0.0'
    ports:
      - "80:80"
    environment:
      VIRTUAL_PORT: 80
    volumes:
      - ./src:/app/src
    depends_on:
      - mysql
    restart: always

Gemfile

src/Gemfile
source 'https://rubygems.org'
gem 'rails', '5.2.2'

Gemfile.lockは、ファイルの中身は空でOK

src/Gemfile.lock

手順

スケルトンアプリ作成

$ docker-compose run app rails new . --force --database=mysql --skip-bundle

database.ymlを修正

src/config/database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: root-pass
  host: mysql

イメージ再ビルド

※処理完了までちょっと時間がかかることがあります。。。

$ docker-compose build

コンテナ立ち上げ

$ docker-cocmpose up -d

MySQLにRailsのDBを作成

$ docker-compose run app rails db:create

これで、http://localhostにブラウザでアクセスすると、Railsのインストール完了画面がでてきました。

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

Ruby on Railsの環境構築した時に出たエラーとか

はじめに

サーバーサイドの知識があまりなかったため、Railsをなんとなーく導入してみようと思い、なんとなーく環境構築を勧めていたら、無知も相まって色々と悩んだのでメモを残しておく。今回予め、Gitlab,Redmineあたりを勉強ついでに導入しており、その関係でRails側の設定やインストール工程を一部省いたりもしたため、rubyとかbundlerとかpostgresqlとかをrailsのためにインストールし直したりはしていない。

Railsを導入するにあたって参考にしたサイト

環境

  • OS (CentOS7)
  • ruby (2.4.1)
  • bundler (1.17.3)
  • gem (3.0.4)
  • rails (5.2.3)
  • postgresql (9.2.24)

本題

bundler command not found: spring

Bundle complete! 18 Gemfile dependencies, 60 gems now installed.
Gems in the groups development and test were not installed.
Bundled gems are installed into `./vendor/bundle`
         run  bundle exec spring binstub --all
bundler: command not found: spring
Install missing gem executables with `bundle install`

rails new 'アプリ名' -d postgresqlを実行した時に出たエラー。
単純にspringというコマンドがなく、次の工程に進めないという話なのだけど、なぜ存在しないのかが分からなかった。
結論から言うとbundlerのconfigに問題があり、developmentとtestグループがBUNDLE_WITHOUTに設定されていたため、Gemfileの特定の行を通っていなかった。
参考URL(http://ruby.studio-kingdom.com/bundler/bundle_config/)

.bundle/config
---
BUNDLE_PATH: "vendor/bundle"
BUNDLE_WITHOUT: "development:test"

ひとまず以下の様に修正

.bundle/config
---
BUNDLE_PATH: "vendor/bundle"
BUNDLE_WITHOUT: "test"

これでspringコマンド含め、諸々インストールされた。

Could not find a JavaScript runtime

JSのランタイムがないとかなんとか。nodejsインストールで解決した。
参考URL(http://djandjan.hateblo.jp/entry/2018/07/25/224929)

Address already in use

正しくは

Address already in use - bind(2) for "127.0.0.1" port 3000 (Errno::EADDRINUSE) 

と出力されていた。
多分3000番ポートが既に使用されているので、ポートの使用状況を lsof -i:3000 で確認してみたら、どうやらGitlabで使われてるっぽい...のでRailsのポートを変更することにした。
以下のコマンドでひとまず解決。

$ rails s -p 3001

Linux上ではひとまず起動したようなので、よしこれでページアクセスできるわ!と思い、ブラウザーにアドレス入力。エンターキーに渾身の力をこめてターンッしたら想定していたページが表示されない。このサイトにアクセスできませんとのこと。
これについては、下のURLに詳しいことが書いてあった。
参考URL
結果

$ rails s -b 0.0.0.0 -p 3001

これでアクセス"は"できるようになった。

あとファイアウォールのポートの設定忘れてるとアクセスできないので注意。

role "ユーザー名" does not exist

データベース側の設定ができておらんのでアクセスできてもページの表示ができなかった。
まずはユーザーが存在しないところから。下記URLの通りにすれば作成できた。ユーザー名はRails sを実行しているユーザー名にすればいい思う。(多分)
自分の場合はrootでやっていたので、

$ sudo su – postgres
$ createuser -s -r root

で作成は完了。
参考URL(https://kirohi.com/rails_pg_error_role_doesnotexist)

データベースが存在しない。

下記URLの通り設定していきましょう。"Rails の設定とか"の辺りに詳しく書いてある。
データベースの名前はエラーページに表示されている通り。(***の部分はrails new で作成したアプリの名前)
***_development
***_test
***_production
参考URL

余談

改めて記事にしてみて、アドレスの指定のところとか、そもそもDBまわりの知識があまりないとかちらほらあったので改めて調べ直したら記事にして考えを整理したいと思う。

また、もし誤った情報があれば、ご指摘いただけると幸いです。

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

Rails Tutorialの知識から【ポートフォリオ】を作って勉強する話 #7 ログイン下準備編

こんな人におすすめ

  • プログラミング初心者でポートフォリオの作り方が分からない
  • Rails Tutorialをやってみたが理解することが難しい
  • ポートフォリオを作成しながら勉強したい

リードミー

  • 目的:ポートフォリオを作成して会社に面接を受ける準備をする
  • AWSによるCloud9で作成
  • 間違いの可能性大、一緒に勉強させてください:pray:

ログインまでの色々を実装する

Tutorialは8.1 セッションに突入。
これからログインするまでのあれこれを実装していこう:thumbsup:
流れとしては下記の3つを行う。

  • コントローラ生成
  • ビュー生成
  • それらのテスト生成

機能としてのログインは次回#8。

ログインのためのコントローラ生成

とりあえずTutorial通りにSessionsコントローラを生成する。
くどいけどコントローラテストはRequest specで書く。

bash
$ rails g controller Sessions new
$ rails g rspec:request session
spec/requests/sessions_spec.rb
require 'rails_helper'

RSpec.describe "Sessions", type: :request do

  describe "GET /login" do
    it "returns http success" do
      get login_path
      expect(response).to have_http_status(:success)
    end
  end
end

あとはホーム画面の'Login'ボタンのリンク先も書く。

app/views/layouts/_header.html.erb
<!-- 中略 -->
<div id="menu" class="collapse navbar-collapse">
  <ul class="navbar-nav ml-auto">
    <li class="nav-item">
      <%= link_to "Login", login_path, class: "btn btn-info btn-md" %>
    </li>
  </ul>
</div>
<!-- 中略 -->

以前failureとなったテストがようやくこれで通る。
ではSessionsコントローラを実装しよう。

app/controllers/sessions_controller.rb
class SessionsController < ApplicationController

  def new
  end

  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      # ユーザーログイン後にユーザー情報のページにリダイレクトする
    else
      # エラーメッセージを作成する
      render 'new'
    end
  end

  def destroy
  end
end

createアクションにこんな記述があると思うけど、

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

Tutorialではこんな風に述べてくれている。

Rubyではnilとfalse以外のすべてのオブジェクトは、真偽値ではtrueになる

だからUserクラスが真偽値っぽくなくてもif文が通るのね。


あと何気にuserがインスタンスじゃないのも特徴。
createアクションに対するビューとかが必要なく(ログインできましたよ!みたいな画面を別で作るなら考慮してよい)アクション内で収まるのでインスタンスでない。

ログインのためのビュー生成

form_for(:session, url: login_path)について考える

続いてログイン画面を作ろうと思うんだけど、Tutorialでこんな記述がある。

form_for(@user)

Railsでは上のように書くだけで、「フォームのactionは/usersというURLへのPOSTである」と自動的に判定しますが、セッションの場合はリソースの名前とそれに対応するURLを具体的に指定する必要があります。

form_for(:session, url: login_path)

なにこれ??最初は意味が分かりませんでした。
で調べたわけだけど、リソース名を'session'にすることでparams[:session]にキーと値が保存されるのね。

つまり渡しているのはそれぞれこう。

form_for(@user)  params[:user]
form_for(:session, url: login_pash)  params[:session]

このあたりはこちらが分かりやすい!↓
rails ログイン機能のform_forの作りについての疑問
[Rails4.0] フォームの基本とStrongParametersを理解する

form_withで書き換える

このポートフォリオではform_forではなくform_withを使う(理由は#6)。
その際はリソース名をscopeに指定するので、最終的にはこうなる。

app/views/sessions/new.html.erb
<% provide(:title, "ログイン") %>
<div class="container form-container login-container">
  <div class="row">
    <div class="col">
      <div class="form-logo-img">
        <%= link_to image_tag('lantern_lantern_logo.png', width: 100), root_path, class: "logo-img" %>
      </div>
      <h1 class="form-title">ログイン</h1>
      <%= form_with(scope: :session, url: login_path, local: true) do |form| %>

        <div class="form-group">
          <%= form.email_field :email, class: 'form-control', placeholder: "メールアドレス" %>
        </div>

        <div class="form-group">
          <%= form.password_field :password, class: 'form-control', placeholder: "パスワード" %>
        </div>

        <div class="form-group">
          <%= form.submit "ログイン", class: "btn btn-info btn-lg form-submit" %>
        </div>
      <% end %>

      <p class="form-go-to-signup-or-login">新しくはじめる方は<%= link_to "こちら", signup_path %></p>
    </div>
  </div>
</div>

書き換えでお世話になりました↓
【Ruby】チュートリアルのform_forをform_withで書き換え (おまけ:capybaraでのテスト)

ログインのE2Eテスト生成

Tutorial 8.1.5 フラッシュのテストにならってテストをSystem specで書こう。
Tutorialとの差分も用意する。

bash
$ touch spec/systems/login_spec.rb
spec/systems/login_spec.rb(RSpec:ポートフォリオ)
require 'rails_helper'

RSpec.describe "Logins", type: :system do

  context "login with invalid information" do
    it "is invalid because it has no information" do
      visit login_path
      expect(page).to have_selector '.login-container'
      fill_in 'メールアドレス', with: ''
      fill_in 'パスワード', with: ''
      click_on 'ログイン'
      expect(page).to have_selector '.login-container'
      expect(page).to have_selector '.alert-danger'
    end

    it "disappears flash messages when users input invalid information then other links" do
      visit login_path
      expect(page).to have_selector '.login-container'
      fill_in 'メールアドレス', with: ''
      fill_in 'パスワード', with: ''
      click_on 'ログイン'
      expect(page).to have_selector '.login-container'
      expect(page).to have_selector '.alert-danger'
      visit root_path
      expect(page).not_to have_selector '.alert-danger'
    end
  end
end
test/integration/users_login_test.rb(Minitest:Tutorial)
require 'test_helper'

class UsersLoginTest < ActionDispatch::IntegrationTest

  test "login with invalid information" do
    get login_path
    assert_template 'sessions/new'
    post login_path, params: { session: { email: "", password: "" } }
    assert_template 'sessions/new'
    assert_not flash.empty?
    get root_path
    assert flash.empty?
  end
end

大きな変更点は5つ。

  1. テストは1つから2つに
  2. getはvisitに
  3. assert_templateはhave_selectorに
  4. postはfill_inに
  5. flash_empty?はhave_selectorに

1.
・フォームに入力しない場合エラーが発生する
・その場合別ページでflashが消える
を独立させたかったので2つに分けた。

2.
System specでgetやpostを指定するとうまくいかない。
代わりにvisitを使おう。

3.
assert_templateもRequest specでしか動作が確認できないので、have_selectorを使用しdivを検証することで描画をテストする。

4.
E2Eテストの性格上、直接postをリクエストするのではなくフォームに値を入力する方がテストらしいのでfill_inを使う。

5.
flashに対してbe_emptyを使用したテストは実装できなかった。
System specではこういう書き方もできない。

expect(flash[:danger]).to be_truthy
bash
NoMethodError:
       undefined method `flash' for nil:NilClass

(Request specでは無事機能する)
Request specと異なりSystem specはコントローラで使用できるFlashHashオブジェクトと紐づいていないっぽい??
代わりにalert時に生成されるdiv要素を検証した。

参考にさせていただきました↓
Ruby on Rails ガイド 5.2 Flash

次回はいよいよ機能としてのログイン実装

おそらく次回がログインのメインです。
Sessionsヘルパーに各メソッドを与えます。
(超初心者向け)解説が載っているのでぜひご参考に〜:stuck_out_tongue_closed_eyes:

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

Railsチュートリアル1章まとめ

はじめに

Railsチュートリアルで発生したエラーとか詰まったところをまとめていこうと思います。
備忘録として。
2週目の今回はbitbucketではなくgithubで進めていこうと思います。

rails newの時点でエラー発生

$rails _5.1.6_ new hello_app2

#エラー発生
can't find gem railties (= 5.1.6) with executable rails (Gem::GemNotFoundException)

対処方法が下記コマンド

$gem install rails -v 5.1.6

こちらで再度rails newコマンドすれば無事作成できました。

githubでのリポジトリ作成

いつもはgithubからローカルにクローンするやり方でやってたので今回はこちらで。
ローカルからプッシュするやり方で。

まずgithubでリポジトリ作成する。
名前はhello_app2にしました。

# ローカルリポジトリを作成するディレクトリに移動する
cd hello_app2

# ローカルリポジトリを作成する
$ git init
$ git add .
$ git commit -m "first commit

# リモートリポジトリのアクセス先を設定する
$ git remote add origin https://github.com/GitHubのユーザ名/GitHubのリポジトリ名(こちらではhello_app2).git

# pushする
$ git push -u origin master

git push heroku でエラー

エラー文

remote:  !
remote:  !     Could not detect rake tasks
remote:  !     ensure you can run `$ bundle exec rake -P` against your app
remote:  !     and using the production group of your Gemfile.
remote:  !     Activating bundler (2.0.1) failed:
remote:  !     Could not find 'bundler' (2.0.1) required by your /tmp/build_b85aaac02a3a812e844f20844711c628/Gemfile.lock.
remote:  !     To update to the latest version installed on your system, run `bundle update --bundler`.
remote:  !     To install the missing version, run `gem install bundler:2.0.1`
remote:  !     Checked in 'GEM_PATH=vendor/bundle/ruby/2.5.0', execute `gem env` for more information
remote:  !     
remote:  !     To install the version of bundler this project requires, run `gem install bundler -v '2.0.1'`
remote:  !

こちらのエラー分の中にあるように下記コマンド

$ gem install bundler -v 2.0.1
$ rm Gemfile.lock
$ bundle install
$ git add .
$ git commit -m 'hoge'
$ git push heroku master

こちらで解決し無事デプロイ完了しました。

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

rails db:〇〇 は連続して書ける (メモ)

$bundle exec rake db:drop db:create db:migrate

こういう書き方で連続していける

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