20201223のRubyに関する記事は30件です。

[Ruby on Rails] kaminariの使い方

kaminariとは?

rubyのgemの一つで、ページネーションを作るためのものです。

kaminariのインストール

以下を Gemfile に追加します。

gem 'kaminari'

ターミナルでインストールします。

$ bundle install

コントローラーにページネーションのコードを追加

class PostsController < ApplicationController

def index
  @posts = Post.all
  @posts = Post.page(params[:page]).per(15)
end

end

page(params[:page]).per(15) の部分でページあたりいくつ表示させるかを設定します。
ページネーションが表示されるのは、表示される数が設定した数より多い場合です。

ビューファイルにページネーションのコードを追加

ビューでページネーションを表示させる場所に追加します。

    <%= paginate @posts %>

kaminariを日本語化する

kaminariの表示はデフォルトだと英語です。
日本語化する場合は、 config/localeskaminari_ja.yml というファイルを作成すると管理しやすいです。
kaminari_ja.yml に以下を追記します。

ja:
  views:
    pagination:
      first: "&laquo; 最初"
      last: "最後 &raquo;"
      previous: "&lsaquo; 前"
      next: "次 &rsaquo;"
      truncate: "..."
  helpers:
    page_entries_info:
      one_page:
        display_entries:
          zero: ""
          one: "<strong>1-1</strong>/1件中"
          other: "<strong>1-%{count}</strong>/%{count}件中"
      more_pages:
        display_entries: "<strong>%{first}-%{last}</strong>/%{total}件中"
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Ruby】モジュール

モジュール

  • 「インスタンス化できないクラス」のようなもの。
    • Class クラスは Module クラスのサブクラス。
    • 「クラス = モジュール + インスタンス化能力」

Mix-in

  • 制限された多重継承

  • Rubyは、クラスは1つしか継承できない。

  • Rubyは、モジュールという「クラスのようだがクラスではない」ものならば何個でも継承できる。

    • クラスがモジュールを継承することを include すると呼ぶ。

モジュールと継承

  • include したモジュールはクラスの ancestors メソッドに現れる。

  • クラスがモジュールを include すると、モジュールのインスタンスメソッドが手に入る

インスタンス化不能性

  • モジュールをインスタンス化することはできない。
  • Mix-inはあくまでも「トッピング」。主となるクラスに従として実装を提供するための「機能の集まり」(module)であり、具体的なもの(object)ではない。

名前空間

  • 異なる名前空間に属する同じ名前のクラスは、たまたま名前が同じでも別物として扱われる。
  • Rubyでは名前空間を提供するためにもモジュールを利用する習慣がある。

参照

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

irb でサクっとパフォーマンスを調べる

irb の進化が止まらない。最も先進的な REPL は Ruby の irb かもしれない1。複数行編集,シンタックスハイライト,オートインデント,メソッド名補完は衝撃的だった。

さて,2020 年 12 月 20 日リリースの irb 1.2.8 で measure という機能が追加された。
これは式の評価にかかる時間を計測してくれるもの。

Ruby で 100 万番目の素数を表示するのは

require "prime"
p Prime.lazy.drop(999999).first

でいい。答えは 15485863。
これがどのくらいの時間で実行されるのかが知りたいことがある。計測手段はいくつもあるが irb 上でサクッとできるなら,それはそれで嬉しい。

では

gem update irb

で最新の irb をインストールしておこう。記事執筆時点(2020/12/23)での最新は 1.2.9 だ。

そして,

irb -r prime

で irb を起動する。
いや,もちろん

irb

で起動しておいて,

irb(main):001:0> require "prime"

とやってもいいのだが,面倒なので,使うライブラリーは -r オプションに渡したほうがいい。

そして,

irb(main):001:0> measure

と打つと,式の評価時間を計測するモード2になる・・・はずなのだが,実はバージョン 1.2.8, 1.2.9 では何らかの手違いにより,LoadError が出るようになっている3
幸い,measure コマンドが使えるようにするためのプルリクエスト@elim さんにより出され,既に取り込まれている。

現状では,仕方ないので

irb(main):001:0> IRB.conf[:MEASURE] = true

と打とう。こんなの覚えられないけど,覚えなくていい。次のバージョンでは measure でいけるようになるはずだ。

こうすると,irb 上で式を評価するたび,いちいち時間を測ってくれる。
ではさっそく:

irb(main):002:0> Prime.lazy.drop(999999).first
processing time: 7.395823s
=> 15485863

7 秒ちょいだった。

計測モードをやめるには

irb(main):003:0> IRB.conf[:MEASURE] = false

とする。
measure コマンドが使えるようになったら,

irb(main):003:0> measure :off

でいけるようになるはずだ。

ところで,この手の速度(実行時間)計測では気をつけないといけないことがいろいろある。

irb の measure 機能は,デフォルトでは処理前後の時刻の差を取っているだけのようなので,CPU が他のことで忙しいときに計測すると長めの値が出そう。
実行時間はさまざまな要因で揺れ動く。実際,さきの 100 万番目の素数は,もう一度測ったら 6.5 秒程度であった。

計測結果が 7.395823s などと出ていても,有効数字はそんなに無い。単に Float をそのまんま表示しているだけ。
まあおそらく小数第二位の桁(0.01 の桁)には全く意味が無いだろう。

だから,

irb(main):004:0> 1+1
processing time: 0.000282s
=> 2

とやって出てくる「0.000282s」という数字には全く意味が無い。「ホトンドゼロデスネー」としか言えない。
このような〈軽すぎる式評価〉の実行時間は,多数回実行させて求める必要がある。そうなるとベンチマークテストライブラリーの出番なわけで,irb なんかでやることではなくなってしまうだろう。

なお,筆者は試してないが,計測・表示の処理はカスタマイズできるようだ。
ユーザーのホームディレクトリーの .irbrc というファイルに,

IRB.conf[:MEASURE_PROC][:CUSTOM] = proc { |context, code, line_no, &block|
  # 云々
}

てな感じで記述すればいいらしい。
これで,例えば「結果表示は小数第一位までにするぞ」とかが可能になる。

具体的なことは irb のコミットメッセージをどうぞ:
https://github.com/ruby/irb/commit/3899eaf2e21fcb8b6c1ba3fdb1ac4ec8c9b4798f


  1. 他の言語の REPL を知らずに書いてますので各言語愛好者のマサカリ・オシカリを歓迎します。 

  2. 本家では「モード」という用語は使われていない。私がこの記事で仮にそう呼ぶだけ。 

  3. よく分からないけど,gem としてインストールした場合にこの問題が起こるようだ。 

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

【Ruby】真 (true) とは何か

最近技術書を読んでいて、わかった気になってるなーと思い、
細かいことでも頻繁にアウトプットしていくことにしました。

未来の自分のための記事です。

※検証Rubyバージョン: 2.7.2

結論

Rubyではfalsenil以外のすべての値が真です。

TrueとFalse

Rubyにおいて、すべてのデータ・値は何らかのクラスに属するオブジェクトです。
もちろんtrueやfalseも例外ではありません。

irb> true.class
=> TrueClass
irb> false.class
=> FalseClass

irb> true.class.ancestors
=> [TrueClass, Object, Kernel, BasicObject]
irb> false.class.ancestors
=> [FalseClass, Object, Kernel, BasicObject]

falseとnil以外が真であるなら、0(空文字)も真です。

# 「0」は true
irb> p 0 ? true : false
true
=> true

# 「 」もtrue
irb> p '' ? true : false
(irb):15: warning: string literal in condition
true
=> true

しかし他の言語では「0」は偽であることが多く、JavaScriptでは0や空文字はfalsyです。

nilとfalse

nilとfalseを区別するにはnil?メソッドがあります。

irb> a = nil
=> nil
irb> a.nil?
=> true
irb> b = false
=> false
irb> b.nil?
=> false

Rubyのプログラムを書いていると予想外にnilが混入することがあるので、nilチェックをよくしますが、
nilオブジェクトは型変換をしてあげるとnil回避ができます。

