20190708のRubyに関する記事は18件です。

カスタムバリデーションで他のモデルのカラムを使用する

About

カスタムバリデーションで他のモデルのカラムを使いたい場合の記載方法について記載しています。

Environment

この記事ではmacbook(unix)にインストールしたruby 2.5.1p57, Rails 5.2.3を使用しています。

Validationとは

「バリデーション」とは、「検証、実証、認可、妥当性」を意味する英単語
「質的な良し悪し」を判断するのではなく、「システム的な適合不適合」を判断するための言葉

小難しく書きましたが、意図しないデータをDBに登録できない様、バリデーションをかけることが一般的であるそうです。

(参考文献)
https://career-picks.com/business-yougo/validation/

カスタムバリデーション

通常、バリデーションは以下の様な形式で設定しますが、複雑なバリデーションを設定したい場合、自作のバリデーション(= カスタムバリデーション)を作成します。

通常
class User < ApplicationRecord
  validates :user_id, presence: true
  validates :email, presence: true
end

基本的には、以下の様に定義したメソッド名をvalidate に続けて記載することで、設定可能です。

カスタムバリデーション
class User < ApplicationRecord
  validates :user_id, presence: true
  validates :email, presence: true
  validate :if_user_does_not_have_nickname

  def if_user_does_not_have_nickname
    return if nickname.present?
    errors.add(:nickname, "Nickname is absent")
  end
end

他のモデルのカラムも組み合わせたい場合

validationを考えていると、他のモデルのカラムを条件として加えたい時があるかと思います。
その様な場合、モデル間でアソシエーションを組むことで他のモデルのカラムを使用することができます。

(例)睡眠時間を記録するアプリ
・日付がデータとして渡されている
・もしユーザの睡眠記録が一つもなければ、睡眠記録の新規作成が可能
・睡眠記録がすでにある場合は、渡されたデータが既存の睡眠記録の最終日付の翌日にマッチしているかどうか調べる
・ミスマッチの場合、渡されたデータを新規登録しない

sleep.ruby
class Sleep < ApplicationRecord
  # アソシエーションの設定。読み込み順の関係で、カスタムバリデーションより必ず上に記載してください。
  belongs_to :user
  validates :slept_time, presence: true
  validates :wakeup_time, presence: true
  # カスタムバリデーションの呼び出し(コントローラでnew, createメソッドの場合のみ)
  validate :dates_cannnot_be_registered_if_there_is_no_yesterdays_date, on: [:new, :create]

  # カスタムバリデーションの作成
  def dates_cannnot_be_registered_if_there_is_no_yesterdays_date
    # もし日付があり、ユーザの睡眠記録がない場合は処理を抜ける(= そのまま新規登録する。)
    return if date.present? && user.sleeps.blank?
    # もし日付がユーザの最新の睡眠記録の日付の翌日でない場合は、データを新規登録しない。
    if date != user.sleeps.last.date.tomorrow
      errors.add(:date, "You can't register that there is no data about before days.")
    end
  end
end

最後に

いかがでしょうか。アソシエーションを組めていれば、意外と簡単に実装できてしまいます。
私はbelongs toの記述位置がカスタムバリデーションの下になっていたため、エラーと何時間も格闘する羽目になりましたが...。
参考になれば幸いです!

筆者について

TECH::EXPERTにて4月よりruby, railsを学習している未経験エンジニアです。
記載内容に不備・不足があればご指摘いただけると幸いです。
至らぬ点ばかりですので、改善点がありましたらどんどんご指摘下さい!

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

【Ruby】モジュールについて(ネームスペースの活用)

はじめに

今回はモジュールによるネームスペース(名前空間)の作成に関してまとめました!

モジュールには様々な用途があります。
主に以下のような用途です。

・クラスにインスタンスメソッドを追加する(ミックスイン)
・クラスにクラスメソッドを追加する(ミックスイン)
・クラス名などの重複を防ぐためにネームスペース(名前空間)を作成する
・ミックスインせずにモジュール単体としてメソッドを定義
など。

今回はこれらの機能のうち、上から3つ目のネームスペースに関してのみまとめています。
1つ目及び2つ目の機能(ミックスイン)に関しては別記事にてまとめていますので、ぜひこちらでご覧ください:information_desk_person_tone2:
→ https://qiita.com/tomokichi_ruby/items/b348d7a44928262cc7e4

ネームスペース(名前空間)とは

各要素に一意の異なる名前をつけた範囲のことです。
また要素の名前が衝突しても識別できるように、要素の集合に対して付与された名前のこと

例えば複数名で同じアプリケーションを作成する場合などは特に、クラス名やメソッド名が被ってしまう危険性があります。
この名前の衝突を防ぐために活用するのがネームスペースであり、モジュールにてこのネームスペースを作ることができます!

モジュールによるネームスペースの付け方

test.rb
module Admin
 class User
  #メソッドなど
 end
end

module Guest
 class User
  #メソッドなど
 end
end

上記の例では、Userクラスが2つ存在していますが、区別するためにそれぞれAdminとGuestモジュール内に入れています。
このようにモジュール内のクラスを参照する場合はモジュール名::クラス名とすればOKです!
例えば、クラスからインスタンスを作成する時は

test.rb
Guest::User.new

このようにするだけです!

ネームスペースの定義の応用

すでにモジュールが定義されている場合で、そのモジュール内にクラスを追加したい場合は、
以下のようにclass モジュール名::クラス名と宣言することもできます。

test.rb
module Admin
end

class Admin::User
  #メソッドなど
end

さいごに

ネームスペースについて解説しました!
大規模なプロジェクトになるほど、モジュールによるネームスペースの活用は必要になってきます。
モジュールを有効活用していきたいですね:relaxed:
最後までご覧いただきありがとうございました!

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

amcharts 4 Demos を使ってグラフを作成(piechart編)

About

amcharts関連の日本語文献のあまりの少なさから、誰かのお役に立てればと思い記載しています!

最終的には、このようなグラフを作成することができます。7d0de671ad5679b2edef0a20c7ab9b06.gif

Environment

この記事ではmacbook(unix)にインストールしたruby 2.5.1p57, Rails 5.2.3を使っています。

amchartsとは

前回の記事をご覧ください!

Piechartとは

円グラフです。今回はこちらを作成します。
2e57a92ca3d39c6dbd16f293e967fc7c.gif

導入方法

こちらのサイトのページ内を下にスクロールすると、Demo sourceが置いてありますので、コピペで使用可能です。
基本的にはそのままコードを触らずとも、実装可能です。

Demo_source
<!-- Styles -->
<style>
#chartdiv {
  width: 100%;
  height: 500px;
}

</style>

<!-- Resources -->
<script src="https://www.amcharts.com/lib/4/core.js"></script>
<script src="https://www.amcharts.com/lib/4/charts.js"></script>
<script src="https://www.amcharts.com/lib/4/themes/animated.js"></script>

<!-- Chart code -->
<script>
am4core.ready(function() {

// Themes begin
am4core.useTheme(am4themes_animated);
// Themes end

// Create chart instance
var chart = am4core.create("chartdiv", am4charts.PieChart);

// Add data
chart.data = [ {
  "country": "Lithuania",
  "litres": 501.9
}, {
  "country": "Czech Republic",
  "litres": 301.9
}, {
  "country": "Ireland",
  "litres": 201.1
}, {
  "country": "Germany",
  "litres": 165.8
}, {
  "country": "Australia",
  "litres": 139.9
}, {
  "country": "Austria",
  "litres": 128.3
}, {
  "country": "UK",
  "litres": 99
}, {
  "country": "Belgium",
  "litres": 60
}, {
  "country": "The Netherlands",
  "litres": 50
} ];

// Add and configure Series
var pieSeries = chart.series.push(new am4charts.PieSeries());
pieSeries.dataFields.value = "litres";
pieSeries.dataFields.category = "country";
pieSeries.slices.template.stroke = am4core.color("#fff");
pieSeries.slices.template.strokeWidth = 2;
pieSeries.slices.template.strokeOpacity = 1;

// This creates initial animation
pieSeries.hiddenState.properties.opacity = 1;
pieSeries.hiddenState.properties.endAngle = -90;
pieSeries.hiddenState.properties.startAngle = -90;

}); // end am4core.ready()
</script>

<!-- HTML -->
<div id="chartdiv"></div>

編集方法

style, htmlについては割愛し、scriptについて筆者がわかる範囲で記載していきます。