irb> nil.to_s
=> ""
irb> nil.to_i
=> 0
irb> nil.to_a
=> []
irb> nil.to_h
=> {}
irb> nil.to_sym
NoMethodError (undefined method `to_sym' for nil:NilClass)

to_symはだめっぽい。

追記

Rubyにおける真/偽とtrue/falseを混同していたのですが、下記記事がとても参考になりました。
Ruby の真/偽と true/false は違う

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

Rails の credentials.yml.enc と master.key の関係性について(undefined method `[]' for nil:NilClass (NoMethodError))

職場で運用している Rails で作成した、顧客と収益アプリの公開用 clone を作成する際に、題名のところでハマったのでメモ用として残しておきます。

経緯

  1. GitHub のプライベートリポジトリからローカルにソースコードを clone
  2. プライバシー情報を削除後、.gitファイルを削除して、アプリ名(folder 名)を_v2へと変更
  3. git initして、リモートリポジトリへプッシュ
  4. CircleCi と連携させて、master に merge されたタイミングで自動テストを組む
  5. RDS を本番環境の db にしたいので、ローカルでcredentials.ymlに、RDS の情報を打ち込む設定を行う(このとき、credentials:editコマンドを一度使用して、なぜか一度 credential ファイルを削除、credeitials:editコマンドで再作成という謎の手順を踏んでいます)
  6. ECR にコンテナデプロイして、ECS のタスクを実行しようとしたところ、ECR のコンテナが起動していない
  7. 開発用の Dockerfile を push していたため、rails sコマンドを起動しておらず追記
  8. 一度、開発環境で動作確認しようと思い、docker buildすると、サーバーが起動しないエラー発生(undefined method `[]' for nil:NilClass (NoMethodError))

ここまでが経緯です。

AWS は一度お試しアプリで、本番環境として自動デプロイを組んだことがあるという程度で、サービスに関してよく理解しないまま進めていました。

何が問題だったのか

問題は、普通に 5 番目の項目ですね。笑

少し詳しくみると、

  • credential.yml.enc は、master.key を使用して暗号化、復号される
  • git push時に、master.key はプッシュされない(.gitignore に記載されているため)

つまり、clone してきた時点で master.key は無かった。 そして、一回目のcredentials:editコマンド時に master.key が作成された。

しかも、一度 credentials.yml ファイルを削除して、再度credentials:editコマンドを行っていたので、master.key との整合性が取れていない credentials.yml が作成されていた。

そのため暗号化も復号もできておらず、一度作成していた RDS の設定も消えていた。という感じですかね?多分

どう解決したか

この場合は、

  • 一度、master.key と credentials.yml を削除
  • credentials.editコマンドで 2 つのファイルを再作成

という工程でいけました!

結構ありがちなエラーみたいで調べたらたくさん情報が出てきたけど、いい勉強になった。

同じようなケースの方は私の屍を超えていってください!!

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

【rails】railsとjsを用いて「いいね機能」を実装してみた

今回はrailsとjsでいいね機能を実装していきたいと思います

** また最後におまけでユーザーがいいねした投稿を表示できるような機能も実装していきます**

jsを読み込んだりする説明は割愛!

参考にさせていただいた記事

https://techtechmedia.com/favorite-function-rails/
https://qiita.com/hayabusa3703/items/2b916e652a1dc85bb6e3

完成予想図

スクリーンショット 2020-12-22 21.34.19.png

下準備

ユーザーはたくさんの投稿にいいねをして、投稿もたくさんのユーザーにいいねされるので

likesテーブルを中間テーブルにした、ユーザと投稿の多対多のテーブル構造

rails g model like 

マイグレーションファイル

class CreateLikes < ActiveRecord::Migration[5.0]
  def change
    create_table :likes do |t|
      t.integer     :user_id
      t.integer     :drink_id
      t.timestamps
    end
  end
end
rails g controller likes

アソシエーション

like.rb

class Like < ApplicationRecord
  belongs_to :user
  belongs_to :drink, counter_cache: :likes_count
end

・counter_cahce: :likes_countはリレーションされているlikeの数の値をリレーション先のlikes_countというカラムの値に入れますよっていう意味です。なのでlikes_countカラムをstoriesテーブルに追加しましょう。(rails g migration AddLikes_countToStories likes_count:integerをターミナルで実行すればオッケーです。)
この文章の参照元

drink.rb

class Drink < ApplicationRecord
  has_many :likes
  has_many :liking_users, through: :likes, source: :user
end

liking_usersモデルは無いので、likesテーブルを中間テーブルにして、userモデルとアソシエーションを汲みますよーってことをrailsに伝えてます

has_manyはbelongs_toはアソシエーションを組むのが本質ではなくて、メソッドを作るメソッド。

つまり,@drink.liking_userとかやったら、その投稿にいいねしたユーザー一覧を取得できるメソッドができるし、アソシエーションも組める

user.rb

  has_many :likes
  has_many :like_drinks, through: :likes, source: :drink

これも、user.like_drinksとかやったら、そのユーザーがいいねした投稿一覧が取得できる
これは、インスタ、Twitterによくある、そのユーザーがいいねした投稿を表示する時に便利

has_manyはbelongs_toはアソシエーションを組むのが本質ではなくて、メソッドを作るメソッド。

これを覚えて帰りましょう。

いいねボタンの記述

drinks/index.html.erb

こちらは投稿一覧のページになります

<%if @drinks%>
      <% @drinks.each do |drink|%>
      <li class='list'>
        <%= link_to drink_path(drink.id) do %>
          <%= link_to user_path(drink.user.id) do%>
            <div class="user-info-timeline">
                <%=image_tag drink.user.image.variant(resize: '60x60'),class: "user-img-timeline"  if drink.user.image.attached?%> 
                <div class="username-timeline">
                  <%= drink.user.nickname %>
                </div>
            </div>
          <% end %>
        <div class='item-img-content'>
          <%= image_tag drink.image , class: "item-img" if drink.image.attached? %>

          <%# if drink.trade%>



          <%# end %>
        </div>
        <div class='item-info'>
          <h3 class='item-name'>
            <%= drink.name %>
          </h3>
          <div class='item-price'>
            <span><%= drink.price %>円<br>(税込み)</span>
            <div class='star-btn'>
              <%# image_tag "star.png", class:"star-icon" %>
              <span class='star-count'>0</span>
            </div>
          </div>
          <div class='item-explain'>
            <%= drink.explain%>
          </div>
          <div class='item-tag'>
            <% drink.tags.each do |tag| %>
              #<%=tag.tag_name%>
            <%end%>
          </div>
          <%= render "likes/like",drink: drink%>
        </div>

        <% end %>
      </li>
      <%end%>

 <%= render "likes/like",drink: drink%>

に注目して欲しいです!

まずは可読性を高めるために
画像のいいねボタンを部分テンプレートで切り出しています、

そして、,drink: drinkの部分ですが、

  <% @drinks.each do |drink|%>

のeach文内のブロック変数を、likes/likeにも適用するために変数を受け渡しています。

ブロック変数とは(分かる人は飛ばして)

ブロック変数とは、each文やらtimes文,form_withとか、そのメソッド内だけで使える変数です。
つまり、eachだったらeachから endまでの範囲無で使える変数

@drinksにはいろんな情報が、配列として入っていますが、|drink|
とすることで、配列の中の一つ一つの情報がdrinkに入っていって、@drinksにある配列の数だけ表示します

likes/_like.html.erb

パーシャル(部分テンプレート)であることを分かりやすくするために慣習的にファイル名を_likeとしてます。
ただ

<%= render "likes/like",drink: drink%>

で呼び出す時はアンダーバーはいりません

<div class="like" id="like-link-<%= drink.id %>">
  <% if current_user.likes.find_by(drink_id: drink.id) %>
    <%= link_to unlike_path(drink.id), method: :delete, remote: true do %>
        <div class = "iine__button">❤️<%= drink.likes.count %></div>
    <% end %>
  <% else %>
    <%= link_to like_path(drink.id), method: :post, remote: true do %>
        <div class = "iine__button">♡️<%= drink.likes.count %></div>
    <% end %>
  <% end %>
</div>


id="like-link-<%= drink.id %>"

がミソ。

jsで非同期で画面を切り替えたいので、idを取得できるように、投稿ごとにidを区別するために
このように記述しましょう。

<%= link_to unlike_path(drink.id), method: :delete, remote: true do %>

, remote: true

と記述することにより、

リンクを押した時にajaxが発火するので非同期で通信が行われます。

いいねボタンを押したらいいねがすでについてれば、unlike_pathそうじゃなければlike_pathに飛びます

それぞれのpathをまだ定義してないので、このままじゃルーティングエラーになってしまうので

routes.rb

  post 'like/:drink_id' ,to: 'likes#like', as: 'like'
  delete 'like/:drink_id',to: 'likes#unlike', as: 'unlike'

と記述しましょう

as: 'like' とすることにより本来ならlikes_like_path(drink.id)とパス指定をしなきゃいけないのですが、
like_path(drink.id)でlikes#likeにpostリクエストを送ることができます

これで、リンクを踏んでリクエストを送ることができたので、次はコントローラーをみていきましょう

likes_controller

class LikesController < ApplicationController
  include SessionsHelper

  before_action :set_variables

  def like  
    like = current_user.likes.new(drink_id: @drink.id)
    #redirect_to drinks_path
    # jsを用いるので画面遷移は行わない
    #binding.pry
    like.save
  end

  def unlike
    like = current_user.likes.find_by(drink_id: @drink.id).destroy
    #binding.pry
  end

  private

    def set_variables
      @drink = Drink.find(params[:drink_id])
      @id_name = "#like-link-#{@drink.id}"
    end

end

remote: trueのリンクからlike,unlikeアクションが呼び出されるので、
デフォルトの遷移先はilke.js.erb,unlike.js.erbとそれぞれなります。

「⚠︎ @id_name = "#like-link-#{@drink.id}"
とControllerにViewの処理を書くのは、MVCパターン的にあまりよろしくないと思いますね。」

とご指摘をいただいたので、あまりよく無いですが、機能的には問題無いので一旦次いきます。

likes/like.js.erb

$("<%= @id_name %>").html('<%= escape_javascript(render("likes/like", drink: @drink  )) %>');

/likes/unlike.js.erb

$("<%= @id_name %>").html('<%= escape_javascript(render("likes/like", drink: @drink  )) %>');

likes/_like.html.erbにまた戻ります

この時にまた

drink: @drink

と書いて_like.html.erbに変数を受け渡してあげましょう

この@drink

likes_controller

  private

    def set_variables
      @drink = Drink.find(params[:drink_id])
      @id_name = "#like-link-#{@drink.id}"
    end

@drinkです。

以上で実装終了です。お疲れ様でした。

おまけ、ユーザーがいいねした投稿を表紙

users/show.html.erb

 <%= link_to "#{@user.nickname}がいいねした投稿",user_likes_path(@user.id)%>

こんな感じのリンクを作成

@userhはusers#showで@user = User.find(params[:id])
とかよくある感じで定義してます

user_like_pathはまだ定義してないので

routes.rb

  get 'user/likes/:id',  to: 'users#likes',as: 'user_likes'
  resources :users do
    member do
      get :following,:followers
      # memberメソッドを使うと
      # ユーザーidが含まれてるURlを扱うようになる
    end
  end

resources :userとかみんなやると思うので、resourcesの上に get 'user/likes/:id', to: 'users#likes',as: 'user_likes'

を書きましょう

これで、 リンクを踏んだらusers#likesにGETリクエストを飛ばすことができます

users_controller

   def likes
    @user = User.find(params[:id])
    @drinks = @user.like_drinks.paginate(page: params[:page],per_page: 10).order("created_at DESC")

   end

こんな感じで実装しましょう

.paginate(page: params[:page],per_page: 10)

はページネーション をまだ取り入れてなければ書かなくて大丈夫です。

.like_drinksメソッドは

  has_many :like_drinks, through: :likes, source: :drink

とuser.rbで書いたので、ユーザーがいいねした投稿一覧を取得できます。

デフォルトで、users/likes.html.erbにリダイレクトされるので、そのビューも用意しましょう

users/likes.html.erb


  <div class="user-profile">
    <h2 class="user-profile-name"><%= current_user.nickname %></h2>
    <h2><%= image_tag @user.image.variant(resize: '100x100'),class: 'user-img' if @user.image.attached? %></h2>
    <div class="user-like-post">
      <%= link_to "#{@user.nickname}がいいねした投稿",user_likes_path(@user.id)%>
    </div>
    <div class="user-edit">
      <% if current_user?(@user) %>
      <%= link_to "プロフィールを編集",edit_user_path(@user)%>  
      <% end %>
    </div>
      <% unless current_user?(@user) %>
        <div id="follow_form">
        <% if current_user.following?(@user) %>
          <%= render 'unfollow' %>
        <% else %>
          <%= render 'follow' %>
        <% end %>
        </div>
      <% end %>
  </div> 





<% @user ||= current_user %>
<div class="stats">
  <a href="<%= following_user_path(@user) %>">
    <strong id="following" class="stat">
      <%= @user.following.count %>
    </strong>
    following
  </a>
  <a href="<%= followers_user_path(@user) %>">
    <strong id="followers" class="stat">
      <%= @user.followers.count %>
    </strong>
    followers
  </a>
</div>

<div class='main'>

  <%# 商品一覧 %>
  <div class='item-contents'>
    <h2 class='title'><%= @user.nickname%>の投稿</h2>
    <%= will_paginate @drinks%>
    <ul class='item-lists'>

      <%# 商品のインスタンス変数になにか入っている場合、中身のすべてを展開できるようにしましょう %>
      <%if @drinks%>

      <% @drinks.each do |drink|%>

      <li class='list'>

        <%= link_to drink_path(drink.id) do %>
        <div class='item-img-content'>
          <%= image_tag drink.image , class: "item-img" if drink.image.attached? %>

          <%# if drink.trade%>



          <%# end %>
        </div>
        <div class='item-info'>
          <h3 class='item-name'>
            <%= drink.name %>
          </h3>
          <div class='item-price'>
            <span><%= drink.price %>円<br>(税込み)</span>
            <div class='star-btn'>
              <%# image_tag "star.png", class:"star-icon" %>
              <span class='star-count'>0</span>
            </div>
          </div>
          <div class="item-explain">
            <%= drink.explain%>
          </div>
        </div>
        <% end %>

      </li>

      <%end%>
    </ul>
    <%= will_paginate @drinks%>
  </div>
  <%end%>
</div>

自分はこんな感じ

これで以上です。お疲れ様でした。

今までのコードのまとめ

自分のgithub

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

「小学生から楽しむ きらきら Ruby プログラミング」スモウルビーのご紹介

初めに

ご覧頂き、ありがとうございます。
名古屋でプログラミング講師を致しておりますアトリヱ未來と申します。
普段は、プログラミングを学びたい初心者の方々(小中学生〜大人)を対象に、HTML/CSS や Ruby on Rails、C言語(Arduino) を教えております。
今日は Ruby 3.0 リリースということもあり、それに因んだことを書きたい気持ちもありましたが、技術的な解説は先達の方々が記してくださっているので、ここでは少し指点を変えて、お子様向けのプログラミングのご紹介です。



小学生から楽しむ きらきら Ruby プログラミング (Amazon)

テキストプログラミング言語 と ビジュアルプログラミング言語

プログラミングを構成する要素として、逐次、反復、条件 があります。
例えば、以下のコードです。

puts "Hello World!"

3.times do
  puts "Congratulations! Ruby 3.0!"
end

n = gets.to_i
if n.even?
  puts "偶数です"
else
  puts "奇数です"
end

「おまじない」なしに平易に書けるRubyのおかげで、
プログラミングが分からなくても、英語が読めるならば、ほぼ自明です。

しかしながら、小学生となると、英語が読めません。また、キーボードの入力も大変です。
お子様向けにC言語(Arduino)を教えていますが、ほとんどの子はタイピングはできませんし、コピーはどうするの? など、コーディングする上での基礎的なキーボードの使い方を指導するところから始まります。

そこで、登場したのが、ビジュアルプログラミング言語と呼ばれるGUIベースのプログラミング言語です。
ブロックを組み立てることで、順次、反復、条件の3つがコーディングが可能です。
有名なところでは、スクラッチ(Scratch)があります。

1280px-Scratch_screenshoot_ja.jpg
(画像はWikipediaより引用)

小学校におけるプログラミング教育

プログラミング思考を養い、社会に情報技術が活きていることを学ぶことが目的となっているようです。
プログラミング思考とは、「意図を満たすべく、適宜ブロックが配置できる能力」の事らしく、
要は、アルゴリズムを理解し、逐次、反復、条件で成り立つコードを組み立てられるようになりましょうということのようです。
そして、家電や車など、身の回りの様々な分野で、コンピュータが使われているので、それを動かしているプログラム(=コンピュータへの指示書)を書けるようになりましょう、ということのようです。

文部科学省:小学校プログラミング教育の手引(第三版) を読んだ雑感です)

SmalRuby スモウルビー とは

島根県在住の高尾宏治さん(NPO法人 Ruby プログラミング少年団 理事長)が開発したプログラム言語です。

ブロックによるビジュアルプログラミングと、
テキストによるRubyコーディングが両方できるのが特徴です。

https://smalruby.jp/smalruby3-gui/
をブラウザで開くと体験できます。

スクリーンショット 0002-12-24 23.36.48.jpg

ブラウザベースなので、Mac の他、iPad でも利用可能ですが、
テキストベースでコーディングを行う際には、無線キーボードがあると快適です。
(iPhoneはさすがに画面が小さく難しかったです)

今日から君はプログラマー! プログラミング体験

猫が鼠を追いかけるゲームを作ります。
ドラッグ&ドロップでブロックを組み立てます。
緑の旗を押すと、猫が鼠に向かって動き、「捕まえた」と言います。

スクリーンショット 0002-12-24 22.29.14.jpg

同じプログラムをテキストベースでも書くことができます。

スクリーンショット 0002-12-24 22.30.35.jpg

同じプログラムをコーディングするに際し、
始めてのうちは、ビジュアルプログラミング
学習が進むにつれ、より本格的なテキストベースのRubyプログラミングへと
繋げられるように配慮されている点が、本当に素晴らしいと思います。

ちなみに、両者は連動しているので、
ブロックを操作するのが大変に感じたわたくしは、
キーボードからテキストベースで入力いたしました。

コードのアップロードやダウンロードも可能となっていますので、
教材としての配布も容易で、とても良いと思います。

正多角形を描く

三角形の内角の和は180度です。

四角形は、三角形二個に分割することができますから、
四角形の内角の和は180×2=360度です。
ですので、正四角形(=正方形)の一つの角の大きさは、360 ÷ 4 = 90度 です。

一般にN角形は、三角形(Nー2)個に分割することができますから、
N角形の内角の和は180×(N−2)度です。
ですので、正N角形の一つの角の大きさは、180×(N−2) ÷ N 度 です。

これを踏まえて、正N角形を描くコードは次のようになります。
(ここでは N = 5 として、正五角形を描いています)

スクリーンショット 0002-12-24 22.49.31.jpg
スクリーンショット 0002-12-24 22.49.45.jpg

正八角形を45度ずつ回転させて描くと次のような綺麗な幾何学模様も描くことができます。
スクリーンショット 0002-12-24 23.03.26.jpg

スクリーンショット 0002-12-24 23.03.53.jpg

終わりに

簡単なご紹介でしたが、お子様のプログラミング入門には最適なテキストかと思います。
(教師用の付録として指導上の注意点も巻末に紹介されています)

テキストには、他にも
音楽をつくったり、なぞなぞゲームや、シューティングゲーム、
マイクロビットを使った簡単な電子工作がご紹介されています。

この冬休み、お子様とご一緒にプログラミングを楽しまれてはいかがでしょうか?
みなさまのお役に立てば幸いです。

参考


小学生から楽しむ きらきら Ruby プログラミング (Amazon)


NPO法人 Ruby プログラミング少年団

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

[メモ] ?で終わるメソッド

勉強用のメモ代わりとして記事にさせていただきます。

?で終わるメソッド

Rubyのメソッド名は?で終わらせることができる。?で終わるメソッドは慣習として真偽値を返すメソッドになっている。

# 空文字列ならtrue、そうでもなければfalse
''.empty?   #=> true
'abc'.empty?   #=> false

# 引数の文字列が含まれていればtrue、そうでもなければfalse
'movie'.include?('mo')   #=> true
'movie'.include?('at')   #=> false

# 奇数ならtrue、そうでもなければfalse
3.odd?   #=> true
4.odd?   #=> false

?で終わるメソッドは自分で定義することができる。

# 2の倍数ならtrue、それ以外はfalseを返す
def multiple_of_two?(n)
  n % 2 == 0
end
multiple_of_two(1) #=> false
multiple_of_two(2) #=> true
multiple_of_two(3) #=> false

まとめ

真偽値を返す目的のメソッドであれば、?で終わらせるようにした方が良い。

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

【Rails】フォームのオートフォーカスによる画面エラー

Railsで個人アプリを開発しているときに発生したエラーです。
備忘録としてエラーの発見から解消までを記します。

事象

スマートフォン(OS:iOS、ブラウザ:Google Chrome,Safari)でサインアップページにアクセスすると、ページのレイアウトが一瞬だけ表示された後、「このページは開けません。」というエラーが発生する。

image.png

ただし、該当URLを直打ちでアクセスするとページが正常に表示される。(これについては最終的に謎のまま終わりました・・・)

調査

他に同様のエラーが出るページは無いか探したところ、プロフィール編集ページでも発生しました。

サインアップページとプロフィール編集ページの共通点は、ユーザー情報を入力するフォームがあることでした。

サインアップページ

image.png

プロフィール編集ページ

image.png

原因

1画面内にautofocus: trueのフォームが複数存在すると、スマートフォンで閲覧した際にエラーが発生するようです。
そのため、autofocus: trueを一番最初の入力フォームにのみ残し、残りのフォームから消したところエラーは解消されました。
PCで閲覧する際にはエラーが出ないので見落としていました・・・
フォームをコピペで増やしていくのもいいですが、この辺りをしっかりメンテナンスしないといけないですね・・・。

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

lib/配下のファイルをリロードするたびに再読み込みする

めも

TL;DR

config/application.rb
...
config.eager_load_paths += ["#{Rails.root}/lib"]
...

参考記事: https://stackoverflow.com/questions/3282655/ruby-on-rails-3-reload-lib-directory-for-each-request

requireは不要になるので削除。

開発環境でのみonにする

開発時にしか行わないことなので、本番で毎回読み込まないようにするには以下のように書く。

config/application.rb
...
config.eager_load_paths += ["#{Rails.root}/lib"] if Rails.env.development?
...
呼び出し側
require './lib/invoice_pdf' unless Rails.env.development?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

「OpenSSH keys only supported if ED25519 is available」のエラーの解決方法

目標

エラーを解決してデプロイを完了させる。

本番環境

・Ruby: 2.5.7
・Rails: 5.2.4
・AWS:EC2
・OS: macOS Catalina

解決方法

開発環境にてGemを追加

1.Gemを追加

Gemfile
gem 'ed25519'
gem 'bcrypt_pbkdf'

bundleインストールを行い、再度capistranoの自動デプロイコマンドを実行する

ターミナル
$ bundle install
$ bundle exec cap production deploy

エラー文にもこのgemファイルを入れろという指示があったので試してみたら治りました。

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

[Rails]本番環境のデータベースをリセットする方法(Capistrano版)

はじめに

前提

・Railsを使用してアプリケーションを開発
・Capistranoでの自動デプロイを実装している
・AWSのEC2にてサーバーを構築している
・RDSでMySQLを使用している

背景

私は開発環境では、rails db:migrate:resetにていつもデータベースを作り直していましたが、本番環境ではどのようにすればいいのかという疑問から実装しました。

本番環境のデータベースをリセットする

まずはターミナルを用いてEC2で自分のアプリケーションフォルダの階層まで進む。
Capistranoでの実装をしているのでミスをしないように基本的にはcurrentディレクトリで作業するようにする。

terminal
[ec2-user@ip-222-22-2-222 アプリ名]cd current
current
RAILS_ENV=production DISABLE_DATABASE_ENVIRONMENT_CHECK=1 bundle exec rails db:drop

これでデータベースが消去することができました。

データベース再度作成する

今回はRDSにMySQLを導入している前提でお話していきます。
まず引き続きターミナルは同じディレクトリでmysqlに接続します。

mysql -u (マスタユーザ名) -p -h (エンドポイント)
「-u」はユーザ名、「-p」はパスワードの入力、「-h」は接続先の情報を表すオプションです。
なお、「エンドポイント」は、RDSメニューで確認し、入力してください。
「パスワード」は、「マスターパスワード情報」で設定したパスワードを入力します。

terminal
mysql -u root -p -h rds-mysql-server.xxx.ap-northeast-1.rds.amazonaws.com

このコマンドを打つとpasswordの入力が求められるので入力すると無事mysqlに接続が完了しました。

削除したアプリケーション名と同じ名前をつけデータベースを再度作成します。

terminal
mysql> create database アプリケーション名;

再度本番環境でmigrateを実行します。

terminal
bundle exec rails db:migrate RAILS_ENV=production

これでデータベースのリセットは終了です。
あとは本番環境でcapistranoのコマンドを叩いて終了です

terminal
bundle exec cap production deploy

お読みいただきありがとうございました

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

roman numerals

Ubuntu-20.04.1 ruby-2.7.1p83

お題

アラビア数字(arabic numerals)を受け取って,ローマ数字(roman numerals)を返すmethodを書きなさい

ローマ数字は記号の組み合わせ

ローマ数字は1,5,10,50,100,500,1000を1文字で表現できるらしく、その組み合わせで数字を表現する
じゃあ、受け取った数字を大きい数から割っていったらいいだけじゃん、と思ってまずは一気に書き上げた

def roman_numerals(num)
  m, modulo = div(num,1000)
  d, modulo = div(modulo,500)
  c, modulo = div(modulo,100)
  l, modulo = div(modulo,50)
  x, modulo = div(modulo,10)
  v, modulo = div(modulo,5)

  val = 'M' * m + 'D' * d + 'C' * c + 'L' * l + 'X' * x + 'V' * v + 'I' * modulo
  return val
end

def div(num, waru)
  div = num / waru
  modulo = num % waru

  return div, modulo
end

[1,2,4,5,6,9,10,11,14,15,19,38,42,49,51,97,99,439,483,499,732,961,999,1999].each do |number|
  puts "#{number} : #{roman_numerals(number)}"
end

結果

1 : I
2 : II
4 : IIII
5 : V
6 : VI
9 : VIIII
10 : X
11 : XI
14 : XIIII
15 : XV
19 : XVIIII
38 : XXXVIII
42 : XXXXII
49 : XXXXVIIII
51 : LI
97 : LXXXXVII
99 : LXXXXVIIII
439 : CCCCXXXVIIII
483 : CCCCLXXXIII
499 : CCCCLXXXXVIIII
732 : DCCXXXII
961 : DCCCCLXI
999 : DCCCCLXXXXVIIII
1999 : MDCCCCLXXXXVIIII

これで完成と思うじゃないですか。ところがそうはいかないのがローマ数字
3はIII、38はXXXVIIIなのはいい。しかし、4はIV、9はIX、99はXCIXなのである。誰だよこんなめんどくさい数字思いついた奴は

4と9は例外

プログラムというよりローマ数字の勉強になってきたが、このややこしいルールを理解するキーは「4」と「9」である。上述のとおり4がIV(5-1)、9がIX(10-1)。4と9はVやXという基本の数字の左側に端数のIが来て、基本の数字から端数を牽くという構造になってる
なので、14はXIV(10+(5-1))、42はXLII((50-10)+2)、49はXLIX((50-10)+(10-1))。49はIL(50-1)じゃないことに注意。同様に99はIC(100-1)ではなくXCIX((100-10)+(10-1))なのである。もーややこしい
つまり、一桁一桁ごとに計算していけばなんとかなりそうだ

def roman_numerals(num)
  m, modulo  = calcDiv(num,1000)
  d, modulo, cm = calcDiv(modulo,500)
  c, modulo, cd = calcDiv(modulo,100)
  l, modulo, xc = calcDiv(modulo,50)
  x, modulo, xl = calcDiv(modulo,10)
  v, modulo, ix = calcDiv(modulo,5)
  i, modulo, iv = calcDiv(modulo,1)

  val = 'M' * m + 'CM' * cm + 'D' * d + 'CD' * cd + 'C' * c + 'XC' * xc + 'L' * l + 'XL' * xl + 'X' * x + 'IX' * ix + 'V' * v + 'IV' * iv + 'I' * i
  return val
end

def calcDiv(num, waru)
  div = num / waru
  modulo = num % waru
  reigai = 0

  if waru == 500
    if div == 1 and num - 900 >= 1
      div = 0
      reigai = 1
    end
  elsif waru == 100
    if div == 4
      div = 0
      reigai = 1
    end
  elsif waru == 50
    if div == 1 and num - 90 >= 1
      div = 0
      reigai = 1
    end
  elsif waru == 10
    if div == 4
      div = 0
      reigai = 1
    end
  elsif waru == 5
    if div == 1 and num == 9
      div = 0
      reigai = 1
    end
  elsif waru == 1
    if div == 4
      div = 0
      reigai = 1
    end
  end

  return div, modulo, reigai
end

汚いコードだが、reigaiという変数が4や9の例外的な表記をするかどうかの判定用の変数とした。とりあえずこれで条件を分けられたでしょう。実行。

1 : I
2 : II
4 : IV
5 : V
6 : VI
9 : IXIV
10 : X
11 : XI
14 : XIV
15 : XV
19 : XIXIV
38 : XXXVIII
42 : XLII
49 : XLIXIV
51 : LI
97 : XCXLVII
99 : XCXLIXIV
439 : CDXXXIXIV
483 : CDLXXXIII
499 : CDXCXLIXIV
732 : DCCXXXII
961 : CMCDLXI
999 : CMCDXCXLIXIV
1999 : MCMCDXCXLIXIV

ん?なんか色々おかしいぞ。おかしいのは9、19、49、97、99、439、499、961、999、1999。つまり9絡みで何かが起きてる。
よく見ると、9はIXが出てほしいが、IVというのが余計についてる。19も同じく。つまり4が余分についてるんだな。
プログラムを見直すとなるほど、例えば7行目でcalcDivを呼び出して、9の時は例外的な表記(IX)しますよーとしてるのはいいのだけど、それで処理を終えるべきところを次の行に進んでまたcalcDivを呼んでしまい、9%5の答えは4だからIVを表記しなければ!となっているなので、roman_numeralsのvalの前に

 if cm == 1
   cd = 0
 end
 if xc == 1
   xl = 0
 end
if ix == 1
   iv = 0
 end

を追加して、それぞれの位で9を表記することになったら、4はもう表記するなと定義してやった。すると

1 : I
2 : II
4 : IV
5 : V
6 : VI
9 : IX
10 : X
11 : XI
14 : XIV
15 : XV
19 : XIX
38 : XXXVIII
42 : XLII
49 : XLIX
51 : LI
97 : XCVII
99 : XCIX
439 : CDXXXIX
483 : CDLXXXIII
499 : CDXCIX
732 : DCCXXXII
961 : CMLXI
999 : CMXCIX
1999 : MCMXCIX

と、ちゃんと出力できた。どうだ古代ローマ人見たか。refactoringとかはもう体力がないので一旦ここで勘弁してください

参考記事


  • source ~/MasahiroOba/grad_members_20f/members/MasahiroOba/roman_numerals.org
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

変数の中身を意識したい!

言語

Ruby

対象読者

  • 変数の型を意識していない方。
  • プログラミングを勉強したばかりの方
  • 変数がよくわからない方
  • RubyでNoMethodErrorが発生した際に原因がパッと思い浮かばない。

記事の内容

変数や型の難しい箇所(参照型、値型、null、スコープなど)は説明せず、変数のイメージをつかんで頂けるように作成しております。

最低限知って欲しいこと

変数の中身を意識すること!

これだけです。これは静的でも動的でも変わらず重要なことです。
イメージ図はこんな感じです。

スクリーンショット

変数の中身を意識しないプログラマーは往々にしてこのような書き方をします。

変数さん! 料理をしてください!!

スクリーンショット 2020-12-23 16.27.25.png

変数の中身が大工さんだと知らないんですね。なので、大工さんに対して料理をしてと言ってしまいます。
コード的に説明すると以下の感じです。

#大工クラスを定義しています。
class Carpenter
#建設メソッドを定義しています。
  def construction
    puts "建設します"
  end
end
#大工クラスを実体化しています。
hennsuu = Carpenter.new
#大工クラスに定義されていないメソッドを呼び出しています。
hennsuu.cooking

分かるでしょうか?実際に存在しているクラスを用いてもう一度説明します。
RubyのStringクラスとIntegerクラスの公式ドキュメントをみてください。

サイトの中で検索(command + F)でupcaseと検索してください。
Stringではヒットしますが、Integerではヒットしません。
これはStringクラスではupcaseメソッドが定義されていますが、Integerクラスではupcaseメソッドが定義されていないことを示します。
では、実際にコードを実行してみましょう。

hennsuu = "abcdef"
puts hennsuu.upcase #"ABCDEF"
hennsuu2 = 12345
puts hennsuu2.upcase #NoMethodError
hennsuu.class # String
hennsuu.class # Integer

NoMethodエラーが出ましたね。これはIntegerクラスにはupcaseメソッドが定義されていないためにエラーが発生しています。
このように、変数の中身を意識せずもっと言えば変数の中身のクラスを意識せずにプログラムは書けないのです。

まとめ

変数の中身を意識しないと思わぬバグやエラーに苦しめられることがあります。
変数の中身は意識するようにしましょう!

外部サイト

今回使用している可愛い絵に関しては下記サイトにて取得したものです。
素材に関しての著作権はみふねたかし様が保持されております。
https://www.irasutoya.com/

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

Rails に CircleCIを導入する

はじめに

Rails に CircleCI を導入して GitHub に push すれば自動でテストなどが走るようにしていきます。

手順

  1. CircleCI の自分のアカウントで CI を走らせたいリポジトリを選択
  2. .circleci 以下に config.yml を作成
  3. push する

実装

Rails のアプリ作成(MySQLで作ります。)

$ rails new circleci_practice -d mysql

scaffoldで適当なCRUDを作成します。

circleci_practic $ rails g scaffold users name:string email:string

Gemfile に rspec を追加し bundle install

適当な RSpec を一つ書いてみてください。

.circleci ディレクトリを作り config.yml を作成

そのままでは Docker上でMySQLに接続できないのでhostの部分を編集してください。
とりあえず以下のような感じでいいと思います。

confg/database.yml
test:
  <<: *default
  database: circleci_parctice_test
  # ↓これを追加
  host:  <%= ENV['DATABASE_HOST'] } %>
circleci/config.yml
version: 2

jobs:
  test:
    docker:
      - image: circleci/ruby:2.7.1-node
        environment:
          RAILS_ENV: test
          DATABASE_HOST: 127.0.0.1 
      - image: circleci/mysql:5.7
        environment:
          MYSQL_ALLOW_EMPTY_PASSWORD: true
          MYSQL_ROOT_PASSWORD: ''
          MYSQL_DATABASE: circleci_parctice_test
    working_directory: ~/circleci_parctice
    steps:
      - checkout
      - run:
          name: "bundle install"
          command: bundle install --path vendor/bundle
      # MySQLと接続できるまで待ちます
      - run:
          name: "waiting DB start"
          command: dockerize -wait tcp://127.0.0.1:3306 -timeout 1m
      - run:
          name: "initialize DB"
          command: |
            bundle exec rake db:create
            bundle exec rake db:migrate
      - run:
          name: RSpec
          command: bundle exec rspec


workflows:
  version: 2
  workflows:
    jobs:
      - test

これで push すると自動でCIが走ると思います。

おわりに

他にも rubocop を入れたり、 slim-lint を入れたりするといい感じになると思います。
yarn install や bundle install で cache を使ったりすると速くなります。

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

第9回

assert_equal

assert_equalとは,equalか否かを確認する関数であり,今回はこの関数がどのような振る舞いをするのかを確認する.

assert_equal.rb
p 1==1

def assert_equal(expected,result)
  return expected==result
end

p assert_equal(1,1)

出力結果は以下のようになる.

true
true

colorize

 ターミナルの出力に色付けするStringの拡張機能です.通常の色付け方法だとコードが覚えにくい,読みにくいというデメリットを抱えているが,このgemがあればそれらの問題を解決することができる.

colorize.rb
require 'colorize'

def assert_equal(expected, result)
  if expected == result
    puts 'true'.green
  else
    puts 'false'.red
  end
end

p "assert_equal(1, 1)"
p "assert_equal(1, 2)"

assert_equal(1, 1)
assert_equal(1, 2)


  • source ~/grad_members_20f/members/NobuakiMori/L09_assert_equal.org
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

anyenvでrbenvとnodenvを管理する

はじめに

anyenvを導入した理由はrubyの管理をrbenvからanyenv経由でrbenvをインストールする形式に変えたかったからです。ついでにNode.jsのバージョン管理もnodebrewからnodenvに切り替えました。こうすることで、.zshrc(bashを使う人は.bash_profile)にanyenvのパスを通すだけで済むのでファイルをきれいに保つことができます。

anyenv経由でnodenvをインストール

Node.jsをいったんアンインストールする

こちらの記事を参考にさせていただきました。また、rm -rf .npm-globalでグローバルインストールしたパッケージ等も削除しました。グローバルインストールをしない理由ということでこちらの記事も参考にさせていただきました。

anyenvを導入

brewでインストールしていきます。

$ brew install anyenv
$ echo 'eval "$(anyenv init -)"' >> ~/.zshrc
$ exec $SHELL -l
$ anyenv install nodenv
$ exec $SHELL -l

~/.zshrcの部分は使っているシェルに応じて変更してください。これだけで完了です!

プラグインの導入

anyenv-update

これはnodenvを含めた~envをまとめてアップデートできるanyenv updateとういコマンドを提供してくれます。

$ mkdir -p $(anyenv root)/plugins
$ git clone https://github.com/znz/anyenv-update.git $(anyenv root)/plugins/anyenv-update

$anyenv rootの場所はホームディレクトリ直下です。

$ anyenv root  
/Users/shuntagami/.anyenv

nodenv-default-packages

anyenv プラグインのanyenv-update と、npmインストール時にデフォルトでいっしょにインストールしておくパッケージを指定できるnodenvプラグインです。

$ mkdir -p $(anyenv root)/plugins
$ git clone https://github.com/znz/anyenv-update.git $(anyenv root)/plugins/anyenv-update
$ mkdir -p "$(nodenv root)"/plugins
$ git clone https://github.com/nodenv/nodenv-default-packages.git "$(nodenv root)/plugins/nodenv-default-packages"
$ touch $(nodenv root)/default-packages

default-packagesの中身にインストールしたいパッケージを指定します

default-packages
yarn
typescript
ts-node
typesync

nodenvでNode.jsをインストール

$ nodenv install -l
$ nodenv install 15.4.0
$ nodenv global 15.4.0

最新の15.4.0をインストールしました。これでnode -vと打ってインストールしたバージョンが入っていれば成功です!

anyenv経由でrbenvをインストール

rbenvの詳細はこちらの記事でまとめました。nodenvの導入時と同様、いったんrbenvをアンインストールする必要があります。

rbenvをアンインストールする

こちらの記事を参考にさせていただきました。

再びrbenvをインストール

$ anyenv install rbenv
$ rbenv install 2.6.5
$ rbenv global 2.6.5

バージョンは適宜変えてください。以上で完了です!

補足 railsコマンドがみつからないと言われた時

私はコマンドも消してしまったので再インストールしました。

$ gem install bundler
$ gem install rails --version='6.0.0'
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

anyenvに移行しました!(anyenvでrbenvとnodenvを管理する)

はじめに

anyenvを導入した理由はrubyの管理をrbenvからanyenv経由でrbenvをインストールする形式に変えたかったからです。ついでにNode.jsのバージョン管理もnodebrewからnodenvに切り替えました。こうすることで、.zshrc(bashを使う人は.bash_profile)にanyenvのパスを通すだけで済むのでファイルをきれいに保つことができます。

anyenv経由でnodenvをインストール

Node.jsをいったんアンインストールする

こちらの記事を参考にさせていただきました。また、rm -rf .npm-globalでグローバルインストールしたパッケージ等も削除しました。グローバルインストールをしない理由ということでこちらの記事も参考にさせていただきました。

anyenvを導入

brewでインストールしていきます。

$ brew install anyenv
$ echo 'eval "$(anyenv init -)"' >> ~/.zshrc
$ exec $SHELL -l
$ anyenv install nodenv
$ exec $SHELL -l

~/.zshrcの部分は使っているシェルに応じて変更してください。これだけで完了です!

プラグインの導入

anyenv-update

これはnodenvを含めた~envをまとめてアップデートできるanyenv updateというコマンドを提供してくれます。

$ mkdir -p $(anyenv root)/plugins
$ git clone https://github.com/znz/anyenv-update.git $(anyenv root)/plugins/anyenv-update

$anyenv rootの場所はホームディレクトリ直下です。

$ anyenv root  
/Users/shuntagami/.anyenv

以下がanyenv updateコマンドを実行したときの私の例です。

$ anyenv update
Skipping 'anyenv'; not git repo
Updating 'anyenv/anyenv-update'...
Updating 'nodenv'...
Updating 'nodenv/node-build'...
Updating 'nodenv/nodenv-default-packages'...
Updating 'nodenv/nodenv-vars'...
Updating 'rbenv'...
Updating 'rbenv/ruby-build'...
Updating 'tfenv'...
Updating 'anyenv manifest directory'...

nodenv-default-packages

anyenv プラグインのanyenv-update と、npmインストール時にデフォルトでいっしょにインストールしておくパッケージを指定できるnodenvプラグインです。

$ mkdir -p $(anyenv root)/plugins
$ git clone https://github.com/znz/anyenv-update.git $(anyenv root)/plugins/anyenv-update
$ mkdir -p "$(nodenv root)"/plugins
$ git clone https://github.com/nodenv/nodenv-default-packages.git "$(nodenv root)/plugins/nodenv-default-packages"
$ touch $(nodenv root)/default-packages

default-packagesの中身にインストールしたいパッケージを指定します

default-packages
yarn
typescript
ts-node
typesync

nodenvでNode.jsをインストール

$ nodenv install -l
$ nodenv install 15.4.0
$ nodenv global 15.4.0

最新の15.4.0をインストールしました。これでnode -vと打ってインストールしたバージョンが入っていれば成功です!

anyenv経由でrbenvをインストール

rbenvの詳細はこちらの記事でまとめました。nodenvの導入時と同様、いったんrbenvをアンインストールする必要があります。

rbenvをアンインストールする

こちらの記事を参考にさせていただきました。

再びrbenvをインストール

$ anyenv install rbenv
$ rbenv install 2.6.5
$ rbenv global 2.6.5

バージョンは適宜変えてください。以上で完了です!

補足 railsコマンドがみつからないと言われた時

私はrailsコマンドも消してしまったので再インストールしました。

$ gem install bundler
$ gem install rails --version='6.0.0'
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Ruby】searchとeach_with_indexを使用したプログラム 

論理的思考強化の為、ドリルの復習をしています。
初学者のため、何かお気づきの点がありましたらご教示いただけますと幸いです。
今回は私の失敗談です。

問題

以下の配列から、数を探して何番目に含まれているか結果を返すsearchメソッドをeach_with_indexを用いて作成してください。

ruby.rb
input = [3, 5, 9 ,12, 15, 21, 29, 35, 42, 51, 62, 78, 81, 87, 92, 93]

模範解答

ruby.rb
def search(target_num, input)

  input.each_with_index do |num, index|
    if num == target_num
      puts "#{index + 1}番目にあります"
      return
    end
  end
  puts "その数は含まれていません"
end

input = [3, 5, 9 ,12, 15, 21, 29, 35, 42, 51, 62, 78, 81, 87, 92, 93]
search(11, input)

#結果
その数は含まれていません

私のミス解答

ruby.rb
def search(target_num, input)

  input.each_with_index do |num, index|
     if num == target_num
        puts "#{num}は、#{index + 1}番目にあります。"
     else
        puts "#{num}は含まれていません。"
     end
   end
end

input = [3, 5, 9 ,12, 15, 21, 29, 35, 42, 51, 62, 78, 81, 87, 92, 93]
search(12, input)


#結果
3は含まれていません。
5は含まれていません。
9は含まれていません。
12は、4番目にあります。
15は含まれていません。
21は含まれていません。
29は含まれていません。
35は含まれていません。
42は含まれていません。
51は含まれていません。
62は含まれていません。
78は含まれていません。
81は含まれていません。
87は含まれていません。
92は含まれていません。
93は含まれていません。

反省

引数で渡しているinputの配列の要素全てを処理結果を出力してしまっていますし、
さらに条件分岐にelseを使っているので、一致しなければ配列内に存在していても「含まれていない」と出てしまいます。

そのため、以下の2点が必要になります。
①elseをなくす
②returnで止める

①each_with_indexは繰り返し処理なので、input配列の値一つ一つを確認してくれます。
そのためelseを置いてしまうとそれぞれの結果も表示させてしまうのでifのみに留めます。

ruby.rb
input.each_with_index do |num, index|

 if num == target_num
      puts "#{index + 1}番目にあります"
 end
end

そしてeach_with_indexさんの配列内の値全ての確認が終了したら、
結果として「含まれていない」を返して欲しいので、条件分岐+繰り返し処理の外にputsを置きます。

ruby.rb
def search(target_num, input)

  input.each_with_index do |num, index|
    if num == target_num
      puts "#{index + 1}番目にあります"
    end
  end
  puts "その数は含まれていません"
end

②目的の数値の存在確認ができても、今のままだと「その数は含まれていません」も表示されてしまいます。
以下のような感じです。

ruby.rb
def search(target_num, input)

  input.each_with_index do |num, index|
    if num == target_num
      puts "#{index + 1}番目にあります"
    end
  end
  puts "その数は含まれていません"
end

input = [3, 5, 9 ,12, 15, 21, 29, 35, 42, 51, 62, 78, 81, 87, 92, 93]
search(12, input)


#結果
4番目にあります
その数は含まれていません

値の存在を確認できたらその時点でメソッドを終了させたいので、「◯番目にあります」と表示させた後にreturnを置きます。





今回は理解不足で時間がかかってしまいましたが、処理の流れをしっかり理解でき収穫は大きかったと思います!
無理やりポジティブにしていますが、理解が遅い点に結構凹んでいます。。
明日も頑張ろう!!

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

herokuでback-ground-imageを表示させたい。

環境

ruby 2.6.5
rails 6.0.0
mysql2 0.3.5

問題

ローカル環境では問題なく表示されていた背景画像が本番環境(heroku)では表示されていなかった。

試したこと

config/environments/production.rb
# Do not fallback to assets pipeline if a precompiled asset is missed.
  config.assets.compile = false

から

config/environments/production.rb
# Do not fallback to assets pipeline if a precompiled asset is missed.
  config.assets.compile = false

に変更
参照記事→https://qiita.com/___xxx_/items/fa15f358beba2b9389da

しかし期待された挙動はうまく行かなかった。

他の記事でも大概この内容で本番環境で挙動がうまく行っている傾向にあるものの、自分はダメだった。

解決策

2015年の少し古い記事を発見→https://www.cotegg.com/blog/?p=189
試してみる。

①cssからscssに変更

②合わせてscssの内容も変更

main.css
.main {
  display: flex;
  background-image: url("../main-bg.png");      /* 画像のURLを指定       */
  background-size:cover;
  background-repeat:  no-repeat;
  background-attachment: fixed;
}

から

main.scss
.main {
  display: flex;
  background-image: image-url("main-bg.png");      /* ここの記述を変更       */
  background-size:cover;
  background-repeat:  no-repeat;
  background-attachment: fixed;
}

に変更
ローカル環境で不具合がないことを確認

③プリコンパイル実施

% bundle exec rake assets:precompile RAILS_ENV=production

ここでコミットして本番環境にpushすることを忘れないこと。

% git push heroku master

本番環境にて挙動を確認。

うまく行った。

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

Ruby処理系自作入門

はじめに

この記事はRuby Advent Calendar 2020 22日目の記事です。遅刻して申し訳ありません。

Rustという言語でrurubyというRuby処理系の独自実装を作っており、今回はその紹介をします。RustでRubyということでrurubyという安直なネーミングにしましたが、発音しずらい上に本家Rubyと紛らわしく恐れ多いので後悔しております。もう少し技術的なポエムを言語実装Advent Calendar 2020に書きましたので、興味のある方はそちらもご参照頂ければ。

Rustについて

Rustは比較的新しいプログラミング言語で、C言語と同様の静的型付け言語です。最新の研究成果を取り入れた柔軟で堅固な型システムと型推論機構を持ち、高速かつメモリ安全・スレッド安全な言語となっています。また、高度な抽象化機能を実行速度を犠牲にしないやり方でうまく取り入れていて、言語処理系のようなシステムプログラミングには最適です。

逆に、所有権システムという独特の仕組みがあってプログラマに一定の制約が課されます。コンパイルが通れば安全ですが、慣れるまではコンパイルを通すまでが大変、と巷では言われています。プログラムの書きやすさは犠牲にして高速性と安全性を追求するという、ある意味Rubyとは正反対の思想のプログラミング言語ですね。(どちらの言語が良いか、という不毛な議論はするつもりはありません。場面に応じて自分が使いやすいものを使えばよいと思います。念のため。)

実装の現状について

基本的には現在のRubyと同様にプログラムを中間表現(バイトコード)にコンパイルして仮想マシンが解釈実行するタイプの処理系です。パーサは独自に古典的な再帰下降パーサを心を込めて手書きで書いています(つらい)。不要になったオブジェクトを回収するガーベジコレクタも、単純なものですが自前で実装しています。
現在の進捗状況ですが、ほとんどの制御構文やリテラルは動きます。組み込みクラスは基本的なものは実装していますが、まだまだクラスもメソッドも足りていないのと、特殊変数など、まだ適当な実装になっている部分が多数あります。

Rubyベンチマーク界のゴールデンスタンダードであるOptcarrotも動きます。実行速度の方も頑張ってはいますが、残念ながらRuby3.0.0-previewの2.7倍、--optという自己書き換えで高速化するオプションをつけた場合は2.1倍遅いです。いろいろなベンチマークの結果はこちら
opt_bench.PNG

今年の夏に話題になった大塚製薬のカロリーメイト・リキッドのプログラマ向けCUIサイトに隠されている人間にはとても読めないひどい超絶技巧を駆使したプログラムであるQuineも動かしました。

Rubyの標準ライブラリもrequireで読めるようになっています。ただCバインディングを使って書かれたものは動きませんし、一部文法がサポートされていないのと、メソッドも足りないので全然ダメです。実はこの原稿を書くにあたっていくつか試しましたが、ほとんど動かなかったのでへこんでいます。一つ一つ動かせるようにしていくことが当面の課題です。

使い方

レポジトリはこちらです。
https://github.com/sisshiki1969/ruruby

❯ git clone https://github.com/sisshiki1969/ruruby.git
❯ cd ruruby

実行環境としてはLinuxを想定しています。Rustが動く環境であればWindowsでもOKなはずですが検証していません。WSL2やdocker上での実行を推奨します。

実行にはまずRustのインストールが必要です。少し手数がかかりますが、基本的にはコマンド一発で終わりますので、Rubyのビルドに比べると飛躍的に簡単です。
https://www.rust-lang.org/ja/learn/get-started

Rustはstable/beta/nightlyという3つのリリースチャンネル(バージョンみたいなものだと思ってください)があり、rurubyの実行にはnightlyチャンネル(Rubyでいうとpreview的なもの)が必要です。
インストールの際にnightlyを選択するか、もしくはとりあえずデフォルトのインストールを行い、その後で下記のようにnightlyをデフォルトのチャンネルとしてください。

❯ rustup install nightly
❯ rustup default nightly

レポジトリのルートでcargo runを実行すると自動的にコンパイルされ、できた実行ファイルが実行されます。--releaseオプションを付けるとコンパイルに時間がかかりますが、最適化され、高速化された実行ファイルが生成・実行されます。

実行ファイルはtarget/debug/ruruby(--release なしの場合)かtarget/release/ruruby(--release ありの場合)に配置されますが、cargo runを使えば適当に実行されますのであまり気にしなくて大丈夫です。
tests/以下にいくつかベンチマーク・プログラム(多くはRubyのリポジトリ内にあるもの)がありますので試してみてください。

まずは定番、フィボナッチ数列の素朴な計算です。

❯ cargo run --release tests/app_fibo.rb
   Compiling ruruby v0.2.0 (/mnt/c/Users/sissh/Documents/GitHub/ruruby)
    Finished release [optimized] target(s) in 30.04s
     Running `target/release/ruruby tests/app_fibo.rb`
9227465

aobenchはシンプルなレイトレーシングです。標準出力に.ppm形式のデータを吐き出しますので、convertコマンド(ImageMagick)などで適当なフォーマットに変換する必要があります。


❯ cargo run --release tests/app_aobench.rb > ao.ppm
    Finished release [optimized] target(s) in 1.15s
     Running `target/release/ruruby tests/app_aobench.rb`![aobench.PNG]
❯ convert ao.ppm ao.jpg

ao.jpg

ファイル名を省略するとREPL(Rubyでいうirb)が起動します。

❯ cargo run
   Compiling ruruby v0.2.0 (/mnt/c/Users/sissh/Documents/GitHub/ruruby)
    Finished dev [unoptimized + debuginfo] target(s) in 1m 24s
     Running `target/debug/ruruby`
irb:0> a = "Ruby"
=> "Ruby"
irb:0> puts "Hello #{a} world!"
Hello Ruby world!
=> nil

エラー処理もエラーの発生場所と対処方法が分かりやすいように頑張ってスタックトレースを表示してくれます。

❯ cargo run --release tests/so_nbody.rb
    Finished release [optimized] target(s) in 1m 26s
     Running `target/release/ruruby tests/so_nbody.rb`
-0.169075164
NoMethodError(no method `distances' for #<Planet:0x7f7509584a00>:Planet)
0:/mnt/c/Users/sissh/Documents/GitHub/ruruby/tests/so_nbody.rb:31
      mag = dt / (distances * distance * distance)
                  ^^^^^^^^^
1:/mnt/c/Users/sissh/Documents/GitHub/ruruby/tests/so_nbody.rb:143
    b.move_from_i(BODIES, nbodies, dt, i + 1)
      ^^^^^^^^^^^
2:/mnt/c/Users/sissh/Documents/GitHub/ruruby/tests/so_nbody.rb:139
n.times do
  ^^^^^

まとめ

「Ruby処理系自作入門」と題していますが「オレの作ったRuby自作処理系への入門」になってしまいました。ご容赦ください。
言語処理系製作、大変楽しく勉強になりますので(Rustの勉強にももちろんなりますし、Rubyの実行モデルの理解も深まりますので一挙両得です)、皆さんもぜひオレオレ言語処理系製作にチャレンジしてみてください。

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

roman numerals

Ubuntu-20.04.1 ruby-2.7.0p0

これからすること

一言にすると,「アラビア数字をローマ数字に変換」.ローマ数字の仕組みは

辺りを参照で.

方針

1000の位,100の位,10の位,1の位に分けて考える.ローマ数字はどの位であっても4と9だけイレギュラーな表記をする(なんでや)から例外として扱う.位の数字が5以上になると先頭に5, 50, 500を表す文字を付ける必要がある.

最初に作ったコード

構造としては,各位ごとの表記を配列に追加していって,後でまとめて出力.

output = []

input = ARGV[0].to_i

if input >= 1000
  th = input / 1000
  output.push("M" * th)
  input = input - th * 1000
end

if input >= 100
  ha = input / 100

  if ha == 4
    output.push("CD")

  elsif ha == 9
    output.push("CM")

  elsif ha > 5
    output.push("D")
    output.push("C"*(ha - 5))

  else
   output.push("C"*ha)

  end

  input = input - ha * 100

end

if input >= 10
  te = input / 10

  if te == 4
    output.push("XL")

  elsif te == 9
    output.push("XC")

  elsif te > 5
    output.push("L")
    output.push("X"*(te - 5))

  else
   output.push("X"*te)

  end

  input = input - te * 10
end

if input == 4
  output.push("IV")

elsif input == 9
  output.push("IX")

elsif input > 5
  output.push("V")
  output.push("I"*(input - 5))

else
 output.push("I"*input)

end

result = ""
for string in output
  result = result + string
end
puts result

使うと,

> ruby roman_numerals.rb 439
CDXXXIX

と正しく出力できた.

再帰メソッド化

先ほどのコードを見ると,文字が違うだけで処理は同じなのが見て取れる.そこで一連の処理をrank = 位ごとに配列に保存する文字列を変更するメソッド化した.保存場所となる配列はmainでもさらに,1000の位,100の位…とmainで呼び出さず,再帰メソッド化することで一度のメソッド呼び出しで全ての処理を行えるようにした.再帰から抜け出す方法は,

  • 入力が0
  • 現在処理しているのが1の位

また,出力もメソッド化することで再帰を抜ける時に出力されるようにした.以下コード

$output = []

def roman(input,rank)
  if input < rank
    return roman(input,rank / 10)
  end

  if rank == 1000
    word = ["M", "", ""]

  elsif rank == 100
    word = ["C", "D", "M"]

  elsif rank == 10
    word = ["X", "L", "C"]

  else
    word = ["I", "V", "X"]

  end

  if input == 0
    print_result($output)
    return
  end

  num = input / rank

  if num == 4
    $output.push(word[0] + word[1])

  elsif num == 9
    $output.push(word[0] + word[2])

  elsif num > 5
    $output.push(word[1])
    $output.push(word[0] * (num - 5))

  else
   $output.push(word[0] * num)

  end

  if rank != 1
    input = input - num * rank
    return roman(input,rank / 10)
  end

  print_result($output)
  return

end

def print_result(output)
  result = ""

  for string in output
    result = result + string
  end

  puts result
end

input = ARGV[0].to_i
roman(input,1000)

使ってみると,

> ruby roman_numerals.rb 439
CDXXXIX

と正しく出力できた.まだリファクタリングできそうな気はするが,とりあえずこの辺で


  • source ~/grad_members_20f/members/musutafakemaru/ex1.org
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

roman numerals

Ubuntu-20.04.1 ruby-2.7.0p0

これからすること

一言にすると,「アラビア数字をローマ数字に変換」.ローマ数字の仕組みは

辺りを参照で.

方針

1000の位,100の位,10の位,1の位に分けて考える.ローマ数字はどの位であっても4と9だけイレギュラーな表記をする(なんでや)から例外として扱う.位の数字が5以上になると先頭に5, 50, 500を表す文字を付ける必要がある.

最初に作ったコード

構造としては,各位ごとの表記を配列に追加していって,後でまとめて出力.

output = []

input = ARGV[0].to_i

if input >= 1000
  th = input / 1000
  output.push("M" * th)
  input = input - th * 1000
end

if input >= 100
  ha = input / 100

  if ha == 4
    output.push("CD")

  elsif ha == 9
    output.push("CM")

  elsif ha > 5
    output.push("D")
    output.push("C"*(ha - 5))

  else
   output.push("C"*ha)

  end

  input = input - ha * 100

end

if input >= 10
  te = input / 10

  if te == 4
    output.push("XL")

  elsif te == 9
    output.push("XC")

  elsif te > 5
    output.push("L")
    output.push("X"*(te - 5))

  else
   output.push("X"*te)

  end

  input = input - te * 10
end

if input == 4
  output.push("IV")

elsif input == 9
  output.push("IX")

elsif input > 5
  output.push("V")
  output.push("I"*(input - 5))

else
 output.push("I"*input)

end

result = ""
for string in output
  result = result + string
end
puts result

使うと,

> ruby roman_numerals.rb 439
CDXXXIX

と正しく出力できた.

再帰関数化して短く

先ほどのコードを見ると,文字が違うだけで処理は同じなのが見て取れる.そこで一連の処理をrank = 位ごとに配列に保存する文字列を変更する関数化した.さらに,1000の位,100の位…とmainで呼び出さず,再帰関数化することで一度の関数呼び出しで全ての処理を行えるようにした.以下コード

$output = []

def roman(input,rank)
  if input < rank
    return roman(input,rank / 10)
  end

  if rank == 1000
    one = "M"
    five = ""
    ten = ""

  elsif rank == 100
    one = "C"
    five = "D"
    ten = "M"

  elsif rank == 10
    one = "X"
    five = "L"
    ten = "C"

  else
    one = "I"
    five = "V"
    ten = "X"
  end

  num = input / rank

  if num == 4
    $output.push(one + five)

  elsif num == 9
    $output.push(one + ten)

  elsif num > 5
    $output.push(five)
    $output.push(one * (num - 5))

  else
   $output.push(one * num)

  end

  if input > 10
    input = input - num * rank
    return roman(input,rank / 10)
  end

  return

end

input = ARGV[0].to_i
roman(input,1000)

result = ""

for string in $output
  result = result + string
end

puts result

使ってみると,

> ruby roman_numerals.rb 439
CDXXXIX

と正しく出力できた.まだリファクタリングできそうな気はするが,とりあえずこの辺で


  • source ~/grad_members_20f/members/musutafakemaru/ex1.org
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Webの仕組みとHTTPについて

ブラウザとサーバー

ブラウザは今表示されてる画面
サーバーは色んなデータを保存してる大きなPCのようなもの
サーバーからファイルをダウンロードすることでブラウザに表示される
ファイルの中にはHTML、CSS、JSなどの情報が入ってる

図で表すとこんな感じ
      
(ブラウザ) ←←←←←← (サーバー)
         ファイル
       (HTML,CSS,JS)

HTTP

HTTPとはブラウザとサーバーのやりとりの方法
基本はリクエストとレスポンス

      リクエスト
      →→→→→→
(ブラウザ)      (サーバー)
      ←←←←←←
      レスポンス

リクエストで欲しい情報をサーバーに投げてサーバーがその情報を返してくれる
このリクエストとレスポンスのやりとりのルールを作るのがサーバーサイド言語

HTTPの基本メソッド4つ

1.GET データの取得
Webページを表示するときに使用

     このページ見たい
      →→→→→→
(ブラウザ)       (サーバー)
      ←←←←←←
      ページの情報

2.POST データの送信
フォームなどでデータを保存するに使用

      データ保存して
      →→→→→→
(ブラウザ)      (サーバー)
      ←←←←←←
      保存したよ

3.PUT データの更新
既存のデータを書き換えるときに使用

     データ変更して
      →→→→→→
(ブラウザ)      (サーバー)
      ←←←←←←
      変更したよ

4.DELETE データの削除
既存データを消すときに使用

      データ消して
      →→→→→→
(ブラウザ)      (サーバー)
      ←←←←←←
       消したよ

Twitterで例えると
ツイートを表示 GET
ツイートの新規投稿 POST
ツイートの更新 PUT (Twitterはできなかったはず)
ツイート削除 DELETE

IPアドレスとドメイン

IPアドレスとはサーバー内でデータに割り振られている住所みたいなもので数字で管理されている
ドメインとは「~~.com」の部分
IPアドレスだと分かりにくいのでDNSというシステムがIPアドレスとドメインを紐付けている

最後にWebサイト(Qiita)が表示されるまで

    qiita.comのIPアドレス教えて
      →→→→→→
(ブラウザ)      (DNS)
      ←←←←←←
     IPアドレスを返す

  DNSから取得したIPアドレスにGETリクエスト
      →→→→→→
(ブラウザ)      (サーバー)
      ←←←←←←
    HTML、CSS、JSを返す

こんな感じです

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

Windows VSCode環境で"Debugger terminal error: Process failed: spawn bundle ENOENT"

お困りごと

Windows VSCodeでRubyをデバッグしたい。
まずは、bundlerを介した環境でデバッグしたいため、launch.jsonのuseBundlertrue で設定することからスタート

launch.json
{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Debug Local File",
            "type": "Ruby",
            "request": "launch",
            "program": "${workspaceRoot}/main.rb",
            "useBundler": true
        }
    ]
}