index.html.erb
<!-- Chart code -->
<script>
am4core.ready(function() {

// Themes begin
// テーマです。
am4core.useTheme(am4themes_animated);
// Themes end

// Create chart instance
// 始めにインスタンスを作成します。Piechart形式であることをここで指定しています。
var chart = am4core.create("chartdiv", am4charts.PieChart);

// Add data
// ここにデータを入力していきます。
chart.data = [ {
  "country": "Lithuania",
  "litres": 501.9
}, {
  "country": "Czech Republic",
  "litres": 301.9
}, {
  "country": "Ireland",
  "litres": 201.1
}, {
  "country": "Germany",
  "litres": 165.8
}, {
  "country": "Australia",
  "litres": 139.9
}, {
  "country": "Austria",
  "litres": 128.3
}, {
  "country": "UK",
  "litres": 99
}, {
  "country": "Belgium",
  "litres": 60
}, {
  "country": "The Netherlands",
  "litres": 50
} ];

// Add and configure Series
// 作成したインスタンスに設定を追加していきます。
var pieSeries = chart.series.push(new am4charts.PieSeries());
// データはlitres(リットル)
pieSeries.dataFields.value = "litres";
// 単位はcountryです
pieSeries.dataFields.category = "country";
pieSeries.slices.template.stroke = am4core.color("#fff");
pieSeries.slices.template.strokeWidth = 2;
pieSeries.slices.template.strokeOpacity = 1;

// This creates initial animation
pieSeries.hiddenState.properties.opacity = 1;
pieSeries.hiddenState.properties.endAngle = -90;
pieSeries.hiddenState.properties.startAngle = -90;

}); // end am4core.ready()
</script>

実装例

最後に、私が作成した"sommeil"というアプリケーションのコードの内、一部のamcharts部分を参考までに記載します。

_piechart.html.erb
<!-- Styles -->
<style>
  #chart2div {
    width: 100%;
    height: 300px;
  }
</style>
<!-- Resources -->
<script src="https://www.amcharts.com/lib/4/core.js"></script>
<script src="https://www.amcharts.com/lib/4/charts.js"></script>
<script src="https://www.amcharts.com/lib/4/themes/animated.js"></script>
<!-- Chart code -->
<script>
  am4core.ready(function () {
    // Themes begin
    am4core.useTheme(am4themes_animated);
    // Themes end
    // Create chart instance
    var chart2 = am4core.create("chart2div", am4charts.PieChart);
    // Add data
    var sleeping_time = '<%= @sleeping_time %>';
    chart2.data = [{
      "time": "Sleep",
      "amount": sleeping_time
    }, {
      "time": "Awake",
      "amount": (24 - sleeping_time)
    }];
    // Add and configure Series
    var pieSeries = chart2.series.push(new am4charts.PieSeries());
    pieSeries.labels.template.disabled = true;
    pieSeries.dataFields.value = "amount";
    pieSeries.dataFields.category = "time";
    pieSeries.slices.template.stroke = am4core.color("#fff");
    pieSeries.slices.template.strokeWidth = 2;
    pieSeries.slices.template.strokeOpacity = 1;
    // This creates initial animation
    pieSeries.hiddenState.properties.opacity = 1;
    pieSeries.hiddenState.properties.endAngle = -90;
    pieSeries.hiddenState.properties.startAngle = -90;
  }); // end am4core.ready()
</script>
<!-- HTML -->
<div id="chart2div"></div>

最後に

注釈や解説できない部分がまだまだありますので、今後検証して追記していきます。
ご覧いただき、ありがとうございました。

筆者について

TECH::EXPERTにて4月よりruby, railsを学習している未経験エンジニアです。
記載内容に不備・不足があればご指摘いただけると幸いです。
至らぬ点ばかりですので、改善点がありましたらどんどんご指摘下さい!

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

MaterializecssのCarouselを使用して、3秒ごとに画像が自動で切り替わるページを作る

概要

TECH::EXPERTのカリキュラムでオリジナルのミニアプリを作成する機会があり、
その一部のページでMaterializecssのCarouselを使用し、3秒ごとに画像が切り替わるページを作成したので紹介します。

MaterializecssのCarouselとは

画像をくるくると回せる機能です。
Image from Gyazo
https://materializecss.com/carousel.html

自分が作成したページ紹介

Image from Gyazo

作成する前提

MaterializecssがCDNで読み込めている

編集するファイル

・ビューファイル
・CSSファイル
・jsファイル

ビューファイル

about.html.erb
<section class="about-main" >
  <div class="carousel carousel-slider" data-indicators="true" id="big3" >
    <div class="carousel-fixed-item">
      <div class="container">
        <h1 class="white-text">Work Hard See Result</h1>
      <% if user_signed_in? %>
        <a class="btn waves-effect white black-text darken-text-2" href="/" target="_blank">HOME</a>
      <%else%>
        <a class="btn waves-effect white black-text darken-text-2" href="/users/sign_in" target="_blank">Log in</a>
      <%end%>
      </div>
    </div>
    <div class="carousel-item big3pic" id="benchpress">
      <div class="container">
        <h3 class="white-text">Bench Press</h3>
        <p class="white-text">chest</p>
      </div>
    </div>
    <div class="carousel-item big3pic" id="deadlift">
      <div class="container">
        <h3 class="white-text">Dead Lift</h3>
        <p class="white-text">back</p>
      </div>
    </div>
    <div class="carousel-item big3pic"  id="squat">
      <div class="container">
        <h3 class="white-text">Squat</h3>
        <p class="white-text">legs</p>
      </div>
    </div>
  </div>
</section>

"carousel carousel-slider" を用いました。
"carousel-fixed-item"は固定して表示したいものを書きます。
"carousel carousel-item"の部分がそれぞれの画像のクラスです。

CSSファイル

style.css
#big3{
  height: 100vh;
}
#benchpress{
  background-image: url('benchpress.jpg');
  background-size: 100%;
}
#deadlift{
  background-image: url('deadlift.jpg');
  background-size: 100%;
}
#squat{
  background-image: url('squat.jpg');
  background-size: 100%;
}

それぞれidを付与してるので、idによって画像を変更してください。

jsファイル

about.js
$(document).ready(function(){
  $('#big3').carousel(
  {
    dist: 0,
    padding: 0,
    fullWidth: true,
    indicators: true,
    duration: 100,
  }
  );
  autoplay()
  function autoplay() {
      $('#big3').carousel('next');
      autoplay: true,
      setTimeout(autoplay, 3000);
  }
});

autoplayの関数を定義してます。
id=big3に対して3000ミリ秒で次のcarousel-itemを
表示するように設定してます。

オプションは下記を参照しました。

  • [1] dist: 0, => 遠近ズーム0
  • [2] padding: 0, => 中央以外の項目の余白0
  • [3] fullWidth: true, => carouselを全幅のスライダーへ
  • [4] indicators: true, => インジケータを表示
  • [5] duration: 100, => 次のスライドへ移動しきるまでの時間100ミリ秒

Image from Gyazo

最後に

この記事を書いた目的

・自分なりに工夫した点をアウトプットして、理解を深める。
・あわよくば有識者にフィードバックをもらいたい。
・私と同じ初学者からも奇譚のない意見をもらいたい。(自分だったらどうこうする的な)

筆者について

TECH::EXPERTにて4月27日より52期夜間・休日コースでruby/railsを学習している未経験エンジニアです。
ご不備等ありましたら、ご指摘ください。ちなみに本記事が初投稿になります。
言わずもがなかもしれませんが、趣味はボディメイク・筋トレでございます。
余談ですが、120kg⇨66kgまで減量して大会出場した経験があり
ダイエットについての質問はなんでも答えられるかと思います:muscle:

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

rails スラッシュを複数挟んでもnot foundにならない

同僚から

Rails のルーティングは連続するスラッシュは単一扱いされるらしい。
localhost:3000/utilities///////hoge/39
の様にスラッシュを複数挟んでもnot foundにならないのはなんでなん?

という質問を受けたので、備忘録として書いておきます。

僕が調べた感じ、railsの名前解決の流れは
https://qiita.com/kkyouhei/items/1203f5aa521c065a7097
らしい。

そこで通る
Journey::Router::Utils.normalize_path(path)
にて

 def self.normalize_path(path) 
     path = "/#{path}"
     path.squeeze!('/')
     path.sub!(%r{/+\Z}, '')
     path = '/' if path == ''
     path
 end

https://github.com/rails/journey/blob/master/lib/journey/router/utils.rb#L14

引数に含まれる文字が複数並んでいたら 1 文字にまとめる

.squeeze!('/')をしているから

https://docs.ruby-lang.org/ja/latest/method/String/i/squeeze.html

履歴にも残してはいけないものを書いていたので上げ直しです。
ごめんなさい

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

クラス継承の名前変更、削除(aliasとundef )

aliasについて

クラスの継承において、既に存在するメソッドに別の名前を割り当てたいときにaliasを使う。書き方としては、引数にメソッド名かシンボル名を指定する。

alias 別名 元の名前
または
alias :別名 :元の名前

実際のコードで確認すると、

class Class1
    def hello
        "hello"
    end
end

class Class2 < Class1
    alias old_hello hello

    def hello
        "#{old_hello}, again"
    end
end