いざ、デバッグを開始するとVSCodeのDEBUG CONSOLEに以下のエラーが表示される。

Debugger terminal error: Process failed: spawn bundle ENOENT

swpawnは日本語で"生成"、 "ENOENT"はError No Etry"の略らしい

意訳すると、「bundleプロセス生成しようとおもったけど、見つかんない」といったところ。

解決策

bundleが呼び出せるようにする記述をlaunch.jsonに記述する。
pathToBundlerの部分がその記述。

launch.json
{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Debug Local File",
            "type": "Ruby",
            "request": "launch",
            "program": "${workspaceRoot}/main.rb",
            "useBundler": true,
            "pathToBundler": "bundle.bat"
        }
    ]
}

以上

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

【Ruby on Rails】タグ付機能/タグ絞り込み機能の実装

はじめに

Railsでgem acts-as-taggable-onを用いて、タグ機能を実装しました。
仕様としては、よくあるパターンでインスタンス作成画面でユーザーが任意のタグを入力して保存するというものです。

実装イメージ

イメージのように
①new画面でインスタンスを作成

②confirm画面で入力内容を確認

③createアクション実行で一覧画面にリダイレクト

④タグをクリックすると、そのタグに紐づくインスタンスが一覧表示される

それでは実装に入ります!

前提(開発環境)

  • macOS Catalina
  • Ruby 2.6.5
  • Ruby on Rails 6.0.0
  • Visual Studio Code
  • データベース: Mysql
  • テンプレートエンジン: haml

目次

1.事前準備
2.ModelとController
3.View

1. 事前準備

今回はacts-as-taggable-onというGemを使うのですが、インストールの途中でエラーが発生してしまうので、そのエラーがでないように実装していきましょう。順番に記載します。

Gemfile
gem 'acts-as-taggable-on', '~> 6.0' #追加
ターミナル
% bundle install
ターミナル
% rails acts_as_taggable_on_engine:install:migrations

rails acts_as_taggable_on_engine:install:migrationsを実行すると必要なマイグレーションファイルが作成されます。通常はここでrails db:migrateを実行してデータベースに反映させるのですが、Mysqlを使用している場合、ここで必要な処理を行わないとエラーが起きてしまいます。

もしも、migrateさせてしまった場合はrails db:rollbackでファイルを戻しましょう。

acts-as-taggable-on公式リファレンスの通りターミナルでコマンドを実行します。
スクリーンショット 2020-12-23 10.49.07.png

ターミナル
% rails acts_as_taggable_on_engine:tag_names:collate_bin

次に先程生成されたマイグレーションファイルを編集します。(ファイル名に気をつけてください)

db/migrate/・・・・_add_missing_unique_indices.acts_as_taggable_on_engine.rb
# This migration comes from acts_as_taggable_on_engine (originally 2)
if ActiveRecord.gem_version >= Gem::Version.new('5.0')
  class AddMissingUniqueIndices < ActiveRecord::Migration[4.2]; end