obj = Class2.new
p obj.old_hello                 #=> "hello"
p obj.hello                     #=> "hello, again"

undefについて

定義されたメソッドをなかったことにしたいときに、undefを使う。書き方として、aliasと同様に引数にメソッド名かシンボル名を指定する。

undef メソッド
または
undef :メソッド
class Class1
    def hello
        "hello"
    end
end

class Class2 < Class1
    undef hello
end

obj1 = Class1.new
obj2 = Class2.new
p obj1.hello       #=>"hello"       
p obj2.hello       #=>エラー(undefined method `hello')              

同じクラス内に用いても、

class Class1
    undef hello
    def hello
        "hello"
    end
end

obj1 = Class1.new
p obj1.hello        #=> エラー(undefined method `hello' for class `Class1')  
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rubyの整数から下1桁や2桁取る方法

コード

x = 123456789
puts x % 10
puts x % 100
puts x % 1000
puts x % 10000

出力結果↓

9
89
789
6789

あまりで下1桁、や2桁を取ってます。
ただ、ピンポイントでほしい桁だけを取ることが出来ません。

ピンポイントでほしい桁だけを取るときは

x = 123456789
puts x.to_s.split("")[-1]
puts x.to_s.split("")[-2]
puts x.to_s.split("")[-3]
puts x.to_s.split("")[-4]

出力結果↓

9
8
7
6

訂正しました

もっと簡単に取れる方法を教えて頂いたので、訂正します。
ttakuru88さんありがとうございました。

x = 123456789
p x.digits

出力結果↓

[9, 8, 7, 6, 5, 4, 3, 2, 1]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[ruby]バッチ処理について

バッチ処理とは?

バッチ処理とは“一定量の(あるいは一定期間の)データを集め、一括処理するための処理方法”

私は、大量のデータを処理すること全般をイメージしてる。

引用元
https://www.imkk.jp/blog/what-is-batch-processing.html

バッチ処理のフロー

私の業務では、DBに一括登録するデータをスプレッドシートから取り組むバッチ処理でした。
以下では、データベースに商品を一括にスプレッドシートから取り込んで追加するという場合。

フローは以下の通り
まずrakeファイルにそのバッチ処理がどういったものなのかをdescにかく。
そのあと走らせる。
以下がそのコード

import_new_products_information.rake
namespace :batch do
    desc '新規商品の追加'
    task import_new_products_information: :environment do
        require 'batches/import_new_products_information'
        Batches::ImportNewProductsInformation.run
    end
end

走らせるファイル。
一応シート内に商品名と商品コードというカラムがあることが前提。

import_new_products_informations.rb
require 'batch/base'
require 'google_spreadsheet'

module Batches
  class ImportrProductsInformations < Batch::Base
    SPREADSHEET_KEY = '追加する商品のスプレッドシートキー'.freeze
    SHEET_NAME = '追加する商品一覧があるシートの名前 例)取り込み用など'.freeze

    def execute(_args)
      gs = GoogleSpreadsheet.new(SPREADSHEET_KEY)

      gs.spreadsheet.worksheet_by_title(SHEET_NAME).tap do |sheet|
        sheet.list.each do |row|
          products_informations.create!(
            product_name: row['商品名'],
            product_number: row['商品コード']
          )
        end
      end
    end
  end
end
google_spredsheet.rb
def worksheet_by_title(sheet_name)
    spreadsheet.worksheet_by_title(sheet_name)
 end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Thorのコマンドの中でrailsのActiveRecordを使う

以前railsのタスクの書式という記事を書きましたが、railsのタスクが引数がややこしくて、オプションもスマートに使えず余り好きではありません。

Thorでコマンドを書く方が好きなのですが、Thorの中でActiveRecordを使ってDBの操作が出来たのでメモしておきます。

以下はrailsアプリケーションの直下にthorというディレクトリを作りthor/foobarにコマンドがある想定のコードです。

railsのautoloadも効くのでthorをGemfileに追加します。

gem 'thor', '~> 0.20'

@scivola さんにコメントいただきましたがrailtiesがthorに依存しているのでGemfileには書かなくても読み込めます。ただし私の環境で試したところrequire 'thor'を書かないとuninitialized constant Thorで動きませんでした。

#!/usr/bin/env ruby

require_relative '../config/application'
Rails.application.initialize!

class Main < Thor
  desc "exec", "foobar"
  def exec
    binding.pry
  end
end

Main.start(ARGV)

これで

thor/foobar exec

で実行可能です。なんてことは無くてconfig/environment.rbのコードを丸っと持ってきただけです。なので

RAILS_ENV=production thor/foobar exec

で実行するとproduction環境で実行可能です。

spring経由で起動してないので起動が重いのが玉に傷です。やり方をちょっと調べてみて、ちなみにspring経由で起動するのにこんなタイムリーな記事があったのですが、残念ながら記事内のリンク先のgithubのリンクが死んでいてコードを見ることが出来ませんでした。spring-commands-rspecも参考になると思ってみてみましたが、私がspringの仕組み自体全く理解してないのでイマイチでよく分かりませんでした。その内調べて分かったらまた共有します。何かご存じの方いらっしゃいまたらヒントでも結構ですのでコメントお願いします!

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

複数のherokuアカウント上のRailsアプリを管理・デプロイするための情報まとめ

一台のデバイスで複数のherokuアカウントを管理・デプロイするための情報

余裕があるときに随時加筆修正します。

heroku で複数アカウントを管理するCLIのプラグインを追加する

$ heroku plugins:install heroku-accounts
$ heroku accounts:add any_account_name
$ heroku accounts:set added_account_name

heroku のssh鍵を追加する

heroku keys:add
# 自動で公開健がherokuにupされる

heroku にRailsアプリケーションをデプロイする

herokuにアプリケーションを作成する

heroku create app_name

app_nameは省略可能
アプリ名を省略した場合、heroku側で自動に生成される

この時自動でremoteのリポジトリ名がherokuに設定される。
もし、違う名前にしたい場合は、

heroku git:remote -a app_name -r <any_repository_name>

以降、pushするときは

git push <any_repository_name> master

Rails アプリケーションの設定

Railsアプリケーションのデータベースをsqlite3からpostgreSQLに変更する。

TIPS

herokuアプリケーションを削除する

herokuのデプロイに失敗したとき、作成したアプリケーションをいったん削除したい場合がある。

その場合は

heroku apps:destroy --app app_name

を実行すればいい。

herokuにRailsアプリケーションをデプロイしたとき、rake taskに失敗するときの対処法

考慮した可能性
- assets precompile関係
- bundler関係 〇
- gem関係
- その他

bundler 2.0.1を利用したrailsアプリケーションがherokuにデプロイできなくなったので原因を探ってみたらbundler 2.0.2対応が原因らしい。

つい最近まで、bundler のバージョンが2.0.1でも問題なく動作したが、おそらく2019年6月13日にリリースされたbundler 2.0.2に対応した影響のためか、2.0.1だと

could not find bundler(2.0.1) required by your /tmp/build_xxxxxx....xxxxx/Gemfile.lock

とエラーが出る。

この場合、まずローカル環境にあるGemfile.lockを一度削除したのちに、以下のコマンドを打ち

gem install bundler -v 2.0.2

bundler(2.0.2)をインストールし、bundle install (--path vendor/bundle)、bundle updateする。

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

Rails6 のちょい足しな新機能を試す48(Duration編)

はじめに

Rails 6 に追加されそうな新機能を試す第48段。 今回は、 Duration 編です。
Rails 6 では、 Duration の計算で小数点以下が丸められることが無くなりました。

Ruby 2.6.3, Rails 6.0.0.rc1, Rails 5.2.3 で確認しました。Rails 6.0.0.rc1 は gem install rails --prerelease でインストールできます。

$ rails --version
Rails 6.0.0.rc1

簡単なスクリプトを作る

今回は、動作確認用の簡単なスクリプトを書いて確認します。

bin/duration.rb
duration = 0.4.seconds

d = DateTime.parse('2019-01-01')

d += duration
d += duration
d += duration
d += duration
d += duration

p d > DateTime.parse('2019-01-01')
p d == DateTime.parse('2019-01-01')

Rails 5 では

0.4.seconds を足すとき 整数に丸められて 0.seconds として加算されてしまいます。

結果、 0.4.seconds を5回足す前も後も変化がありません。

$ bin/rails runner bin/duration.rb
false
true

Rails 6 では

整数に丸められることが無くなったので、より正確な値が得られるようになります。

$ bin/rails runner bin/duration.rb
true
false

試したソース

試したソースは以下にあります。
https://github.com/suketa/rails6_0_0rc1/tree/try048_duration

参考情報

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

ブロックとスコープを使いこなしたい【メタプログラミングRuby】

はじめに

この記事ではブロックって何?からinstance_evalってやつすげー!までやります。
ブロックとスコープの知識をスッキリさせたい方にはぴったりだと思っています。

ブロックとは

Rubyでは、do ~ end、または{ ~ }の処理の塊のことをブロックと言います。

一行で記述されるブロックは{ ~ }を使い、複数行のブロックにはdo ~ endを使う慣習があります。

ブロックを定義できるのは、メソッドを呼び出す時だけです。
ブロックはメソッドに渡され、メソッド内でyieldキーワードを使ってブロックをコールバックできます。

block.rb

def my_method(a, b)
  a + yield(a, b)
end

p my_method(1, 2){ |x, y| x + y } # => 4

ブロックの基本的な説明はここまでにして、早速内容に入っていきましょう。

内容

ブロックと束縛

ブロックを呼び出すためには、ローカル変数、インスタンス変数、selfといった環境が必要になります。
このオブジェクトに紐づけられている環境の事を束縛とも言います。

つまり、ブロックとはコードと束縛両方の集まりという事になります。

ブロックにおいて、コードはブロック内にあるコードを使用し、束縛はブロックを定義した場所にある束縛を使用します。

そして、ブロックをメソッドに渡した時は、その束縛も一緒に運ばれていきます。
メソッドにある束縛はブロックからは見る事が出来ません。

blocks.rb
def my_method
  my_var = "my_methodの変数"
  yield
end

my_var = "トップレベルの変数"
p my_method { my_var } # => "トップレベルの変数"

ブロックと束縛を理解するためには、スコープに関する知識を深める必要があります。

スコープゲート

スコープゲートとは、スコープが変化する境界線の事です。(スコープに関しての基本的な説明は省きます)
Rubyでは、下記の3つがスコープゲートになります。

  • クラス定義
  • モジュール定義
  • メソッド

実際に下記のコードを見ながら確認していきましょう。

scope_gate.rb
top_level_var = "トップレベルの変数"
local_variables # => [:top_level_var, :obj]

class MyClass #クラス定義
  my_class_var = "マイクラスの変数"
  local_variables # => [:my_class_var]

  def my_method # メソッド定義
    my_method_var = "マイメソッドの変数"
    local_variables # => [:my_method_var]
  end

  local_variables # => [:my_class_var]
end

local_variables # => [:top_level_var, :obj]

このような感じですね。

それぞれのスコープ内で束縛が変わっている事が確認できると思います。
思わぬ所で、予期せぬ変数が参照されるエラーが発生し難くなり便利ですね。

しかし、Rubyを使っていると、スコープゲートを超えて束縛を渡したい局面に遭遇する事があります。

そういう時はフラットスコープを使っていきましょう。

スコープゲートを超えて束縛を渡す(フラットスコープ)

クラス定義ではなく、メソッド呼び出しを使用する事でスコープゲートを超えることが可能になります。
(正確には、スコープゲートを超えているわけではなく、スコープゲートを使用していないだけです)

先程のscope_gate.rbを書き換えていきましょう。
もしMyClass内でtop_level_varを使いたい時はこのように書きます。

scope_gate.rb
top_level_var = "トップレベルの変数"
local_variables # => [:top_level_var, :obj]

MyClass = Class.new do #クラス定義
  my_class_var = "マイクラスの変数"
  local_variables # => [:my_class_var, :top_level_var, :obj]

  def my_method # メソッド定義
    my_method_var = "マイメソッドの変数"
    local_variables # => [:my_method_var]

  end
  local_variables # => [:my_class_var, :top_level_var, :obj]
end

local_variables # => [:top_level_var, :obj]

my_method内でmy_class_varを使いたい場合も、先程と同じように、メソッド呼び出しに書き換えます。

scope_gate.rb
top_level_var = "トップレベルの変数"

class MyClass #クラス定義
  my_class_var = "マイクラスの変数"
  local_variables # => [:my_class_var]

  define_method :my_method do # メソッド定義
    my_method_var = "マイメソッドの変数"
    local_variables # => [:my_method_var, :my_class_var]

  end
  local_variables # => [:my_class_var]
end

local_variables # => [:top_level_var, :obj]

技術的には、この方法の事を入れ子構造のレキシカルスコープと呼びます。
しかしRubyに限ってはフラットスコープと呼ばれる事が多いようです。

フラットスコープを応用すれば、自由自在にスコープを組み合わせる事ができます。
共有スコープという方法もありますが、上記の2つ(スコープゲート、フラットスコープ)を理解していれば自然と使えるようになる方法なので省きます。(気になる方は調べてみてください)

コードと束縛を好きなように組み合わせる(instance_eval)

instance_evalというメソッドがあります。

instance_evalに渡したブロックは、オブジェクトのコンテキストで評価されます。
つまり、レシーバがselfにしてから評価されるので、レシーバのprivateメソッドやインスタンス変数などにもアクセスが可能になるという事です。

また、他のブロックと同じように、instance_evalを定義した時の束縛も見ることが出来ます。

それでは、コードを見ていきましょう。

instance_eval.rb
top_level_var = "トップレベルの変数"

class MyClass
  def initialize
    @my_instance_var = "マイクラスのインスタンス変数"
  end
end

my_class = MyClass.new

my_class.instance_eval do
  self # => #<MyClass:0x00007fc4b08bdcc0 @my_instance_var="マイクラスのインスタンス変数">
  top_level_var # => "トップレベルの変数"
  @my_instance_var # => "マイクラスのインスタンス変数"
end

このような感じです。

instance_evalに渡したブロックのことは、インスタンス探査機と呼ばれます。
オブジェクトの内部を探査して、そこで実行するコードだからです。

まとめ

ブロックを上手く扱うためには、束縛を理解する必要がありました。
そして、束縛の住処であるスコープも学習しました。
instance_evalはおまけのような感じです。

気になる所や、間違っている所等ありましたらコメントください!!

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

#Ruby の pry のドルマークで元のコードを読む的なアレはなんなのか? show-source ですよ。

What is the purpose of reading the original code at Ruby's pry's dollar mark? It is a show-source.

[3] pry(main)> $ Class

From: /Users/yumainaura/.rbenv/versions/2.6.1/lib/ruby/2.6.0/json/common.rb @ line 448:
Class name: Class
Number of lines: 9

class ::Class
  # Returns true if this class can be used to create an instance
  # from a serialised JSON string. The class has to implement a class
  # method _json_create_ that expects a hash as first parameter. The hash
  # should include the required data.
  def json_creatable?
    respond_to?(:json_create)
  end
end
[2] pry(main)> show-source Class

From: /Users/yumainaura/.rbenv/versions/2.6.1/lib/ruby/2.6.0/json/common.rb @ line 448:
Class name: Class
Number of lines: 9

class ::Class
  # Returns true if this class can be used to create an instance
  # from a serialised JSON string. The class has to implement a class
  # method _json_create_ that expects a hash as first parameter. The hash
  # should include the required data.
  def json_creatable?
    respond_to?(:json_create)
  end
end

Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/2256

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

amcharts 4 Demos を使ってグラフを作成

About

amcharts関連の日本語文献のあまりの少なさから、誰かのお役に立てればと思い記載しています!

最終的には、このようなグラフを作成することができます。
7d0de671ad5679b2edef0a20c7ab9b06.gif

Environment

この記事ではmacbook(unix)にインストールしたruby 2.5.1p57, Rails 5.2.3を使っています。

amchartsとは

amcharts
javascriptでチャートを描画するためのフレームワークです。 棒、エリア、列、バー、パイ、XY、散布、ローソク足のようなチャートを描画することが出来ます。

(参考文献)
http://mikawatan.hatenablog.com/entry/2016/10/09/171130

Demosとは

amchartsが提供しているサンプルの一覧です。
実際にご覧いただいた方が早いと思います。下記よりご確認ください。
https://www.amcharts.com/demos/

使用方法

HTML内でResources(CDN)を記載することで利用できます。
商用利用の場合も、チャート内のamchartsアイコン(amchartsへのリンク)を削除しなければそのまま使用可能です。

(参考文献)
https://www.amcharts.com/online-store/
Free license
"Use anywhere you want as long as you don't mind a small amCharts attribution on charts"

Rails環境ですが、Demoをわかりやすく、そのまま使用するために、
今回は各viewファイル内に直接記載していきます。

index.html.erb
<!-- Resources -->
<script src="https://www.amcharts.com/lib/4/core.js"></script>
<script src="https://www.amcharts.com/lib/4/charts.js"></script>
<script src="https://www.amcharts.com/lib/4/themes/animated.js"></script>

これで準備完了です。

導入方法

今回は、こちらのチャートを扱います。
ページ内で下にスクロールすると、Demo sourceが置いてありますので、コピーします。

Demo_source
<!-- Styles -->
<style>
#chartdiv {
  width: 100%;
  height: 500px;
}
</style>

<!-- Resources -->
<script src="https://www.amcharts.com/lib/4/core.js"></script>
<script src="https://www.amcharts.com/lib/4/charts.js"></script>
<script src="https://www.amcharts.com/lib/4/themes/animated.js"></script>