else
  class AddMissingUniqueIndices < ActiveRecord::Migration; end
end
AddMissingUniqueIndices.class_eval do
  def self.up
    add_index ActsAsTaggableOn.tags_table, :name, unique: true

    # remove_index ActsAsTaggableOn.taggings_table, :tag_id if index_exists? (ActsAsTaggableOn.taggings_table, :tag_id) #コメントアウトする
    if index_exists?(ActsAsTaggableOn.taggings_table, :tag_id) #追加
      remove_foreign_key :taggings, :tags                      #追加
      remove_index ActsAsTaggableOn.taggings_table, :tag_id    #追加
    end
    remove_index ActsAsTaggableOn.taggings_table, name: 'taggings_taggable_context_idx'
    add_index ActsAsTaggableOn.taggings_table,
              [:tag_id, :taggable_id, :taggable_type, :context, :tagger_id, :tagger_type],
              unique: true, name: 'taggings_idx'
  end

  def self.down
    remove_index ActsAsTaggableOn.tags_table, :name

    remove_index ActsAsTaggableOn.taggings_table, name: 'taggings_idx'

    add_index ActsAsTaggableOn.taggings_table, :tag_id unless index_exists?(ActsAsTaggableOn.taggings_table, :tag_id)
    add_index ActsAsTaggableOn.taggings_table, [:taggable_id, :taggable_type, :context], name: 'taggings_taggable_context_idx'
  end