<!-- Chart code -->
<script>
am4core.ready(function() {

// Themes begin
am4core.useTheme(am4themes_animated);
// Themes end

// Create chart instance
var chart = am4core.create("chartdiv", am4charts.XYChart);
chart.scrollbarX = new am4core.Scrollbar();

// Add data
chart.data = [{
  "country": "USA",
  "visits": 3025
}, {
  "country": "China",
  "visits": 1882
}, {
  "country": "Japan",
  "visits": 1809
}, {
  "country": "Germany",
  "visits": 1322
}, {
  "country": "UK",
  "visits": 1122
}, {
  "country": "France",
  "visits": 1114
}, {
  "country": "India",
  "visits": 984
}, {
  "country": "Spain",
  "visits": 711
}, {
  "country": "Netherlands",
  "visits": 665
}, {
  "country": "Russia",
  "visits": 580
}, {
  "country": "South Korea",
  "visits": 443
}, {
  "country": "Canada",
  "visits": 441
}];

// Create axes
var categoryAxis = chart.xAxes.push(new am4charts.CategoryAxis());
categoryAxis.dataFields.category = "country";
categoryAxis.renderer.grid.template.location = 0;
categoryAxis.renderer.minGridDistance = 30;
categoryAxis.renderer.labels.template.horizontalCenter = "right";
categoryAxis.renderer.labels.template.verticalCenter = "middle";
categoryAxis.renderer.labels.template.rotation = 270;
categoryAxis.tooltip.disabled = true;
categoryAxis.renderer.minHeight = 110;

var valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
valueAxis.renderer.minWidth = 50;

// Create series
var series = chart.series.push(new am4charts.ColumnSeries());
series.sequencedInterpolation = true;
series.dataFields.valueY = "visits";
series.dataFields.categoryX = "country";
series.tooltipText = "[{categoryX}: bold]{valueY}[/]";
series.columns.template.strokeWidth = 0;

series.tooltip.pointerOrientation = "vertical";

series.columns.template.column.cornerRadiusTopLeft = 10;
series.columns.template.column.cornerRadiusTopRight = 10;
series.columns.template.column.fillOpacity = 0.8;

// on hover, make corner radiuses bigger
var hoverState = series.columns.template.column.states.create("hover");
hoverState.properties.cornerRadiusTopLeft = 0;
hoverState.properties.cornerRadiusTopRight = 0;
hoverState.properties.fillOpacity = 1;

series.columns.template.adapter.add("fill", function(fill, target) {
  return chart.colors.getIndex(target.dataItem.index);
});

// Cursor
chart.cursor = new am4charts.XYCursor();

}); // end am4core.ready()
</script>

<!-- HTML -->
<div id="chartdiv"></div>

お気づきでしょうか?既に使用方法欄で記載した内容は、こちらのDemo sourceに必ず記載されているものです。なので、コピーをそのまま利用される方は、CDNを記載する必要はありません。

それでは、コピーした内容をそのままhtmlにペーストします。

index.html.erb
<!--コピー内容をそのままペースト-->

以上でチャートの描画ができました。
b9e9abae191c2a130c75d49593f3db76.gif

編集方法

このままでは当然ですが、サンプルデータそのままになってしまいます。
各機能について(理解している部分)を記載していきます。

Styles

cssです。フォントのサイズ等を指定できますが、指定できる項目には限りがあります。

index.html.erb
<!-- Styles -->
<style>
  #chartdiv {
    width: 100%;
    height: 500px;
    font-size: 20px;
   }
</style>

chart code

本体です。以下、コメントアウトにて詳細を記載します。

index.html.erb
<!-- Chart code -->
<script>
am4core.ready(function() {

// Themes begin
// テーマです。
am4core.useTheme(am4themes_animated);
// Themes end

// Create chart instance
//始めにインスタンスを作成しています。
var chart = am4core.create("chartdiv", am4charts.XYChart);
//X軸方向のスクロールバーです。私はいらなかったのでコメントアウトしました。
chart.scrollbarX = new am4core.Scrollbar();

// Add data
// ここにデータを入力していきます。
chart.data = [{
  "country": "USA",
  "visits": 3025
}, {
  "country": "China",
  "visits": 1882
}, {
  "country": "Japan",
  "visits": 1809
}, {
  "country": "Germany",
  "visits": 1322
}, {
  "country": "UK",
  "visits": 1122
}, {
  "country": "France",
  "visits": 1114
}, {
  "country": "India",
  "visits": 984
}, {
  "country": "Spain",
  "visits": 711
}, {
  "country": "Netherlands",
  "visits": 665
}, {
  "country": "Russia",
  "visits": 580
}, {
  "country": "South Korea",
  "visits": 443
}, {
  "country": "Canada",
  "visits": 441
}];

// Create axes
//ここからはX軸方向について
var categoryAxis = chart.xAxes.push(new am4charts.CategoryAxis());
//データフィールドのカテゴリーです。今回はcountryですね。
categoryAxis.dataFields.category = "country";
//この辺りはあまり手を加える必要はないかと思います。
categoryAxis.renderer.grid.template.location = 0;
categoryAxis.renderer.minGridDistance = 30;
categoryAxis.renderer.labels.template.horizontalCenter = "right";
categoryAxis.renderer.labels.template.verticalCenter = "middle";
//文字の向きです。このままだと270度回転して縦方向の文字列になりますので、
//横向き希望の方は0に設定すると良いです。
categoryAxis.renderer.labels.template.rotation = 270;
categoryAxis.tooltip.disabled = true;
categoryAxis.renderer.minHeight = 110;

//ここからはY軸方向について
var valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
valueAxis.renderer.minWidth = 50;

// Create series
// ようやくチャートの設定です。上記のデータフィールドの項目を使用します。
var series = chart.series.push(new am4charts.ColumnSeries());
series.sequencedInterpolation = true;
// Y軸方向はvisits
series.dataFields.valueY = "visits";
// X軸方向はcountry
series.dataFields.categoryX = "country";
// tooltipはグラフにカーソルを当てると上部に表示される内容です。
series.tooltipText = "[{categoryX}: bold]{valueY}[/]";
series.columns.template.strokeWidth = 0;

series.tooltip.pointerOrientation = "vertical";

series.columns.template.column.cornerRadiusTopLeft = 10;
series.columns.template.column.cornerRadiusTopRight = 10;
series.columns.template.column.fillOpacity = 0.8;

// on hover, make corner radiuses bigger
// ホバーした際の設定です。
var hoverState = series.columns.template.column.states.create("hover");
hoverState.properties.cornerRadiusTopLeft = 0;
hoverState.properties.cornerRadiusTopRight = 0;
hoverState.properties.fillOpacity = 1;

series.columns.template.adapter.add("fill", function(fill, target) {
  return chart.colors.getIndex(target.dataItem.index);
});

// Cursor
// カーソル設定です。非表示にしたい場合は、以下のコメントアウトのように記載します。
chart.cursor = new am4charts.XYCursor();
// chart.cursor.lineX.disabled = true;
// chart.cursor.lineY.disabled = true;

}); // end am4core.ready()
</script>

HTML

最後に、HTMLを記載します。

index.html.erb
<!-- HTML -->
<div id="chartdiv"></div>

リンクの貼り方

chartにリンクを貼りたい!という方、必見です。
下記の様な記載で実装できます。

index.html.erb
<script>
//  データの項目にidを追加
chart.data = [{
  "country": "USA",
  "visits": 3025,
  "id": 0
}, {
  "country": "China",
  "visits": 1882,
  "id": 1
}, {
  "country": "Japan",
  "visits": 1809,
  "id": 2
}, {
  "country": "Germany",
  "visits": 1322,
  "id": 3
}, {
  "country": "UK",
  "visits": 1122,
  "id": 4
}];

// script内で下記記載。最後の{}内の記載方法でid毎のページを呼び出せます。
series.columns.template.url =
      `http://hoge/users/huga/sleeps/{id.urlEncode()}`;

実装例

最後に、私が作成した"sommeil"というアプリケーションのコードの内、一部のamcharts部分を参考までに記載します。

こちらのコードでは、オリジナル要素としてチャートのグラフ内から詳細ページに飛べる仕組みを導入しています。
この部分に関して、いくらGoogle先生に聞いても出てこなかったので、記事を執筆しました...。

全編はgithubをご覧下さい。
実装イメージについては、グローバルIPで恐縮ですが公開中です。(sommeil)

_chart2.html.erb
</style>
<!-- Resources -->
<script src="https://www.amcharts.com/lib/4/core.js"></script>
<script src="https://www.amcharts.com/lib/4/charts.js"></script>
<script src="https://www.amcharts.com/lib/4/themes/animated.js"></script>
<!-- Chart code -->
<script>
  am4core.ready(function () {
    // Themes begin
    am4core.useTheme(am4themes_animated);
    // Themes end
    // Create chart instance
    var chart = am4core.create("chartdiv", am4charts.XYChart);
    //chart.scrollbarX = new am4core.Scrollbar();
    // Add data
    var sleeping_times = '<%= @sleeping_times %>'.replace(/\[/g, "").replace(/\]/g, "").split(',');
    var dates = '<%= @dates %>'.replace(/\[/g, "").replace(/\]/g, "").replace(/\s/g, "").split(',');
    var sleep_ids = '<%= @sleep_ids %>'.replace(/\[/g, "").replace(/\]/g, "").replace(/\s/g, "").split(
      ',');
    console.log(sleep_ids);
    chart.data = [{
      "day": dates[0],
      "time": sleeping_times[0],
      "id": sleep_ids[0]
    }, {
      "day": dates[1],
      "time": sleeping_times[1],
      "id": sleep_ids[1]
    }, {
      "day": dates[2],
      "time": sleeping_times[2],
      "id": sleep_ids[2]
    }, {
      "day": dates[3],
      "time": sleeping_times[3],
      "id": sleep_ids[3]
    }, {
      "day": dates[4],
      "time": sleeping_times[4],
      "id": sleep_ids[4]
    }, {
      "day": dates[5],
      "time": sleeping_times[5],
      "id": sleep_ids[5]
    }, {
      "day": dates[6],
      "time": sleeping_times[6],
      "id": sleep_ids[6]
    }, {
      "day": dates[7],
      "time": sleeping_times[7],
      "id": sleep_ids[7]
    }, {
      "day": dates[8],
      "time": sleeping_times[8],
      "id": sleep_ids[8]
    }, {
      "day": dates[9],
      "time": sleeping_times[9],
      "id": sleep_ids[9]
    }, {
      "day": dates[10],
      "time": sleeping_times[10],
      "id": sleep_ids[10]
    }, {
      "day": dates[11],
      "time": sleeping_times[11],
      "id": sleep_ids[11]
    }, {
      "day": dates[12],
      "time": sleeping_times[12],
      "id": sleep_ids[12]
    }, {
      "day": dates[13],
      "time": sleeping_times[13],
      "id": sleep_ids[13]
    }];
    // Create axes
    var categoryAxis = chart.xAxes.push(new am4charts.CategoryAxis());
    categoryAxis.dataFields.category = "day";
    categoryAxis.renderer.grid.template.location = 0;
    categoryAxis.renderer.minGridDistance = 30;
    categoryAxis.renderer.labels.template.horizontalCenter = "middle";
    categoryAxis.renderer.labels.template.verticalCenter = "middle";
    categoryAxis.renderer.labels.template.rotation = 0;
    categoryAxis.renderer.labels.template.fill = am4core.color("#fff");
    categoryAxis.tooltip.disabled = true;
    categoryAxis.renderer.minHeight = 110;
    var valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
    valueAxis.renderer.minWidth = 50;
    valueAxis.renderer.labels.template.fill = am4core.color("#fff");
    // Create series
    var series = chart.series.push(new am4charts.ColumnSeries());
    series.sequencedInterpolation = true;
    series.dataFields.valueY = "time";
    series.dataFields.categoryX = "day";
    series.tooltipText = "[{categoryX}: bold]{valueY}[/]";
    series.columns.template.strokeWidth = 0;
    series.tooltip.pointerOrientation = "vertical";
    //Go to detail page
    var current_user_id = '<%= @user.id %>'
    series.columns.template.url =
      `http://52.194.48.235/users/${current_user_id}/sleeps/{id.urlEncode()}`;
    series.columns.template.column.cornerRadiusTopLeft = 10;
    series.columns.template.column.cornerRadiusTopRight = 10;
    series.columns.template.column.fillOpacity = 0.8;
    // on hover, make corner radiuses bigger
    var hoverState = series.columns.template.column.states.create("hover");
    //hoverState.properties.cornerRadiusTopLeft = 0;
    //hoverState.properties.cornerRadiusTopRight = 0;
    hoverState.properties.fillOpacity = 1;
    series.columns.template.adapter.add("fill", function (fill, target) {
      return chart.colors.getIndex(target.dataItem.index);
    });
    // Cursor
    chart.cursor = new am4charts.XYCursor();
    chart.cursor.lineX.disabled = true;
  }); // end am4core.ready()
</script>
<!-- HTML -->
<div id="chartdiv"></div>

最後に

合わせてpiechartの作成方法なども近日公開予定です。
ご覧いただき、ありがとうございました。

筆者について

TECH::EXPERTにて4月よりruby, railsを学習している未経験エンジニアです。
記載内容に不備・不足があればご指摘いただけると幸いです。
至らぬ点ばかりですので、改善点がありましたらどんどんご指摘下さい!

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

capistrano3でpumaの運用ー設定ファイルの管理

railsユーザの多くがつかってるのではないかと思われるcapistranoとpuma。この組み合わせで、どうやってpuma起動時に任意の設定を指定するか、意外とわけわからんのでまとめた。

pumaのデフォルト仕様

なにも指定せずにpuma起動すると、 config/puma/{env}.rb, config/puma.rb, の優先順位で設定ファイルが読まれる。
puma/configuration.rb at 0cd28f373a245eca4e717bff1415b97bbc2356d2 · puma/puma · GitHub

ちなみに、ローカル環境などでrails server したときはどうも、puma, thin, webrickの順にrackハンドラを探して見つかったやつで起動するっぽい。最近のrailsではrails newするとpumaが入るので、特にいじらないかぎりrails serverしたときにはpumaが起動すると思っていい。
rack/handler.rb at 2.0.7 · rack/rack · GitHub

% rails server
=> Booting Puma
=> Rails 5.2.3 application starting in development
=> Run `rails server -h` for more startup options
Puma starting in single mode...

つまり、ローカルで起動するpumaの挙動を変更したい場合は、config/puma/development.rbをつくるか、config/puma.rbをつくってrails server起動すればいい。

capistranoをつかう場合

しかし、capistrano3-pumaをデフォルト設定でつかう場合、config/puma/{env}.rb, config/puma.rbは完全無視される。1
代わりに、capistrano3-pumaが持つ設定ファイル生成機能(後述)をもとに新規設定ファイルを作成し、デプロイサーバにアップロードし、それをpuma起動時に読むようになる。

ここでcapistrano & pumaの設定ファイルの運用方針が、大きく2つの道にわかれる。

pumaオリジナルconfig/puma/{env}.rb, config/puma.rbで運用する

capistrano3-pumaが提供する設定ファイル機能をつかわずに、従来のpumaの運用方法でやる方法。設定を緻密に詳細に指定したいときはこっちのほうがたぶんやりやすい。

必要な設定を逐一記述して、リポジトリにpushしておく。2
あとは、deploy.rbまたはdeploy/{env}.rbに、設定ファイルのパスpuma_confを設定すればいい。
Why it builds a new puma.config instead of using config/puma/production.rb? (question) · Issue #244 · seuros/capistrano-puma · GitHub

具体的にはこういう設定。

set :deploy_to, '/path/to/app'
set :puma_conf, "#{current_path}/config/puma/staging.rb"

# `current_path`の値は`deploy_to`によって決まるので、
# `puma_conf`は`deploy_to`よりあとに指定する点に注意!

こうすると、capistrano経由でpumaを起動するときに、-C /path/to/app/current/config/puma/staging.rbが指定されてくれる。

% bundle exec cap staging puma:start
00:00 puma:start
      using conf file /path/to/app/current/config/puma/staging.rb
      01 bundle exec puma -C /path/to/app/current/config/puma/staging.rb --daemon
...

capistrano3-pumaの設定ファイル生成機能をつかって運用する

capistrano3-pumaには設定ファイル生成機能puma:configがある。pumaの設定をがっちりかけなくてもそれなりの設定で動いてくれる。

cap {env} deploy:checkが実行されるときに、存在しなければ/path/to/app/shared/puma.rb(またはpuma_confに指定されたパス)を自動生成して各サーバにアップロードする。3具体的にはこのとき、cap {env} puma:configというタスクが実行されている。
cap {env} puma:configを単体で実行すると、既存ファイルが存在するしないに関係なくcapistrano管理上の設定で上書きされる

ふだんcapistrano運用者がつかうコマンドはcap {env} deploy(内部的にdeploy:checkを実行する)のはずなので、設定ファイルははじめの一回だけ自動生成され、その後都度更新されることはない。
だから、サーバ上の設定を手動で直接かきかえる運用も可能。ただし、そういう運用をおこなう場合は、cap {env} puma:configの実行に注意。手動で加えた設定は消されてしまうので。

% bundle exec cap staging puma:config
00:00 puma:config
      Uploading /path/to/app/shared/puma.rb 100.0%

# /path/to/app/shared/puma.rb
# を確認すると、設定ファイルが追加されてたり、上書きされてるのがわかる