end

①11行目をコメントアウト

db/migrate/・・・・_add_missing_unique_indices.acts_as_taggable_on_engine.rb
# remove_index ActsAsTaggableOn.taggings_table, :tag_id if index_exists?(ActsAsTaggableOn.taggings_table, :tag_id)

②12行目から以下の記述を追加

db/migrate/・・・・_add_missing_unique_indices.acts_as_taggable_on_engine.rb
if index_exists?(ActsAsTaggableOn.taggings_table, :tag_id) #追加
  remove_foreign_key :taggings, :tags                      #追加
  remove_index ActsAsTaggableOn.taggings_table, :tag_id    #追加
end

これで処理はバッチリです!

ターミナル
% rails db:migrate

次に一覧画面に表示しておくタグをseed.rbに入れておきます。

db/seed.rb
# タグ付紐付け(%w()の中に記述)
array = %w(ダンス  撮影モデル サロンカットモデル 演技 マジック アイドリング エキストラ 楽器演奏 リモート可能 東京 神奈川 埼玉 千葉 群馬 茨城 兵庫 北海道 大阪 京都 愛知 福岡 5000~ 10000~ 15000~ 20000~ 25000~ 30000~ 積極募集 Youtube広告モデル)
array.each{ |tag|
  tag_list = ActsAsTaggableOn::Tag.new
  tag_list.name = tag
  tag_list.save
}
ターミナル
% rails db:seed