# /path/to/app/shared/puma.rb
# ちなみに、このパスを変えたい場合は、前述した要領で`puma_conf`を指定すればいい

capistrano管理上のpuma設定を上書きする

cap {env} puma:configが実行されたときに生成されるファイルは、デフォルトでは以下に定義された値がコピーされる。

capistrano-puma/puma.rb at v3.1.1 · seuros/capistrano-puma · GitHub

これらの設定値はもちろんcapistrano設定で全部上書きできる。
config/deploy.rbconfig/deploy/{env}.rbに以下のように記述する。(すでに紹介したpuma_confの上書きと同じ要領)

set :puma_env, 'staging'
set :puma_threads, [4, 4]
set :puma_workers, 0
set :puma_preload_app, false

設定可能な設定値はこちらのリストも参考
GitHub - seuros/capistrano-puma: Puma integration for Capistrano

念のためもう一度言うと、ここで設定した設定値はpuma起動時に直接読まれるわけではない。puma:configが実行されて、shared/puma.rb(またはpuma_confで指定されたパス)ファイルにその値がハードコードされ、それが起動時(cap {env} puma:start)に読み込まれることによってpumaに渡る。
つまり、puma_threads, puma_workersなどのcapistrano管理上のpuma設定は、cap {env} puma:configが実行されてはじめてリリースされる。

capistrano管理上のpuma設定をプログラムする

ここまで見ると、設定ファイル生成機能はライトにつかえる。しかしpumaのチューニングに余念がない人々にとっては、これだけでは足りない点がある。

puma設定には、設定の「値」だけではなく、「プログラム」がかけるのである。たとえば、pumaプロセスがフォークしたときのコールバックなどをカスタム設定できる仕組みになっている。
GitHub - puma/puma: A Ruby/Rack web server built for concurrency

こういうかんじで。

on_worker_boot do
  # configuration here
end

before_fork do
  # configuration here
end

こういうことをやりたくなったら4、はじめのpumaオリジナル設定ファイルの運用に切り替えるのをおすすめするが、一応設定ファイル生成機能をつかってもできる。

cap {env} puma:configが実行されるときに、生成される設定ファイルのテンプレートがこれ。
capistrano-puma/puma.rb.erb at v3.1.1 · seuros/capistrano-puma · GitHub

つまり、このテンプレートを差し替えることができれば、設定だけじゃなくてプログラムも自由にかける。
上記のテンプレートを参考に、config/deploy/templatesまたはlib/capistrano/templates以下にカスタムテンプレートをおけばいい。これらのディレクトリがどこで決まるのかは以下でわかる。
capistrano-puma/puma.rb at v3.1.1 · seuros/capistrano-puma · GitHub

config/deploy/templates/puma.rbに以下のようなテンプレートを用意して(このテンプレートはbundle exec capが実行されるローカルになければいけない点に注意)

#!/usr/bin/env puma

puts "Hello!! This is custom puma template!!"

directory '<%= current_path %>'
rackup "<%=fetch(:puma_rackup)%>"
environment '<%= fetch(:puma_env) %>'
pidfile "<%=fetch(:puma_pid)%>"
state_path "<%=fetch(:puma_state)%>"

puma:configを実行

% be cap staging puma:config
00:00 puma:config
      Uploading /path/to/app/current/config/puma/staging.rb 100.0%

shared/puma.rbにリリースされてる。

#!/usr/bin/env puma

puts "Hello!! This is custom puma template!!"

directory '/path/to/app/current'
rackup "/path/to/app/current/config.ru"
environment 'staging'
pidfile "/path/to/app/shared/tmp/pids/puma.pid"
state_path "/path/to/app/shared/tmp/pids/puma.state"

  1. pumaのふるまいにはぱっと見変更が反映されたのかされてないのかわからない類のものも多いので、これではまる人は多い気がする。そして設定ファイルのパスに環境差分がおきてしまうのも歯がゆい! 

  2. もしサーバごとに異なるpuma設定で運用したい場合、git pushするのはよくない。 

  3. deploy:check時に自動生成する機能は、たぶんCapfileinstall_plugin Capistrano::Pumaと書いていれば有効になっている。 

  4. よくおこなわれるカスタム設定はすでにデフォルトのテンプレートに組み込まれてるので、ここを参照。capistrano-puma/puma.rb.erb at v3.1.1 · seuros/capistrano-puma · GitHub 

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

capistrano3でpumaの運用ー設定ファイルの管理どうする?

railsユーザの多くがつかってるのではないかと思われるcapistranoとpuma。この組み合わせで、どうやってpuma起動時に任意の設定を指定するか、意外とわけわからんのでまとめた。

pumaのデフォルト仕様

なにも指定せずにpuma起動すると、 config/puma/{env}.rb, config/puma.rb, の優先順位で設定ファイルが読まれる。
puma/configuration.rb at 0cd28f373a245eca4e717bff1415b97bbc2356d2 · puma/puma · GitHub

ちなみに、ローカル環境などでrails server したときはどうも、puma, thin, webrickの順にrackハンドラを探して見つかったやつで起動するっぽい。最近のrailsではrails newするとpumaが入るので、特にいじらないかぎりrails serverしたときにはpumaが起動すると思っていい。
rack/handler.rb at 2.0.7 · rack/rack · GitHub

% rails server
=> Booting Puma
=> Rails 5.2.3 application starting in development
=> Run `rails server -h` for more startup options
Puma starting in single mode...

つまり、ローカルで起動するpumaの挙動を変更したい場合は、config/puma/development.rbをつくるか、config/puma.rbをつくってrails server起動すればいい。

capistranoをつかう場合

しかし、capistrano3-pumaをデフォルト設定でつかう場合、config/puma/{env}.rb, config/puma.rbは完全無視される。1
代わりに、capistrano3-pumaが持つ設定ファイル生成機能(後述)をもとに新規設定ファイルを作成し、デプロイサーバにアップロードし、それをpuma起動時に読むようになる。

ここでcapistrano & pumaの設定ファイルの運用方針が、大きく2つの道にわかれる。

pumaオリジナルconfig/puma/{env}.rb, config/puma.rbで運用する

capistrano3-pumaが提供する設定ファイル機能をつかわずに、従来のpumaの運用方法でやる方法。設定を緻密に詳細に指定したいときはこっちのほうがたぶんやりやすい。

必要な設定を逐一記述して、リポジトリにpushしておく。2
あとは、deploy.rbまたはdeploy/{env}.rbに、設定ファイルのパスpuma_confを設定すればいい。
Why it builds a new puma.config instead of using config/puma/production.rb? (question) · Issue #244 · seuros/capistrano-puma · GitHub

具体的にはこういう設定。

set :deploy_to, '/path/to/app'
set :puma_conf, "#{current_path}/config/puma/staging.rb"

# `current_path`の値は`deploy_to`によって決まるので、
# `puma_conf`は`deploy_to`よりあとに指定する点に注意!

こうすると、capistrano経由でpumaを起動するときに、-C /path/to/app/current/config/puma/staging.rbが指定されてくれる。

% bundle exec cap staging puma:start
00:00 puma:start
      using conf file /path/to/app/current/config/puma/staging.rb
      01 bundle exec puma -C /path/to/app/current/config/puma/staging.rb --daemon
...

capistrano3-pumaの設定ファイル生成機能をつかって運用する

capistrano3-pumaには設定ファイル生成機能puma:configがある。pumaの設定をがっちりかけなくてもそれなりの設定で動いてくれる。

cap {env} deploy:checkが実行されるときに、存在しなければ/path/to/app/shared/puma.rb(またはpuma_confに指定されたパス)を自動生成して各サーバにアップロードする。3具体的にはこのとき、cap {env} puma:configというタスクが実行されている。
cap {env} puma:configを単体で実行すると、既存ファイルが存在するしないに関係なくcapistrano管理上の設定で上書きされる

ふだんcapistrano運用者がつかうコマンドはcap {env} deploy(内部的にdeploy:checkを実行する)のはずなので、設定ファイルははじめの一回だけ自動生成され、その後都度更新されることはない。
だから、サーバ上の設定を手動で直接かきかえる運用も可能。ただし、そういう運用をおこなう場合は、cap {env} puma:configの実行に注意。手動で加えた設定は消されてしまうので。

% bundle exec cap staging puma:config
00:00 puma:config
      Uploading /path/to/app/shared/puma.rb 100.0%

# /path/to/app/shared/puma.rb
# を確認すると、設定ファイルが追加されてたり、上書きされてるのがわかる

# /path/to/app/shared/puma.rb
# ちなみに、このパスを変えたい場合は、前述した要領で`puma_conf`を指定すればいい

capistrano管理上のpuma設定を上書きする

cap {env} puma:configが実行されたときに生成されるファイルは、デフォルトでは以下に定義された値がコピーされる。