rails db:seedがうまくいくとMysqlにデータが保存されると思います。
スクリーンショット 2020-12-23 11.17.07.png

ここまでで事前準備は整いました。

2. ModelとController

タグを付与したいモデルに以下の記述を追加します。

app/models/event.rb
class Event < ApplicationRecord
    acts_as_taggable  #追加
end
app/controllers/events_controller.rb
class EventsController < ApplicationController

  def index
  #一覧画面にタグを表示
    @tags1 = ActsAsTaggableOn::Tag.where("id < ?", 10)
    @tags2 = ActsAsTaggableOn::Tag.where(id: 11..22)
    @tags3 = ActsAsTaggableOn::Tag.where(id: 23...29)
    @tags4 = ActsAsTaggableOn::Tag.where(id: 29...31)
  # タグ検索時にそのタグずけしているものを表示
    if params[:tag_name]
      @events = Event.tagged_with("#{params[:tag_name]}").includes(:recruiter)
    else
      @events = Event.all.includes(:recruiter)
    end
  end

  def new
    @event = Event.new
  end

  def confirm
    @event = Event.new(event_params)
    if @event.invalid?
      render :new
    end
  end

  def create
    @event = Event.new(event_params)
    @event.recruiter_id = current_user.id
    render :new and return if params[:back] || !@event.save
    if @event.save
      redirect_to root_path
    else
      render :new
    end
  end

  def show
    @event = Event.find_by(id: params[:id])
  end

  private

  def event_params
    params.require(:event).permit(:event_name, :datetime, :prefecture, :place, :detail, :tag_list)
  #ストロングパラメーターに :tag_listを追加
  end

  def set_user
    @user = current_user
  end

end

解説は後にまとめて行います。先にViewパートに移ります。

3.View

インスタンスにタグ付けを行う

views/events.new.html.haml
 = form_with(model: @event, url: confirm_events_path(@event), local: true, id: "new-event") do |f|
     .
     .

   .create-item-2
     = f.label :tag_list, "タグ付け"
     = f.text_field :tag_list, value: @event.tag_list.join(','), placeholder: "ダンス,東京,・・・"
     .
     .

   .create-btn-field
     = f.submit "確認画面へ"
views/events.new.html.haml
.create-item-2
     = f.label :tag_list, "タグ付け"
     = f.text_field :tag_list, value: @event.tag_list.join(','), placeholder: "ダンス,東京,・・・"

上記の部分をform_withにネストさせることで@eventに対してタグを付与させますよ」という意味になります。

url: confirm_events_path(@event)

となっていますが、確認画面を挟まない場合は= form_with(model: @event, local: true, id: "new-event") do |f|で大丈夫です。
コントローラーの部分もconfirmアクションは必要ありません。
確認画面の実装はこちらを御覧ください

@event.tag_list.join(',')

カンマで区切ることによって、入力されたタグが配列の形でデータが保存されます。

一覧画面にタグを表示させる

views/events.html.haml
  %p.tag-word
    ジャンルで検索
   - @tags1.each do |genre|
     = link_to "#{genre.name}(#{genre.taggings_count})", events_path(tag_name: genre.name), class: "tag-link"

コントローラーの部分で@tag1はこのように定義されています

app/controllers/events_controller.rb
def index
  #一覧画面にタグを表示
    @tags1 = ActsAsTaggableOn::Tag.where("id < ?", 10)

意味としては「seed.rbで事前に作成したタグの中からidが10番未満のタグを@tags1に代入」となります。
viewでは@tags1eachメソッドで一覧表示しています。
残りの@tags2@tags3@tags4も同じ手順で一覧画面に表示しています。