capistrano-puma/puma.rb at v3.1.1 · seuros/capistrano-puma · GitHub

これらの設定値はもちろんcapistrano設定で全部上書きできる。
config/deploy.rbconfig/deploy/{env}.rbに以下のように記述する。(すでに紹介したpuma_confの上書きと同じ要領)

set :puma_env, 'staging'
set :puma_threads, [4, 4]
set :puma_workers, 0
set :puma_preload_app, false

設定可能な設定値はこちらのリストも参考
GitHub - seuros/capistrano-puma: Puma integration for Capistrano

念のためもう一度言うと、ここで設定した設定値はpuma起動時に直接読まれるわけではない。puma:configが実行されて、shared/puma.rb(またはpuma_confで指定されたパス)ファイルにその値がハードコードされ、それが起動時(cap {env} puma:start)に読み込まれることによってpumaに渡る。
つまり、puma_threads, puma_workersなどのcapistrano管理上のpuma設定は、cap {env} puma:configが実行されてはじめてリリースされる。

capistrano管理上のpuma設定をプログラムする

ここまで見ると、設定ファイル生成機能はライトにつかえる。しかしpumaのチューニングに余念がない人々にとっては、これだけでは足りない点がある。

puma設定には、設定の「値」だけではなく、「プログラム」がかけるのである。たとえば、pumaプロセスがフォークしたときのコールバックなどをカスタム設定できる仕組みになっている。
GitHub - puma/puma: A Ruby/Rack web server built for concurrency

こういうかんじで。

on_worker_boot do
  # configuration here
end

before_fork do
  # configuration here
end

こういうことをやりたくなったら4、はじめのpumaオリジナル設定ファイルの運用に切り替えるのをおすすめするが、一応設定ファイル生成機能をつかってもできる。

cap {env} puma:configが実行されるときに、生成される設定ファイルのテンプレートがこれ。
capistrano-puma/puma.rb.erb at v3.1.1 · seuros/capistrano-puma · GitHub

つまり、このテンプレートを差し替えることができれば、設定だけじゃなくてプログラムも自由にかける。
上記のテンプレートを参考に、config/deploy/templatesまたはlib/capistrano/templates以下にカスタムテンプレートをおけばいい。これらのディレクトリがどこで決まるのかは以下でわかる。
capistrano-puma/puma.rb at v3.1.1 · seuros/capistrano-puma · GitHub

config/deploy/templates/puma.rbに以下のようなテンプレートを用意して(このテンプレートはbundle exec capが実行されるローカルになければいけない点に注意)

#!/usr/bin/env puma

puts "Hello!! This is custom puma template!!"

directory '<%= current_path %>'
rackup "<%=fetch(:puma_rackup)%>"
environment '<%= fetch(:puma_env) %>'
pidfile "<%=fetch(:puma_pid)%>"
state_path "<%=fetch(:puma_state)%>"

puma:configを実行

% be cap staging puma:config
00:00 puma:config
      Uploading /path/to/app/current/config/puma/staging.rb 100.0%

shared/puma.rbにリリースされてる。

#!/usr/bin/env puma

puts "Hello!! This is custom puma template!!"

directory '/path/to/app/current'
rackup "/path/to/app/current/config.ru"
environment 'staging'
pidfile "/path/to/app/shared/tmp/pids/puma.pid"
state_path "/path/to/app/shared/tmp/pids/puma.state"

  1. pumaのふるまいにはぱっと見変更が反映されたのかされてないのかわからない類のものも多いので、これではまる人は多い気がする。そして設定ファイルのパスに環境差分がおきてしまうのも歯がゆい! 

  2. もしサーバごとに異なるpuma設定で運用したい場合、git pushするのはよくない。 

  3. deploy:check時に自動生成する機能は、たぶんCapfileinstall_plugin Capistrano::Pumaと書いていれば有効になっている。 

  4. よくおこなわれるカスタム設定はすでにデフォルトのテンプレートに組み込まれてるので、ここを参照。capistrano-puma/puma.rb.erb at v3.1.1 · seuros/capistrano-puma · GitHub 

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

今日学んだ事

7/7(日) 21:30〜0:20

●タグ

html.erb
<span>〜</apan>

・span囲った部分をインライン要素としてグループ化することができるタグ。
グループ化する事で、指定した範囲にスタイルシートを適用できたりする。

html.erb
<i>〜</i>

・フォントを「イタリック体」にする

●averageメソッド

・ActiveRecordクラスのメソッド
使用例
カラム=score
テーブル=students
クラス=Student
scoreカラムのaverageを求める

students = students.all
students.average.(:score)
#scoreの平均を小数点ありの状態で返す

小数点を四捨五入する場合

students = students.all
students.average.(:score) round
#roundメソッドを使用すると小数点を四捨五入する

●present?メソッド

・配列の中身がなければfalseを返す
・レビューの評価の平均など出すときに、レビューがない作品にfalseを返したい時などに使う

index.html.erb
<% if product.reviews.present? %>

このコードでレビューがない場合でもエラーが回避できる

●レシーバ

・インスタンスメソッドを利用するインスタンス自身の事

products = Product.all
#以下の式のレシーバはproducts
products.limit(5)

1.Productクラスからproductsインスタンスを生成
2.productsインスタンス自身がlimitメソッドを利用している
3.この時productsがレシーバとなる
ち・な・み・に
メソッドの呼び出しを「オブジェクト(インスタンス)にメッセージ(メソッド)を送る」と表現する。
今回の場合
「products(オブジェクト)にlimit(メッセージ)を送る」となる

●selfメソッド

・インスタンスメソッド内にselfを記述すると、そのメソッドを利用したインスタンス(レシーバ)が代入された変数のように扱う事ができる

def review.average
 self.review.average(:rate).round

うーん、よく分からない。。。。

●コントローラ内に記述されているlayout 'レイアウトファイル名'について

1.class MovieController < ApplicationController
2.  layout 'movie'
3.  
4.  def index
5.    @movies = Movie.all
6.  end
7.end

・MovieControllerのindexアクションが呼ばれたときに表示されるレイアウトはmovie.html.erbとなる。
・なにも指定しないとレイアウトファイルはapplication.html.erbとなる

以上

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

メモ: Ubuntu18.04-desktop 仮想マシンに ruby v2.4.5環境を作る時に実行する一連のコマンド等

自分が再度同じ作業をする時に再利用するためのメモ。
以下ではデスクトップPC上で使用する仮想化ソフトで作る仮想マシンを前提にしてるけど、クラウドPlatform系でもだいたい同様。

※なお、この記事では apt で管理されている rbenv は使わずに github から clone してセットアップする方法を扱う。

1. Ubuntu 18.04-desktop の iso イメージを入手

下記URL等から任意に欲しいisoイメージを取得する
http://cdimage-u-toyama.ubuntulinux.jp/releases/

20190707_ubuntu_1804_release.png

2. VMware や VirtualBox 等の仮想化ソフトを使ってVMを起動する

新規仮想マシンの作成とかそんなメニューから、インストーラディスクイメージファイルとして上記で取得したisoイメージを指定してインストールを進める。
仮想化ソフト側で「簡易インストール」みたいな機能を持ってれば、それを使うと楽ちん。

3. 作業に必要なパッケージをセットアップする

  • 仮想マシンのコンソールでターミナルソフトを起動して以下を実行
ip addr show          # 母艦からsshでログインする時に指定するIPアドレスを確認
sudo apt install tasksel
sudo tasksel install openssh-server
service ssh status    # sshサーバの起動状態を確認
ss -ln -t4            # sshサーバの使用ポート番号を確認
exit
  • 母艦から ssh でログインして以下を実行
cd    # HOMEディレクトリで作業
sudo apt install mysql-client libmysqlclient-dev    # mysqlを使うrubyプロジェクトだから入れておく
sudo apt install git
sudo apt install curl
sudo apt install autoconf bison build-essential libssl-dev libyaml-dev libreadline6-dev zlib1g-dev libncurses5-dev libffi-dev libgdbm5 libgdbm-dev
git clone https://github.com/rbenv/rbenv.git ~/.rbenv
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
. .bashrc
~/.rbenv/bin/rbenv init
echo 'eval "$(rbenv init -)"' >> ~/.bashrc
. .bashrc
curl -fsSL https://github.com/rbenv/rbenv-installer/raw/master/bin/rbenv-doctor | bash    # rbenvの状態確認
git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
rbenv install 2.4.5
curl -fsSL https://github.com/rbenv/rbenv-installer/raw/master/bin/rbenv-doctor | bash    # rbenvの状態確認
rbenv rehash
rbenv shell 2.4.5
gem install bundler

あとはプロジェクト次第

ソースをGitリポジトリから取得したり、
母艦とディレクトリを共有する設定をしたり、
RubiMine等のリモートインタープリタ設定などなど、
プロジェクトに応じてあれこれやる。

今回の記事は以上です。

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