(#{genre.taggings_count})

.taggings_countメソッドはタグに紐付いているインスタンスの数をviewに表示してくれます。

events_path(tag_name: genre.name)

タグをクリックすると、そのタグが紐付いているインスタンスを一覧表示します。

app/controllers/events_controller.rb
def index
  .
  .
  if params[:tag_name]
    @events = Event.tagged_with("#{params[:tag_name]}").includes(:recruiter)
  else
    @events = Event.all.includes(:recruiter)
  end
end

Event.tagged_with("#{params[:tag_name]}")とすることでタグが紐付いているインスタンスの情報を全て取得してくれます。

実装は以上です!

おわりに

最後まで読んでいただきありがとうございました!
お疲れ様でした。。

参考文献

Rails | acts-as-taggable-on を使ったタグ機能の実装 | 備忘録
Railsにタグ機能をつける。acts-as-taggable-on使用

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

Railsチュートリアル/インデックスをつけることの意義

はじめに

こんにちは.初投稿です.自分の勉強のメモ用です.初心者なので,間違っているところなどあると思います.コメントでご指摘いただけるとありがたいです.

疑問点

rails tutorial でたびたび出てきていたテーブルにインデックスを振る,という操作が一体何のために行われているか?が疑問でした.自分の疑問点としては,「インデックスとは」でググると,「データ検索を行う際に無駄が多いため,索引としてインデックスを追加する」という趣旨の文がたくさん出てきたのですが,これがよくわかりませんでした.インデックス?インデックス番号を使ったのではだめなのか?と感じていました.具体的には以下のようなコードがある場合です.

Railstutorial 第4版 (Rails5.1対応) リスト14.1

db/migrate/[timestamp]_create_relationships.rb
class CreateRelationships < ActiveRecord::Migration[5.1]
  def change
    create_table :relationships do |t|
      t.integer :follower_id
      t.integer :followed_id

      t.timestamps
    end
    add_index :relationships, :follower_id
    add_index :relationships, :followed_id
    add_index :relationships, [:follower_id, :followed_id], unique: true
  end
end

インデックスを追加するとは

インデックスの追加とは,もともとあるテーブルから,idと特定のカラムを抜き出すことを指すようです.僕は初め,次のコード

add_index :relationships, :follower_id

は,follower_idカラムにインデックス番号を順に1,2,3と加えていくことだと思っていました.ゆえに「インデックス番号でその役割よくね?」と考えていました.しかし,上記のコードが意味するところはそうではなく,「relationshipsテーブルからfollower_idカラムとidカラムのみを抜き出したデータを作成して保存する」という行為であるようです.

メリット


つまり,インデックスがない状態ではfollower_idで検索しようとした際にfollower_idの値をすべてのテーブルのfollower_idカラムから一つずつ検索せねばならず,またfollower_idの値が欲しいだけであるのにrelationshipsテーブルの他のカラムもすべて読み込むことになる,ということです.インデックスを作成しておくことで,欲しいカラムのみを用いて検索を行えるため,検索スピードが向上します.

デメリット


ただし,これはいいことばかりではなく,1データあたりに処理しなければならない動作が増えるので,データ追加時の処理が重くなる,というデメリットがあるようなので,適用したいモデルに合っているかをよく考えて実装する必要がありそうです.

参考文献


dbonline インデックスの意味とメリットデメリット|SQLite入門
https://www.dbonline.jp/sqlite/index/index1.html
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Homebrew + rbenv + ruby-build の関係性(後編)実際にrubyとRailsの導入もできます!

前回の記事ではbrewでインストールしたプログラムが/usr/local/Cellerディレクトリなどに入りシンボリンクを作成することでパスが通ること、各ディレクトリ構成について説明しました。今回は実際にrbenvruby-buildをインストールして、それぞれのコマンドで何をしているのかを説明していきます。

rbenv, ruby-buildのインストール

まずはbrewを最新の状態にしておきます。

% brew update

次にrbenvruby-buildのインストールです。ruby-buildはrbenvのプラグインの1つです。rbenv installというRubyのバージョンをインストールするコマンドを提供しています。rbenvをインストールする際に、セットでインストールします。

% brew install rbenv ruby-build

RBENV_ROOT とは

次のコマンドの説明に必要なのでこの用語の説明をします。RBENV_ROOT というのは rbenv の shims (後述) と versions (Ruby のインストール先) がある場所(パス)を指し示すための環境変数です。 デフォルトではホームディレクトリに直下になっており、以下のように確認できます。

% rbenv root
/Users/shuntagami/.rbenv

rbenv initの実体

話を戻します。rbenv のインストール手順によると以下のコマンドを実行するようにあります。

% echo 'eval "$(rbenv init -)"' >> ~/.zshrc  #(`~/.zshrc`の部分は使っているシェルに応じて変えてください。)

これは何を行うのでしょうか? 答えは書いてあるとおり、shimsautocompletion を有効化します。shimsには何が入っているのか確認してみましょう。

% cd .rbenv
% ls
shims    version  versions

% cd shims
% ls
bundle bundler erb gem irb rake rdoc ri ruby testrb,,,

bundleやらrakeやらrailsを使ったことのある人ならおなじみのコマンドが入っています。つまり、shimsを有効化することにより、$RBENV_ROOT/shimsPATH に入れ,これらのコマンドがどこからでも使えるようになるということです。最後にシェルの設定ファイルの変更を以下のコマンドで読み込みましょう。

source ~/.zshrc  #(`~/.zshrc`の部分は使っているシェルに応じて変えてください。)

rbenvでrubyをインストールする

ここまででrbenvのインストールが完了したのでrubyをインストールしていきます。インストール可能なコマンドは以下のように確認しましょう。

% rbenv install --list

特に理由がなければ最新のバージョンをインストールしましょう。

% rbenv install 2.7.2

インストールしたバージョンを使うために以下のコマンドを実行します。

% rbenv global 2.7.2

最後にrbenvを読み込み、変更を反映させます。新しいバージョンの Ruby を入れたときや、実行ファイルを提供する gem を入れたあとには実行するようにしましょう。

% rbenv rehash

Railsを用意する

rubyの導入が終わり、せっかくなのでRailsの導入手順も説明していきます。

bundlerのインストール

bundlerとは、gemのバージョンやgemの依存関係を管理してくれるgemです。bundlerを使うことで、複数人での開発やgemのバージョンが上がってもエラーを起こさずに開発できます。

% gem install bundler

bundler自体もgemなのでインストールが完了すると1 gem installedと表示されるはずです。

Railsのインストール

% gem install rails --version='6.0.0'

バージョンを指定してインストールします。完了したら先ほど説明したrbenv rehashコマンドを実行しましょう。

参考

rbenv + ruby-build はどうやって動いているのか
https://takatoshiono.hatenablog.com/entry/2015/01/09/012040

rbenv公式
https://github.com/rbenv/rbenv#basic-github-checkout

まとめ

rubyとRailsのインストール手順とそれぞれのコマンドの意味を見ていきました。あとはデータベース,yarn,Node.jsをインストールすればRailsを動かすことができるようになります

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

Railsの賞味期限を延ばす方法をまとめてみる

はじめに

年末になりアドベントカレンダーが盛り上がってますね。Railsのエントリーも賑わっています。
以下のエントリーに触発されて、大規模な開発になっても少しでもうまく開発が進む方法まとめてみようと思いました。
https://okuramasafumi.hatenablog.jp/entry/2020/12/16/224401
https://blog.unasuke.com/2020/i-have-to-learn-those-things-in-the-future/

考え方

この記事では、DDDやアーキテクチャについて議論は取り扱わず、具体的なRubyのコーディング方法を中心にまとめていきます。
まず、原則的になぜRailsが大規模に向かないのか?開発が進むにつれしんどくなっていく理由について考えてみます。

大規模開発に向かない理由:
- クラスやオブジェクトの参照箇所が正確に把握できない。このため、リファクタリングはもちろん機能追加などがやり辛く、開発のスピード・品質が悪くなっていく。

こんな経験ありませんか?
- Gemのソースコードを追っていたけど、メソッドの定義がどこかわからなくなってしまう
- ネストが深すぎて処理内容がよくわからなくなる

Rubyのようなスクリプト型言語とコンパイル型言語の大きな違いの1つに動的な関数呼び出しがあり、これが規模が大きくなるにつれ開発を難しくする要因の大きない理由になっています。これを解決する手段として、DDDなどの手法を採用することも多いと思います。

参照箇所がなるべく明示的になるコーディングをする

参照箇所が明示的にわかるようにするには、大まかにいうと "メタプロを避ける" ということが言えます。
具体的には、
- 動的なメソッド定義を避ける=
- #respond_to? を使って処理を変えるコードは避ける
- #send を使わない
- 静的な処理を動的に処理しない
(例: viewでActiveRecordのattributeを.eachを使って表示を作る )

という感じです。

メタプロではありませんが、Railsの標準機能でも避けた方が良い機能もあります。

  • Concernなどのモジュールのinclude/mixinは避ける 別クラスに移譲するべき
  • ActiveRecordのbefore_*/after_*を避ける サービスパターン、コマンドパターンなどで別クラスからメソッドをコールする

コーディングルールを定める

より実践的な運用を考えると、ここまで紹介してきたようなポイントを、コーディングルールとして定めてチームで共有しておくと負債の増加が防げて望ましい状況ができます。
また、これらのことが守られているかはレビューでチェックする必要が出てきますが、レビュアーの負担が増えるため、 querly などのツールで検出することが理想的です。
これがあれば新規メンバー加入時も、チームの負担を少なく品質を守り、また新規メンバーもなるべく自力で馴染んでいくことができます。

雑感

まとめてみる ということで雑記帳のようになりましたが、そのうち統合的に再編集したい気持ちもあります。
ぜひ、よくわからないこと(避けるべき理由や対処法)や他にも気をつけた方が良いポイントなどコメントもらえたら嬉しいです

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

Payjp v2 ざっくり実装

最近Payjpのv2なるものがリリースされたらしく、Payjp側もセキュリティの向上のためv2の使用を推奨しているらしいです。

少し前に質問があり、気になったので実装してみました。忘れないようにメモとして記事作りました。
細かい説明は省きます。リファレンス見れば誰でも簡単に実装できます。

v1との違いは?

v1ではトークン化APIのみを提供しており、カード情報入力フォームは加盟店側で用意する必要がありました。

しかしより安全なクレジットカード商取引のために、最新のPCI-DSSでは、カード情報入力フォームを決済代行業者側で用意することが求められています。
で、
payjp.js v2では、これらのフォームを弊社ドメインのiframe内で用意し、かつ独自スタイルの適用・イベント監視などv1で実現できた機能を提供いたします。 加えて、カード番号入力時の自動フォーマットやレスポンシブデザインなど、ニーズの高かった機能をデフォルトで提供致します。

との事です。Payjp側で用意された入力フォームを使えるので、わざわざ作らなくて良い感じらしいですね。
ところでPCI-DSSってセキュリティの基準か何かの基準??
金融業界出身の方いたら、教えて欲しいです。

ざっくりと実装

- application.html.hamlにv2のcdnを記載
%head
  %script{src: "https://js.pay.jp/v2/pay.js", type: "text/javascript"}
  .....

payjp.jsコード

$(function(){
  if(document.location.pathname !== "/cards/new") return false;
  // カード登録ページじゃい時は処理を実行しない
  const payjp = Payjp('pk_test_7370ce03239ee60f10ca694c') //公開鍵を読み込む。
  // Payjp.setPublicKey('pk_...................')としなくて良いらしい
  const elements = payjp.elements();
  // payjpのインスタンス生成
  const cardElement = elements.create('card', {style: {base: {color: 'black'}}})
  // ここでformを生成してる。createの第一引数には、「card」「cardNumber」「cardExpiry」「cardCvc」とかのタイプを選んで作れる。
  // cardだとカード番号、有効期限、cvcの3つをまとめて横並びにしたフォームを生成する
  // フォームを分けたい人は「cardNumber」「cardExpiry」「cardCvc」を引数にして作ると良いです。
  cardElement.mount('#card-element');
  // 任意のセレクタ(#card-element)に対してiframe(入力フォームを付与する)
  const submit_btn = $("#info_submit")
  // いつものリファクタ


  submit_btn.click(function(e) {
    e.preventDefault();
    // submitしないように止める
    payjp.createToken(cardElement).then(function(response) { 
      if (response.error) {
        // Payjp側からの返ってくるオブジェクトがerrorオブジェクトを持ってた場合
     // = 通信に失敗したとき
        alert(response.error.message)
        // どの情報に対して不備があるのか教えてくれる。
     // 下記に記載しているが該当箇所のエラーを知らせてくれる
        regist_card.prop('disabled', false);
        // いつものやつ
        return ;
      }
      else {
        alert("カード登録が完了しました")
        $("#card_token").append(
            `<input type="hidden" name="payjp_token" value=${response.id}>
             <input type="hidden" name="card_token" value=${response.card.id}>`
            //  これもいつものやつ
         );
      cardElement.clear()
    // 入力情報を消す
      $('#card_form')[0].submit()
      }

    })
  });

});

ビュー

.mypage.horizontal-padding-15
  = render 'shared/side_bar'
  .mypage-main
    .block.horizontal-padding-25
      .block__menu
        支払い方法
    .block.horizontal-padding-25
      #card-element
    -# ↑これ追加しただけ。この#card-elemntがマウントされるセレクタ(名前は何でも良い)
      #card-icons
        = image_tag 'icon_visa.png'
        = image_tag 'icon_master.jpg'
        = image_tag 'icon_saison.png'
        = image_tag 'icon_jcb.png'
        = image_tag 'icon_american.svg'
        = image_tag 'icon_diners.png'
        = image_tag 'icon_discover.png'
      = form_with model: @card, id: "card_form" do |f|
        #card_token
        = f.submit "次へ進む", class:"button back-red font-white", id: "info_submit"

実際のカード情報入力ページ

スクリーンショット 2020-07-10 11.11.03.png
スクリーンショット 2020-07-10 11.14.49.png

入力に不備があると該当箇所に対してアラートもしっかりと出る(V1もできる)

スクリーンショット 2020-07-10 11.17.32.png

使ってみた所感

好きな方を使いましょう!

あんまり変わらないです!

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