20201114のRubyに関する記事は24件です。

安全な数字(ruby編)

[問題]安全な数字(ruby編)

問題

4 桁のパスワードを考える上で法則性のある数字を避けようと考えています。
4 桁の数字で構成されたパスワードの文字列 s が入力されるので同じ数字が 2 つ以上存在すれば「NG」、そうでない場合は「OK」と出力してください。

入力される値

入力は以下のフォーマットで与えられます。

s

・1 行目に 4 桁の数字で構成されたパスワードの文字列 s が与えられます。
・入力は合計で 1 行となり、入力値最終行の末尾に改行が 1 つ入ります。

期待する出力

4 桁の数字で構成されたパスワードの文字列 s が入力されるので同じ数字が 2 つ以上存在すれば「NG」、そうでない場合は「OK」と出力してください。

入力例1

2020

出力例1

NG

入力例2

1234

出力例2

OK

私の答え

a = gets.chomp.chars
if (a.count - a.uniq.count) > 0
    puts "NG"
else
    puts "OK"
end

1行目のcharsメソッドで例1で例えると["2" "0" "2" "0"]のように1文字づつ分割している

2行目でa.countの返り値4からa.uniq.countの返り値2を引いた数が0より大きければ"NG"を出力するというもの。

uniqメソッド

配列の要素の中で重複している要素を削除して削除後の配列として返すメソッド

countメソッド

  • 文字列の特定の文字の出現回数を数えるための機能(「こんばんは」という文字に「ん」は2回みたいに数える機能)
  • 配列の要素の数を数えるための機能

以上!

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

Ralisのポートフォリオでcollection_check_boxesメソッドを使ったので復習

はじめに

user.rb

 has_many :habits, dependent: :destroy

habit.rb

belongs_to :user

userモデルがhabitモデルを複数持っている関係性
habitモデルは、taskというstring型のカラムと、completeというinteger型のカラムを持っている

やりたい事

チェックしたhabitモデルのみ、completeカラムに+1したい
これを1つのフォームで複数一気に出来るのがcollection_check_boxes
書き方はこんな感じ

<%= form_with(model: @user,url: complete_user_path, local: true) do |f|%>
<h4><%= @user.name %></h4>
<p class="pt-3">習慣</p>
<div class="complete-content">
<%= f.collection_check_boxes :habit_ids, @user.habits,:id,:task,checked: false do |b| %>
<%= b.label do %>
<%= b.check_box %>
<%= b.text %>
<% end %>
<% end %>
</div>
<%= f.submit "送信",class: "btn btn-primary m-5"%>
<% end %>

下記のようなHTMLが生成される

<input type="hidden" name="user[habit_ids][]" value="">

<label for="user_habit_ids_11">
<input type="checkbox" value="11" name="user[habit_ids][]" id="user_habit_ids_11">
ランニング
</label>

<label for="user_habit_ids_12">
<input type="checkbox" value="12" name="user[habit_ids][]" id="user_habit_ids_12">
プログラミング
</label>

<%= f.collection_check_boxes :habit_ids, @user.habits,:id,:task,checked: false do |b| %>

分解していくと

model: @userのhabit_idsとしているので
name=user[habit_ids][] となる この配列に格納していく

@user.habits @userの持つhabitモデルの数だけチェックボックスを生成してくれる

:id name=user[habit_ids][]に格納する値 この場合habit.id

:task lavel この場合はランニング、プログラミング

checkd:false  何故かデフォルトでチェック付いていたのでfalseに

controllerで受け取る時は

def complete_params
  params.require(:user).permit(habit_ids: [])
end
29: def complete
    30: @user = User.find_by(id: params[:id])
    31: before_level = @user.level
    32: habit_id = (complete_params)
 => 33: binding.pry


[1] pry(#<UsersController>)> habit_id
=> <ActionController::Parameters {"habit_ids"=>["", "11", "12"]} permitted: true>

""が1つ多いのは多分habitモデルを1つ消してるから

ここからidだけを取り出したい。
完成形

29: def complete
    30: @user = User.find_by(id: params[:id])
    31: before_level = @user.level
    32: habit_id = (complete_params).values.flatten.compact.reject(&:empty?)
 => 33: binding.pry
    34: unless habit_id.empty?
    35: @habit = Habit.find(habit_id)
    36: @habit.each do |h|
    37:   h.complete += 1
    38:   flash[:notice] = "達成!"
    39:   h.save!
    40:   @user.level += 1
    41:   @user.save!
    42: end
    43: end
    44: @user.level_change(before_level)
    45: redirect_to @user
    46: end

[1] pry(#<UsersController>)> habit_id
=> ["11", "12"]

一応うまくいったけどもっといいやり方あるんだろうな

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

Rails 6でjQueryとBootstrapを使えるようにする(Rails 6)

Ruby on Railsでアプリケーションを作る際に,jQueryとBootstrapを使えるようにしたかったので手順をメモ。(Webpackerを使用。)

1.yarnでjQuery、Bootstrap、popper.js(Bootstrapが使う)を導入。

アプリケーションのディレクトリ配下で以下を実行。

yarn add jquery bootstrap popper.js

2.webpackの設定
app/config/webpackのenvironment.jsの記述を追加する。
(これによりimportやrequireなしで$やBootstrapのJavascriptが使えるようにする。)

environment.js
const { environment } = require('@rails/webpacker');

//ここから
const webpack = require('webpack');

environment.plugins.prepend(
  'Provide',
  new webpack.ProvidePlugin({
    $: 'jquery',
    jQuery: 'jquery',
    Popper: 'popper.js'
  })
);
//ここまで追加。

module.exports = environment

3.BootstrapのJS,CSSをインポートする
app/javascript/packsのapplication.jsでBootstrapのJSをimport。
app/javascript/stylesheetsにapplication.scssを作成し、BootstrapのCSSをimport。

application.js
import 'bootstrap';
import '../stylesheets/application';

※Webpackerはビルドの際にapp/javascript/packsの中身だけを参照するため、
application.scssがビルドされるようにapplication.jsでインポートしておく。

application.scss
@import '~bootstrap/scss/bootstrap';

4.アプリケーション側からWebpackerがビルドしたJS,CSSを読み込ませる
app/views/layouts/application.html.erbのhead内に以下2文を追記。

application.html.erb
<%= stylesheet_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>

これで完了。

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

jQueryでタブをマウスオーバー した際に表示画面を切り替える

はじめに

今回はjQueryを使って、tabをマウスオーバーした際にページリロードを行わずに表示を切り替える方法を記述していきます。

完成イメージ

mypageTabChange.gif

環境

MacOS 10.15.7
ruby 2.6.5
Ruby on Rails 6.0.0
jquery 3.4.1

前提条件

  • jQueryが導入済みであること。

それでは作業していきます!

①show.html.erbとcssを作成する。

まずはhtmlから作成します。今回はshow.html.erbというファイルに記述します。

show.html.erb
<div class="container">
  <%# tab部分 %>
  <ul class='user-nav-bar'>
    <li>
      <a href="#" id="top", class='user-nav active'>
        TOP
      </a>
    </li>
    <li>
      <a href="#" id="post", class='user-nav'>
        POST
      </a>
    </li>
    <li>
      <a href="#" id="favorite", class='user-nav'>
        Bookmark
      </a>
    </li>
    <li>
      <a href="#" id="post", class='user-nav'>
        message
      </a>
    </li>
  </ul>
  <%# 表示部分 %>
  <ul class="pages">
    <li class="page show">
      <div class="change-page">
        トップページ
    </div>
    </li>
    <%# POSTページ %>
    <li class="page">
      <div class="change-page">
        ポストページ
      </div>
    </li>
    <%# Bookmarkページ %>
    <li class="page">
      <div class="change-page">
        ブックマークページ
      </div>
    </li>
    <%# Messageページ %>
    <li class="page">
      <div class="change-page">
        メッセージページ
      </div>
    </li>
  </ul>
</div>

続いてSCSS

show.scss
// TOP・POST・Bookmark・Messageのtabセレクター==========================
.user-nav-bar {
  display: flex;
  justify-content: right;
  width: 20vw;
  margin: 0 0 0 1.5vw;
  a {
    color: rgba($color: #ffffff, $alpha: 0.3);
    a:hover {
      color: #00bfff;
    }
  }
  li {
    background-color: rgba($color: #222222, $alpha: 0.4);
    padding: 15px;
    font-size: 2vh;
  }
}
ul.user-nav-bar li .active {
  color: #ffffff;
  text-decoration: none;
}
.user-nav:hover {
  color: #00bfff;
  text-decoration: none;
}

// TOP・POST・Bookmark・Messageの表示画面==================================
.pages {
  height: 100vh;
}
.page {
  background-color: rgba($color: #222222, $alpha: 0.4);
  height: auto;
  margin: 0 auto;
  display: flex;
  justify-content: space-around;
  align-items: center;
  display: none;
  padding: 1.5vh 1.5vw;
}

.change-page {
  display: flex;
  justify-content: space-around;
  height: 500px;

以上となります。

ここまでの完成イメージは以下の通りになるかと思います。
changepreviews.png

②JavaScriptに画面を切り替える記述を行う。

tabにマウスを乗せた時に、画面を切り替える処理をjsファイルに記述していきます。
今回は、show.jsというファイルに記述していきます。

show.js
$(function() {
  // class="user-nav"と設定しているDOM要素を取得してtabsという変数名で定義する。
  let tabs = $(".user-nav");

  // クラス切り替えをtabSwitch関数という名前で定義する。
  function tabSwitch() {
    // 全てのactiveクラスの中から、"active"という要素を削除
    $(".active").removeClass("active");
    // クリックしたタブに"activeクラス"を追加する。
    $(this).addClass("active");
    // 何番目の要素(タブ)がマウスオーバー されたのかを配列tabsから要素番号を取得して、変数indexに代入
    const index = tabs.index(this);
    // 全てのpageクラスから"show"という要素を削除して、マウスオーバーしたタブに対応したpageクラスに"showクラス"を追加する。
    $(".page").removeClass("show").eq(index).addClass("show");
  }
  // タブがマウスオーバーされるとtabSwitch関数が呼び出される。
  tabs.hover(tabSwitch);
});

以上となります。

完成形はこちらになります。
mypageTabChange2.gif

終わり

ご覧いただきありがとうございました。

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

HerokuをリセットしようとしたらActiveRecord::StatementInvalid: Mysql2::Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near が出た場合の対処法

リセットしようとした経緯

本番環境ではあるものの、たくさんのユーザーやデータを作りすぎ、見た目が繁雑になってきたため、Herokuのデータベースをリセットして初めからやり直したいと考えた。

背景

リセットをするために以下を実行した。

% heroku run rails db:reset

(中略)

rails aborted!
ActiveRecord::ProtectedEnvironmentError: You are attempting to run a destructive action against your 'production' database.
If you are sure you want to continue, run the same command with the environment variable:
DISABLE_DATABASE_ENVIRONMENT_CHECK=1

DISABLE_DATABASE_ENVIRONMENT_CHECK=1を付けて再度実行

% heroku run rails db:reset DISABLE_DATABASE_ENVIRONMENT_CHECK=1

rails aborted!
ActiveRecord::StatementInvalid: Mysql2::Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '(6) NOT NULL, `updated_at` datetime(6) NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=u' at line 1

SQL?
いじった覚えがない。。結局リセットしたいわけだから、今回はそんなの関係ない!無視し、自分のリセット方法に誤りがあると仮説を立て、再度リセット方法を調べ直した。

行ったこと

データベースのリセットに関しては、以下のコマンドを実行することを知ったため、それで実行した。

% heroku pg:reset DATABASE
アプリ名 has no databases

消えた!と安心してデータベースを作成してみた、

% heroku run rails db:create

(中略)

Database 'heroku_データベース名' already exists

???
データベースが存在している?
ステータスをみてみた

% heroku run rails db:migrate:status

up
up
up
...(続く)

まだデータベースが残っていたことを確認し、再度データベース削除方法を調べ、以下のコマンドを実行した。

% heroku run RAILS_ENV=production DISABLE_DATABASE_ENVIRONMENT_CHECK=1 bundle exec rake db:drop

(中略)

Dropped database 'heroku_データベース名'

正常にデータベースが削除されたことを確認し、以下のコマンドで、データベースを再度作り直した。

% heroku run rails db:create

(中略)

Created database 'heroku_データベース名'

新しいデータベースが作成されたことを確認した。ステータスを確認すると

% heroku run rails db:migrate:status

(中略)

Schema migrations table does not exist yet.

スキーママイグレーションテーブルがないと出たのでマイグレートを実行。

% heroku run rails db:migrate

■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■

この様に、マイグレート成功のログが流れたら、成功です。笑

思ったこと

直感的にheroku runコマンドの後に、テスト環境でterminalでいつも入力している様なrailsコマンド風に入力をしても、herokuは正しく動いてくれないんだなあと思いました。

今回herokuを使っていますが、Herokuを使う企業は日本だと少ないらしいので、今後はAWSに乗り換えるつもりです。

参考記事

https://qiita.com/quattro_4/items/a2eb3618207e21ca00d3
https://qiita.com/take18k_tech/items/7afdde59d387fbde5f7e
https://qiita.com/twipg/items/d8043cd4681a2780c160

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

RailsのAPIモードをCookie(session)認証に対応させる

onecareer内のアプリ(Vue.js + RailsのAPIモード)の認証方式をtoken認証からsession(cookie)に変更した時の変更点をまとめる

これを始めた理由

既存のWebアプリからシームレスにログインさせる要件が発生したため

  • 普通のwebアプリで実装された画面でログインしたユーザーを、SPAで作られた画面にシームレスにログインさせるには
    • OAuthサーバーを作るか
    • Cookie(Session)の認証を利用する必要がある。

今回は納期優先でsession認証に変更

Cookie認証に必要だったもの

 サーバー側

config/application.rb

    # Only loads a smaller set of middleware suitable for API only apps.
    # Middleware like session, flash, cookies can be added back manually.
    # Skip views, helpers and assets when generating a new resource.
    config.api_only = false # <- false を true に変更

session storeをcookieにする場合は config.api_only = true にした上で必要なmiddle wearを読み込む方法で実現出来たが、それはそれで

  • Deviseと相性が悪い。
    • Deviseを使うとsessionに多くの情報が乗るのでCookieには入り切らない。
  • 既存のWebアプリがsession storeをredisにしているのでそれを利用したい。

上記の制約により、api_onlyをfalseにする方にした。
(api_only=trueかつ deviseを利用する方法で良い方法があれば教えて下さい :bow:

application_controller.rb (APIの基底のcontroller)

class ApplicationController < ActionController::API
  include ActionController::Cookies #<- これを追加

ActionController::API を継承して作る場合はこの設定も必要だった。

クライアント側

axios(ajaxライブラリ)

  axios.create({
    withCredentials: true,
    //...
  })

axios違うドメインCookieを送る機能だが、これ有効しないとsession cookieが使えなかった。

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

テンプレート: Docker コンテナで Ruby / Rails の開発環境を構築する (Mac版)

手軽に Ruby / Rails の開発環境を構築したいなら Docker が最適です。
ここでは、ホストOSとしてMac OSを使用しています。
(Ubuntuホストの場合はこちらを参照してください)

Dockerコンテナでは、ベースOSをUbuntu 20.04とし、Rails アプリケーションを実行するために必要なライブラリやRuby本体をインストールしていきます。
Railsアプリケーション本体はホストOS上のローカルディレクトリから参照できるようにします。
これはRailsアプリケーションの実行環境をDockerコンテナが担い、エディタによるRailsアプリケーションの編集やソースコード管理をホストOS上で実現するためのものです。

以下のようなメリットがあります。

1.ホストOS上にRubyやRailsを直接インストールする必要がない
2.必要に応じて異なるバージョンのRubyやRailsを別々のコンテナで実行させることも可能
3.RailsソースコードはホストOS上の好みのエディタで編集できる
4.Gitの管理対象をホストOS上のソースコードに限定できる
5.(以下のDockerfile.devもGitの管理対象とすべき)

コンテナの構築と起動

あらかじめDockerサービスをホストOS上にインストールしておいてください。

まずは、任意のディレクトリに以下のファイルを配置します。

Dockerfile.dev
# Docker による Ruby / Rails 開発環境の構築
#
# 任意バージョンの Ruby を Ubuntu ベースのコンテナ上にインストールする
# また、node.js をインストールする
# Rails アプリケーションは、コンテナ内ではなく、ホストOSのローカルディレクトリに生成する

From ubuntu:20.04

ENV DEBIAN_FRONTEND=noninteractive

# Ubuntu にインストールされているソフトウェアを最新にする
RUN apt-get update -y
RUN apt-get upgrade -y

# Ruby のビルドに必要なパッケージを apt-get 経由でインストールする
RUN apt-get install -y build-essential
RUN apt-get install -y libssl-dev libreadline-dev zlib1g-dev
RUN apt-get install -y git wget 

# sqlite3 を利用する場合に必要なライブラリ
RUN apt-get install libsqlite3-dev

# MySQL or MariaDB を利用する場合に必要なライブラリ
RUN apt-get install -y libmysqlclient-dev

# その他の便利ツール
RUN apt-get install -y nano

# ruby-build を使って任意の Ruby バージョンをインストールする
RUN git clone --depth=1 https://github.com/rbenv/ruby-build
RUN PREFIX=/usr/local ./ruby-build/install.sh
RUN rm -rf ruby-build
RUN ruby-build 2.7.2 /usr/local

# node.js, npmをインストール
RUN apt-get install -y nodejs npm
# n packageをインストール
RUN npm install n -g
# n packageを使ってnodeをインストール
RUN n stable
# 最初に入れた古いnode.js, npmを削除
RUN apt-get purge -y nodejs npm
# yarn packageをインストール
RUN npm install yarn -g

以下はコンテナの生成方法
(your_app_nameをあなたのアプリケーション名に変更する)

$ export YOURAPP=your_app_name
$ docker build -t $YOURAPP -f Dockerfile.dev .

コンテナを起動する

$ export REPO=`pwd`
$ docker run -d --name $YOURAPP -v $REPO/:/$YOURAPP/ -p 4000:3000 -ti $YOURAPP

コンテナ内で Rails アプリケーションを生成する

コンテナ上のbashを起動し、Railsアプリケーションを構築していきます。

$ docker exec -ti $YOURAPP bash

あなたのアプリケーションディレクトリに移動する
(your_app_nameをあなたのアプリケーション名に変更する)

$ cd /your_app_name

bundle init を実行して Gemfile ファイルを作成

$ bundle init

Gemfile を編集する(以下ではnanoエディタを使用)

$ nano Gemfile

Gemfile内の # gem "rails" のコメントを外す
(# gem "rails" を gem "rails" に変更する)

bundle install により Rails 関連の Gem を vendor/bundle にインストールする

$ bundle config set path 'vendor/bundle'
$ bundle install

rails new により Rails アプリケーションをこのディレクトリで新規に生成する
Overwrite /example/Gemfile? (enter "h" for help) [Ynaqdhm] と尋ねられたら Y と返答する

$ bundle exec rails new .

Rails アプリケーションを起動する
ホスト OS からアクセスできるようにバインドする IP アドレスを 0.0.0.0 にしておく

$ bundle exec rails s -b 0.0.0.0

コンテナ上の作業が完了したので、改めてホスト OS のブラウザで http://localhost:4000 にアクセスする
ブラウザ上で Yay! You’re on Rails! が表示されたら成功です

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

rails6 form_with(ヘルパー)のラジオボタンにCSSを装飾する方法

目的:rails6にてform_withでラジオボタンを実装、それに対してCSSで装飾を行います。

苦戦したこと:ヘルパーを使っていることにより、ラジオボタンの内部構造が見えなくなってしまった。

①他のオプションと違いラジオボタンはclassをセレクタにしてもCSSを反映する事ができない。
②label forが設定されていない事により、選択作業が反映できないバグが発生。

以下、内容を記載。

①ラジオボタンはclassセレクタではCSSが反映されない。

HTML
スクリーンショット 2020-11-14 19.30.25.png

CSS
.second{
font-size: 14px;
font-weight: bold;
display: block; /* ブロックレベル要素化する /
float: left; /
要素の左寄せ・回り込を指定する */
.
.
}

このような記述では全くCSSが反映されません。検証ツールで見てみると、

スクリーンショット 2020-11-14 18.28.39.png

一見OKな気もします。ちなみに、検証ツールで見えるのはヘルパーの記述がHTMLに変換されたものなので、ツール上で修正を加えて正しく動作すればその修正したコードを丸々コピーしてHTML型に変えてしまうのも一つの手です(データの送信自体はHTML形式でも問題なし)。

結論を言ってしまうとラジオボタンにCSSを設定する場合はclassではなくlabel属性に記述をしなくてはいけません。その際、合わせてtype="radio"属性で設定されている標準のラジオボタンの表示を見えなくする必要もあります。

というわけで、

CSS
.fields-label label{
font-size: 14px;
font-weight: bold;
display: block; /* ブロックレベル要素化する /
float: left; /
要素の左寄せ・回り込を指定する */
.
.
}

とし、

input[type=radio] {
display: none; /* ラジオボタンを非表示にする */
}

の記述を加えます。これで外見上は装飾されたラジオボタンのでき上がりです。

スクリーンショット 2020-11-14 18.35.42.png

しかし、実際に入力作業をしてみると問題発生。
ボタンをクリックしたら変色する仕様を以下のように記述したはずが全く動作しません。

input[type="radio"]:checked + label{
background:blue;/* マウス選択時の背景色を指定する /
color: #ffffff; /
マウス選択時のフォント色を指定する */
}

これはtype="radio"属性(labelの要素まで含めて)がクリックされたら指定の動きをする記述です。
再び検証ツールで確認。

スクリーンショット 2020-11-14 18.43.06.png

一見問題が無いように見えます。ちゃんと選択肢事にid、valueも違う値が登録されています。

②label for属性の指定がないことによるlabel要素の読み込みエラー

この原因を特定するのにかなり苦労しましたが、ヘルパーを使わない場合のlabel設定の仕方を確認し、わかりました。

idに対して、label forが設定されていない事で、それぞれの選択肢がうまくVIEWに反映されていなかった。意味わかんないですね、とりあえずidとlabel forが一致して初めてラジオボタンのCSS設定が反映されるという事です。なので、label forにidと同じ値を設定します。
//
スクリーンショット 2020-11-14 19.35.12.png

これが完成形です。
label for="id" 選択肢 /label
の書き方はこの指定となりますので、順番も変えました。

スクリーンショット 2020-11-14 19.05.09.png

このように動作しました。ちなみに検証ツールで確認すると、

スクリーンショット 2020-11-14 19.07.24.png

このようにlabel forが定義されており、idと紐づいている状態です。

※ちなみに、別箇所でcollection_selectでラジオボタンを作っていた箇所は選択肢事の全てのlabel forが同じ値になっており、どれを選択しても最初の選択肢が選択されてしまうというエラーも発生。
railsのフォームヘルパーでラジオボタンを作成する場合はlabel forの設定が必須になると思われますので、注意が必要です。

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

Rails Webサーバーとアプリケーションサーバー

はじめに

普段何気なく使っていたコマンド「rails s」。このコマンドにより、何がどう動いていたのか改めて認識したので、書き記します。

補足
rails sはローカルサーバーを立ち上げるコマンドです。

目次

1.Webサーバーとアプリケーションサーバー
2.ローカル環境
3.本番環境

1.Webサーバーとアプリケーションサーバー

処理の負荷を分散する(高速処理)ため、サーバーは機能ごとに分業されている。

Webサーバー
クライアントサイドからのリクエストを全て受ける。リクエストの内容が静的(HTMLやCSS)な場合、Webサーバーがブラウザ(クライアント)へレスポンスを返す。リクエストの内容が動的(データベースから情報を検索する等)な場合、アプリケーションサーバーへリクエストを渡す。

アプリケーションサーバー
Webサーバーから渡されたリクエストを受け取り、対応するコントローラーのアクションを実行する。その後、処理結果をWebサーバーに返す。

2.ローカル環境

ターミナルでrails s と入力するとサーバーが立ち上がる。そのときターミナルにはpumaが表示されている。これはRails用のアプリケーションサーバーであり、Webサーバーとの分業化されていない。

ターミナル
=> Booting Puma
=> Rails 6.0.3.4 application starting in development 
=> Run `rails server --help` for more startup options
Puma starting in single mode...
* Version 3.12.6 (ruby 2.6.5-p114), codename: Llamas in Pajamas
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://localhost:3000
Use Ctrl-C to stop

3.本番環境

本番環境において、Webサーバーとアプリケーションサーバーを分業化する。理由は先述した通り、アクセスが集中する等のサーバー負荷対策のためである。Nginx(Webサーバー)やUnicorn(アプリケーションサーバー)等を組み合わせ、本番環境を構築する。

参考ページ
なぜrailsの本番環境ではUnicorn,Nginxを使うのか?  ~ Rack,Unicorn,Nginxの連携について ~【Ruby On Railsでwebサービス運営】

以上

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

Docker コンテナで Ruby / Rails 開発環境を構築する

手軽に Ruby / Rails の開発環境を構築したいなら Docker が最適です。
ここでは、ホストOSとしてUbuntuを使用しています。
Dockerコンテナでは、ベースOSをUbuntu 20.04とし、Rails アプリケーションを実行するために必要なライブラリやRuby本体をインストールしていきます。
Railsアプリケーション本体はホストOS上のローカルディレクトリから参照できるようにします。
これはRailsアプリケーションの実行環境をDockerコンテナが担い、エディタによるRailsアプリケーションの編集やソースコード管理をホストOS上で実現するためのものです。

以下のようなメリットがあります。

1.ホストOS上にRubyやRailsを直接インストールする必要がない
2.必要に応じて異なるバージョンのRubyやRailsを別々のコンテナで実行させることも可能
3.RailsソースコードはホストOS上の好みのエディタで編集できる
4.Gitの管理対象をホストOS上のソースコードに限定できる
5.(ただし、以下のDockerfile.devもGitの管理対象とすべき)

コンテナの構築と起動

あらかじめDockerサービスをホストOS上にインストールしておいてください。

まずは、任意のディレクトリに以下のファイルを配置します。

Dockerfile.dev
# Docker による Ruby / Rails 開発環境の構築
#
# 任意バージョンの Ruby を Ubuntu ベースのコンテナ上にインストールする
# また、node.js をインストールする
# Rails アプリケーションは、コンテナ内ではなく、ホストOSのローカルディレクトリに生成する

From ubuntu:20.04

ENV DEBIAN_FRONTEND=noninteractive

# ローカルと同じユーザを作成する
ARG uid=unknown
ARG user=unknown
RUN useradd -m -u ${uid} ${user}

# Ubuntu にインストールされているソフトウェアを最新にする
RUN apt-get update -y
RUN apt-get upgrade -y

# Ruby のビルドに必要なパッケージを apt-get 経由でインストールする
RUN apt-get install -y build-essential
RUN apt-get install -y libssl-dev libreadline-dev zlib1g-dev
RUN apt-get install -y git wget 

# sqlite3 を利用する場合に必要なライブラリ
RUN apt-get install libsqlite3-dev

# MySQL or MariaDB を利用する場合に必要なライブラリ
RUN apt-get install -y libmysqlclient-dev

# その他の便利ツール
RUN apt-get install -y nano

# ruby-build を使って任意の Ruby バージョンをインストールする
RUN git clone --depth=1 https://github.com/rbenv/ruby-build
RUN PREFIX=/usr/local ./ruby-build/install.sh
RUN rm -rf ruby-build
RUN ruby-build 2.7.2 /usr/local

# node.js, npmをインストール
RUN apt-get install -y nodejs npm
# n packageをインストール
RUN npm install n -g
# n packageを使ってnodeをインストール
RUN n stable
# 最初に入れた古いnode.js, npmを削除
RUN apt-get purge -y nodejs npm
# yarn packageをインストール
RUN npm install yarn -g

以下はコンテナの生成方法
(your_app_nameをあなたのアプリケーション名に変更する)

$ export YOURAPP=your_app_name
$ docker build -t $YOURAPP --build-arg uid=$(id -u $USER) --build-arg user=$USER -f Dockerfile.dev .

コンテナを起動する

$ export REPO=`pwd`
$ docker run -d --name $YOURAPP -v $REPO/:/$YOURAPP/ -p 4000:3000 -v /etc/group:/etc/group:ro -v /etc/passwd:/etc/passwd:ro -u $(id -u $USER):$(id -g $USER) -ti $YOURAPP

コンテナ内で Rails アプリケーションを生成する

$ docker exec -ti $YOURAPP bash

あなたのアプリケーションディレクトリに移動する
(your_app_nameをあなたのアプリケーション名に変更する)

$ cd /your_app_name

bundle init を実行して Gemfile ファイルを作成

$ bundle init

Gemfile を編集する(以下ではnanoエディタを使用)

$ nano Gemfile

Gemfile内の # gem "rails" のコメントを外す
(# gem "rails" を gem "rails" に変更する)

bundle install により Rails 関連の Gem を vendor/bundle にインストールする

$ bundle config set path 'vendor/bundle'
$ bundle install

rails new により Rails アプリケーションをこのディレクトリで新規に生成する
Overwrite /example/Gemfile? (enter "h" for help) [Ynaqdhm] と尋ねられたら Y と返答する

$ bundle exec rails new .

Rails アプリケーションを起動する
ホスト OS からアクセスできるようにバインドする IP アドレスを 0.0.0.0 にしておく

$ bundle exec rails s -b 0.0.0.0

コンテナ上の作業が完了したので、改めてホスト OS のブラウザで http://localhost:4000 にアクセスする
ブラウザ上で Yay! You’re on Rails! が表示されたら成功です

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

テンプレート: Docker コンテナで Ruby / Rails の開発環境を構築する(Ubuntu版)

手軽に Ruby / Rails の開発環境を構築したいなら Docker が最適です。
ここでは、ホストOSとしてUbuntuを使用しています。
(Macホストの場合はこちらを参照してください)

Dockerコンテナでは、ベースOSをUbuntu 20.04とし、Rails アプリケーションを実行するために必要なライブラリやRuby本体をインストールしていきます。
Railsアプリケーション本体はホストOS上のローカルディレクトリから参照できるようにします。
これはRailsアプリケーションの実行環境をDockerコンテナが担い、エディタによるRailsアプリケーションの編集やソースコード管理をホストOS上で実現するためのものです。

以下のようなメリットがあります。

1.ホストOS上にRubyやRailsを直接インストールする必要がない
2.必要に応じて異なるバージョンのRubyやRailsを別々のコンテナで実行させることも可能
3.RailsソースコードはホストOS上の好みのエディタで編集できる
4.Gitの管理対象をホストOS上のソースコードに限定できる
5.(以下のDockerfile.devもGitの管理対象とすべき)

コンテナの構築と起動

あらかじめDockerサービスをホストOS上にインストールしておいてください。

まずは、任意のディレクトリに以下のファイルを配置します。

Dockerfile.dev
# Docker による Ruby / Rails 開発環境の構築
#
# 任意バージョンの Ruby を Ubuntu ベースのコンテナ上にインストールする
# また、node.js をインストールする
# Rails アプリケーションは、コンテナ内ではなく、ホストOSのローカルディレクトリに生成する

From ubuntu:20.04

ENV DEBIAN_FRONTEND=noninteractive

# ローカルと同じユーザを作成する
ARG uid=unknown
ARG user=unknown
RUN useradd -m -u ${uid} ${user}

# Ubuntu にインストールされているソフトウェアを最新にする
RUN apt-get update -y
RUN apt-get upgrade -y

# Ruby のビルドに必要なパッケージを apt-get 経由でインストールする
RUN apt-get install -y build-essential
RUN apt-get install -y libssl-dev libreadline-dev zlib1g-dev
RUN apt-get install -y git wget 

# sqlite3 を利用する場合に必要なライブラリ
RUN apt-get install libsqlite3-dev

# MySQL or MariaDB を利用する場合に必要なライブラリ
RUN apt-get install -y libmysqlclient-dev

# その他の便利ツール
RUN apt-get install -y nano

# ruby-build を使って任意の Ruby バージョンをインストールする
RUN git clone --depth=1 https://github.com/rbenv/ruby-build
RUN PREFIX=/usr/local ./ruby-build/install.sh
RUN rm -rf ruby-build
RUN ruby-build 2.7.2 /usr/local

# node.js, npmをインストール
RUN apt-get install -y nodejs npm
# n packageをインストール
RUN npm install n -g
# n packageを使ってnodeをインストール
RUN n stable
# 最初に入れた古いnode.js, npmを削除
RUN apt-get purge -y nodejs npm
# yarn packageをインストール
RUN npm install yarn -g

以下はコンテナの生成方法
(your_app_nameをあなたのアプリケーション名に変更する)

$ export YOURAPP=your_app_name
$ docker build -t $YOURAPP --build-arg uid=$(id -u $USER) --build-arg user=$USER -f Dockerfile.dev .

コンテナを起動する

$ export REPO=`pwd`
$ docker run -d --name $YOURAPP -v $REPO/:/$YOURAPP/ -p 4000:3000 -v /etc/group:/etc/group:ro -v /etc/passwd:/etc/passwd:ro -u $(id -u $USER):$(id -g $USER) -ti $YOURAPP

コンテナ内で Rails アプリケーションを生成する

コンテナ上のbashを起動し、Railsアプリケーションを構築していきます。

$ docker exec -ti $YOURAPP bash

あなたのアプリケーションディレクトリに移動する
(your_app_nameをあなたのアプリケーション名に変更する)

$ cd /your_app_name

bundle init を実行して Gemfile ファイルを作成

$ bundle init

Gemfile を編集する(以下ではnanoエディタを使用)

$ nano Gemfile

Gemfile内の # gem "rails" のコメントを外す
(# gem "rails" を gem "rails" に変更する)

bundle install により Rails 関連の Gem を vendor/bundle にインストールする

$ bundle config set path 'vendor/bundle'
$ bundle install

rails new により Rails アプリケーションをこのディレクトリで新規に生成する
Overwrite /example/Gemfile? (enter "h" for help) [Ynaqdhm] と尋ねられたら Y と返答する

$ bundle exec rails new .

Rails アプリケーションを起動する
ホスト OS からアクセスできるようにバインドする IP アドレスを 0.0.0.0 にしておく

$ bundle exec rails s -b 0.0.0.0

コンテナ上の作業が完了したので、改めてホスト OS のブラウザで http://localhost:4000 にアクセスする
ブラウザ上で Yay! You’re on Rails! が表示されたら成功です

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

【初投稿】売れた商品に対して、SoldOutの文字を出力する

条件式でexists?メソッドを使用し、指定したテーブル内のカラム名(item_id)と売れた商品(item.id)が一致したら商品にSoldOutの文字を出力するコードを書きました。

qiita.rb
<% if Order.exists?(item_id: [item.id]) %> 
  <div class='sold-out'>
    <span>Sold Out!!</span>
  </div> 
<% end %>

私のコードの書き方で当たり前なことかもしれませんが、こういう動作をさせたいといいう目的がある場合、今回の動作であれば売れた商品にSoldOutを表示させたいという目的がありました。

その目的に対して、exists?メソッドはいきなり見つけたわけではなく、まず商品を購入してitem_idを商品情報と一緒にorderテーブル保存しているのでitem_idがorderテーブルにあることに気が付きます。

商品のid、item_idを見境なしに判別してしまうのは違うので、orderテーブルにitem_idが存在した場合トップページに存在するitem_idとorderテーブルの中のitem_idを判別してくれればうまくいきそうだなと考え、トップページ商品id、item_idとorderテーブルのitem_idと見比べたいなと思いexists?メソッドにたどり着きました。

文章で書くときはなんとなく順番通り書きましたが、考えている時は初めにorderテーブルのitem_idと見比べるのではないかと思い付きまずさかのぼって考え、答えに至りました。

プログラミングで目的の動作を考える時は、抽象化された目的に対し細かくプロセスを考えることが大切だと分かりました。

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

Railsで非同期通信で実装する投稿機能

はじめに

今回は非同期通信による投稿機能を実装していきます。
今回は非同期処理を行う事にフォーカスを当てるためすでに画面遷移ありの投稿機能が実装されている状態からのスタートとなります。
jsの理解のため、素のjsでの記述となります。
また初学者のため間違え等ありましたらご指摘頂けると幸いです。

手順1 remote: tureを追加

入力フォームにremote: tureを追記しましょう!

<%# タスク入力フォーム %>
<div class="task-contents">
    <%= form_with model: @category, id: 'new_tasks', class: 'new_tasks' do |f| %>
     <div class="task-field">
       <%= f.text_field :task %>
     </div>
     <div class="task-actions">
       <%= f.submit "+" %>
     </div>
    <% end %>
</div>

ここでどこにremote:tureがあるんだ?と思う方もいると思うのですがform_withを使うとデフォルトでajax通信を行う仕様になっているのでform_withを使う場合は特に記述は不要です。逆にajax通信にしない場合はlocal: trueにすると通常のリクエストになります。
form_forを使う方はremote: trueオプションを忘れずに追記してください。
心配な方は開発ツールなどで確認するとdata-remote="true"となっているはずなので確認してみると良いと思います。

手順2 コントローラーの記述

def create
      @category = Category.new(category_params)
      @category.save
      @categories = current_user.categories.all
end

一般的な保存機能を行う記述ですが@categories = current_user.categories.allの記述はこの後のjsに渡す情報として必要なので記述しています。

手順3 create.js.erbにJavaScriptの記述をする

remote: trueにより返却される内容がHTMLではなくアクション名.js.erbファイルが読み込まれるようになっているのでcreate.js.erbファイルを作成します

(function(){
  document.querySelector(".task-lists").innerHTML =  '<%= j(render 'categories/index', categories: @categories) %>'
  document.querySelector("#category_task").value = ''
})();

.innerHTMLでHTMLの中身を書き換えています。

'<%= j(render 'categories/index', categories: @categories) %>'
ここで@categoriesを渡すために手順2で定義しています。
document.querySelector("#category_task").value = ''
この記述により入力入力フォームを空にしています。

最後に

以上で非同期での投稿機能が完成です!
重要なのはajax通信のデータの流れを理解できればそれぞれの処理はシンプルなので理解しやすいかなと思います。
初学者のためもっと良い記述があればご教授頂けると幸いです。

非同期削除などの記事もよければどうぞ〜
https://qiita.com/shantianchengyilang316/items/10ab2d84f6cfcfd29def

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

whereを用いたparamsの例

こちら完全に備忘録です。。。
見苦しいですが一度投稿させてください。
後で修正いたします。

アソシエーション

user.rb
has_many :hoges
has_many :hoge_events
hoge.rb
belongs_to :user
has_many :hoge_events
hoge_events.rb
belongs_to :user
belongs_to :hoge

ビュー

画面(ビュー)には以下の様なリンクがある。

①hoge_event
②hoge_event
③hoge_event
④hoge_event

例えば、現在操作中のユーザーが③のイベント詳細をクリックすると、③に関する情報全てが表示される様に。
①のイベント詳細をクリックすれば、①に関する情報全てが表示される様にしたい。(paramsもしっかり渡されていることとする)

コントローラー

  def show
    @hoge_events = current_user.hoge_events.where(hoge_id: params[:hoge_id])
  end

イベントごとの詳細を画面に表示できた。

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

【Rails】Bootstrapのtext-lightをlink_toメソッドに継承できないのは、メソッドに引数としてクラスを書いていないのが原因らしい

はじめに

BootstrapをRails環境に導入し、きれいにしていくぞ~って思ったらいきなり躓いたので共有します。

事象

どうしてもlink_toメソッドの要素に継承されない

HTMLで記載したときはテキストに白色が適用されている

image (2).png

index.html
<body> 
  <nav class="navbar navbar-expand-sm navbar-dark bg-dark"> 
    <ul class="navbar-nav mr-auto"> 
      <li class="nav-item"><a class="nav-link" href="">tweetApp</a></li> 
    </ul> 
  </nav>

なぜかlink_toに置き換えると継承されない

navbar-darkクラスを設定してるので、文字色が白になるはずだが、青になっている。
image (1).png

index.erb
<nav class="navbar navbar-expand-sm navbar-dark bg-dark"> 
    <ul class="navbar-nav mr-auto"> 
      <li class="nav-item"> 
        <a class="nav-link" href=""> 
          <%= link_to('tweetApp',"/") %> 
        </a> 
      </li> 
    </ul> 
  </nav>

DevtoolでCSSを生成されたHTMLを見ると、、、

生成されたリンクがaタグから飛び出てる、、、

index.erb
<nav class="navbar navbar-expand-sm navbar-dark bg-dark"> 
    <ul class="navbar-nav mr-auto"> 
      <li class="nav-item"> 
        <a class="nav-link" href=""></a> 
        <a href="/">tweetApp</a> 
      </li> 
    </ul> 
  </nav>

link_toメソッドにはクラスを指定するオプションが存在する模様

書式 link_to(リンクテキスト, パス [, オプション, HTML属性 or イベント属性])
classオプション class: "クラス名"
参考 Railsドキュメント-ビューについて

書式に従って記載してみる

index.erb
<nav class="navbar navbar-expand-sm navbar-dark bg-dark"> 
    <%= link_to("TweetApp","/",class: 'text-light')%> 
  </nav>

結果、、、できた。

image.png

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

heroku run rails db:migrateをした時のPG::DatatypeMismatchエラー

エラー内容

PG::DatatypeMismatch: ERROR: default for column "complete" cannot be cast automatically to type integer

comleteカラムを自動的に整数には出来ませんと言われた。

原因は

 def up
    change_column :habits, :complete, :integer, using: 'complete :: integer'
  end

  def down
    change_column :habits, :complete, :boolean, default: false, null: false
  end
end

change_columnでbooleanからinteger型に変更したんですが、complteカラムにdefaultでfalseが入ってるままintegerに変更しようとしたからエラーが発生。

なのでcomplteカラムのfalseとなってるデータを削除してrails db:migrate:reset

これで無事解決

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

よく理解していないまま使用していたもの一覧: Ruby編

はじめに

この記事は私が勉強をしている中で「よくわからないまま用いていたもの」をまとめることで、私自身の理解を深めることが目的です。以下に一覧として記載いたします。

Ruby編

①文字コードの指定

Rubyなどのプログラムファイルにおいて、以下のようなコードを冒頭に書くことで文字コードを指定する場合があります。

sample1.rb
 # -*- encoding: utf-8 -*-

これはマジックコメントと呼ばれるものです。私は毎回必要だと思って、挿入していました。しかし、Ruby2.0以降ではデフォルトでUTF-8が入っているため、現在のRubyではわざわざUTF-8を指定する必要がないのです。

私が毎回マジックコメントを書いていた原因としては、Ruby2.0より前のものを使用していたコードを参考にしていたからだと考えられます。

②rubyjemsについて

Rubyのコードにおいて、たまにこのような行を見かけると思います。

sample2.rb
 require "rubyjems"

そもそもrubyjemsとは、Rubyにおいてライブラリの作成や公開、インストールを助けるシステムです。以前のRubyでは、gemとしてインストールされるモジュールを使うプログラムを書く場合、まずrubygemsに対してrequireを実行する必要がありました。しかしRuby1.9以降、rubygemsモジュールが標準ライブラリの一部となったため、わざわざrubyjemsをrequireする必要はなくなったのです。

私がこれを書いていた原因も、Ruby1.8以前のものを使用していたコードを参考にしていたからだと考えられます。

まとめと感想

勉強のためにさまざまなコードを参考にする中で、実際は不要なのに使用していたものがあったことに気づきました。
言語のアップデートが理由で不要になったものがある、ということも学んだので、今後は載っているコードで使われている言語のバージョンなども意識して見ていこうと思います。

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

Railsのルーティングの記述方法

概要

Railsでルーティングを設定する際、私が勉強したことを復習の意味も込めてまとめてみます。

ルーティングとは

Railsにおけるルーティングとは、リクエストされたURLからコントローラとアクションを選択することです。

ルーティングの設定方法

config/routes.rbファイル内にて設定を記述し、その内容がルーティングの設定となります。

config/routes.rb
Rails.application.routes.draw do
  #ここにルーティングを記載
end

ルーティングの記述方法

基本は「メソッド "パス" => "コントローラ#アクション"」にて記載する。

config/routes.rb
Rails.application.routes.draw do
  #例
  get "about" => "top#about"
  post "login" => "sessions#login
end

コントローラ#アクションがパスと同じ場合、省略が可能である

config/routes.rb
Rails.application.routes.draw do
  #例
  get "info/room"
end

asオプションを付ければルーティングに名前を付けることができる。"指定した名前_path"をメソッドとして呼び出すと"/指定した名前"で文字列を返す。

config/routes.rb
Rails.application.routes.draw do
  #例
  get "help" => "document#help", as: "help"
end

":パラメータ"と記載すると、パラメータをパスとして使用することができる。

config/routes.rb
Rails.application.routes.draw do
  #例
  get "posts/:year/:month" => "posts#show
end

リソースベースのルーティングの記述方法

リソースベースでルーティングを設定するにはresourcesメソッドを記載し、リソース名を複数形にします。
また、単数リソースでルーティングを設定するにはresouceメソッドを記載し、リソース名を単数形にします。
Railsのリソースベースとは、RESTに基づきコントローラで扱う対象に名前をつけたものです。

config/routes.rb
Rails.application.routes.draw do
  #例
  resources :users
  # get "users" => "users#index"
  # get "users/:id" => "users#show"
  # get "users/new" => "users#new"
  # get "users/:id/edit" => "users#edit"
  # post "users" => "users#create"
  # patch "users/:id" => "users#update"
  # delete "users/:id" => "users#destroy"
  resource :account
  # get "account" => "account#show"
  # get "account/new" => "account#new"
  # get "account/edit" => "account#edit"
  # post "account" => "account#create"
  # patch "account" => "account#update"
  # delete "account" => "account#destroy"
end

resourcesメソッドで作ったルーティングに追加でアクションを追加する場合は、ブロックで囲み、その中で新たにメソッド、アクションを追加で記述します。
追加のアクションが集合を表す場合は、"on: :collection"、id属性を追加する場合は、"on: :member"を設定します。

config/routes.rb
Rails.application.routes.draw do
  #例
  resources :users do
    get "search", on: :collection
    patch "suspend", "restore", on: :member
  end
end

resourcesメソッドで作ったアクションの内、必要のないアクションはonlyオプションやexceptアクションを渡し、アクションを設定します。

config/routes.rb
Rails.application.routes.draw do
  #例
  resources :users, only: [:index, :show]
  resources :users, except: [:show]
end

パスの指定

ルーティングで設定したパスはlink_toメソッドやredirect_toメソッドにてパスで指定できます。
例:リソース名 users

アクション URL    パス   
index users users_path
show users/:id user_path(user)
new users/new new_user_path
edit users/:id/edit edit_user_path(user)
create users users_path
update users/:id user_path(user)
destroy users/:id user_path(user)
~view.html.erb
<%= link_to "一覧", users_path %>

引数にモデルオブジェクトを渡すとidパラメータを取得することができます。
また、methodオプションを使えば、HTTPメソッドを区別できます。

~view.html.erb
<%= link_to "削除", user_path(@user), method: :delete %>

パスの簡略化

簡略化したパスの指定もできます。
第2引数にモデルオブジェクトを渡すとidパラメータを持ったパスと同じパスに変更できます。

~view.html.erb
<%= link_to "削除", users_path(@user), method: :delete %>
     ↓
<%= link_to "削除", @user, method: :delete %>

個別リソースを扱うアクションは[:アクション名, モデルオブジェクト]で設定できます。

~view.html.erb
<%= link_to "編集", edit_user_path(@user) %>
     ↓
<%= link_to "編集", [edit, @user] %>

idのいらないリソースを扱うアクションは"_path"を省略し、シンボルにすると設定できます。

~view.html.erb
<%= link_to "一覧", users_path %>
     ↓
<%= link_to "一覧", :users %>

まとめ

ルーティングとパスについて改めて理解を深めました。
ルーティングやパスは簡略化した状態でプログラムを書くことが多いので、本来の形を忘れがちになってしまうので、今回復習できてよかったです。
まだまだ細かく調べるとルーティングの設定方法はあると思うので、必要に応じて追加でまとめていきたいです。

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

【Ruby/Rails】Herokuでアプリケーションを公開する手順

はじめに

前提

個人的な備忘録
ruby 2.6.5 / Rails 6.0.3.4 / Mysql2

手順

rails_12factor インストール

Gemfile
group :production do
  gem 'rails_12factor'
end

bundle install => master にcommit

#追記
こちらのGemのインストールはRails5以降では不要のようです。
すでにメンテナンスがされていないと思われるGemをインストールしておくのは好ましくないので、不要なGemはアンインストールしておいた方がよさそうです。

参考URL
【初心者必見】RailsアプリをHerokuに公開するのにrails_12factorは不要

Herokuにアプリを作成

ターミナル
% heroku create アプリ名

アプリ名は _(アンダーバー)使えない

Herokuにアプリができているか確認

ターミナル
% git config --list | grep heroku

Mysqlの設定

clearDBアドオンを行う

ターミナル
% heroku addons:add cleardb

これによりDBがHerokuのデフォルトDB(PostgreSQL)からMysqlに変更

Mysql2(Gem)への対応

URLを再設定する

ターミナル
% heroku_cleardb=`heroku config:get CLEARDB_DATABASE_URL`

上記で変数を定義

ターミナル
% heroku config:set DATABASE_URL=mysql2${heroku_cleardb:5}

URLの設定が完了

環境変数の設定

ターミナル
% heroku config:set RAILS_MASTER_KEY=`cat config/master.key`

↓ 確認方法

ターミナル
% heroku config

Herokuにアプリをpush

ターミナル
% git push heroku master

マイグレーションを実行

ターミナル
% heroku run rails db:migrate

公開を確認

ターミナル
% heroku apps:info

エラーの確認

ターミナル
% heroku logs --tail --app アプリ名

Herokuのアップデート(必要に応じて)

Warning: heroku update available が出たときの対処

ターミナル
% heroku update

おわりに

画像がたぶん使えなくなっているのでAmazonS3の設定が必要かも

✔︎

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

[Rails]マイグレーションファイルについて勉強してみた!(テーブルへのカラム追加)

はじめに

Railsを操作していると、何となく存在は知っているけど、それがどういう役割を果たしているのかよく分かっていないファイルやコマンドが数多くあることに気づきました。
その中の一つが、マイグレーションファイルです。

モデルを作ると、なぜか知らんがマイグレーションファイルが出来上がっていて、rails db:migrateを打ち込むと、schema.rbにテーブルデータとして反映する...
それぐらいの認識でしたが、そこについて深掘りして解説していきます!

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

マイグレーションファイルは、データベースを生成する際の設計図になるものです。
また、マイグレーションファイルを実行することで、記述した内容に基づいたデータテーブルが生成されます。

マイグレーションファイルを生成する

ターミナルに、以下のように打ち込み、モデルを作成すると、自動的にマイグレーションファイルも生成されます。

基本構文
rails g model モデル名  #ここでは、モデル名を「book」とします
db/migrate/20201114044025_create_books.rb
class CreateBooks < ActiveRecord::Migration[5.2]
  def change
    create_table :books do |t|
      t.timestamps
    end
  end
end

マイグレーションファイルが出来上がった初期状態では、t.timestampsのみがデフォルトで記載されていることが分かります。
それにより、作成日時を意味するcreated_atと、更新日時を意味するupdated_atがカラムに追加されるのです。

それでは、早速作成したマイグレーションファイルを実行していきましょう!
実際に必要になってくるカラムが色々不足している気もしますが、ここはあえてこのままでいきます!

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

作成したマイグレーションファイルは、ターミナルに下記のコマンドを打ち込むと読み込まれ、データベースに反映されます。

コマンド
rails db:migrate

マイグレーションファイルの状態を確認

今回は作成したマイグレーションファイルが一つだけでした。
中には、同時に何個ものモデルを作成することもあると思います(というか、そっちの方が多い気もします)。
そんな時に、自分が作成したマイグレーションファイルが、どこまで実行されているか確認したいときは、以下のコマンドをターミナルに打ち込みましょう。

コマンド
rails db:migrate:status

そうすると、ターミナルに現在のマイグレーションファイルの状態が出力されます。

ターミナル
Status   Migration ID    Migration Name
--------------------------------------------------
  up     20201114044025   Create books

upになっているマイグレーションファイルはすでに実行済みのファイルなのでrails db:migrateコマンドを入力しても読み込まれることはありません。
なのでもし間違った名前でカラムを作成してしまったときにupになっているマイグレーションファイルを編集しても、読み込まれないので意味がないことになります。

参照:【Rails】マイグレーションファイルを徹底解説!

なるほど、それでは、今作成したばかりのbooksテーブルに、新たにカラムを追加したいときは、どうすればいいのでしょうか?
ひとまず、スキーマファイルを確認しにいきましょう!

スキーマファイルとは

マイグレーションが実行されるとdbフォルダにschema.rbというファイルが作成されます。
早速中身をみましょう。

db/schema.rb
ActiveRecord::Schema.define(version: 2020_11_14_044025) do
  create_table "books", force: :cascade do |t|
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end
end

無事にテーブルが作成されているようです。
force: :cascadeという記述により、外部キーが適切であればスキーマが再読み込みできるようになります。
それでは、ここにtitleというカラムを追加しましょう!

カラムを追加する方法

既存のテーブルに、カラムを追加する方法は2種類あります。

1. 新たにマイグレーションファイルを作成して、カラムを追加する
2. rails db:rollbackでカラムを追加する

①カラムを追加するためのマイグレーションファイルを作成する

この場合は、ターミナルに以下のコマンドを入力します

コマンド
rails g migration Add追加するカラム名To追加するテーブル名 追加するカラム名:

今回の場合だと、以下のようになりますね。

ターミナル
rails g migration AddTitleToBooks title:string

すると、以下のようなマイグレーションファイルが作成されます。

マイグレーションファイル
class AddTitleToBooks < ActiveRecord::Migration[5.2]
  def change
    add_column :books, :title, :string
  end
end

あとは、マイグレーションファイルを実行すれば、titleカラムがテーブルにめでたく追加されます。

コマンド
rails db:migrate

rails db:rollbackでカラムを追加する

コマンド
rails db:migrate:rollback

ターミナルにこのコマンドを入力すると、最新のマイグレーションファイルのバージョンがrails db:migrateする前の状態に戻ります。
今回の場合で言うと、ターミナルの表示が以下のように変化します。

ターミナル
Status   Migration ID    Migration Name
--------------------------------------------------
 down   20201114044025    Create books  #upからdownになっている!!!!

つまり、upからdownになることで、データベースがrails db:migrateされる前の状態に戻ります。
db:migrate前に戻ったので、最初に作成されたマイグレーションファイルに、titleカラムを追記してあげることができます。

db/migrate/20201114044025_create_books.rb
class CreateGenres < ActiveRecord::Migration[5.2]
  def change
    create_table :books do |t|
      t.string :title    #ここにtitleカラムを追加!!!!
      t.timestamps
    end
  end
end

あとは①のパターンと同様に、マイグレーションファイルを実行すれば、titleカラムが追加されます。

コマンド
rails db:migrate
db/schema.rb
ActiveRecord::Schema.define(version: 2020_11_14_044025) do
  create_table "books", force: :cascade do |t|
    t.string "title"  #titleカラムが追加された!!!!
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end
end

おわりに

マイグレーションファイルの修正を行う作業は、思っている以上にセンシティブになることが多いので、タイミングや順序などをしっかりと理解した上で取り組むべきだと再認識できました。
また一つ勉強になりました!!

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

Rails / Ruby : Mail の HTML テキスト を取得する方法

Rails で、 ActionMailer::Base を継承した Mailer クラス では mail というメソッドを使って メールオブジェクトを作成します。
その返り値のメールオブジェクトのメソッド deliver を呼び出すとメールが送信されます。

HTML をテキストとして取得

この HTML のメールの本文をテキストで取得する場合は次のようにメソッドを呼び出します。

mail.html_part.body.to_s

この html_part は Rails が使用している Gem mail で定義されています。

2020-11-14時点でのコード
    # Accessor for html_part
    def html_part(&block)
      if block_given?
        self.html_part = Mail::Part.new(:content_type => 'text/html', &block)
      else
        @html_part || find_first_mime_type('text/html')
      end
    end

Plain Text の本文を取得

Plain Text で取得する場合は text_part が使えます。

mail.text_part.body.to_s

.body までだと Body class のインスタンスが返ります。

関連

この記事では ActionMailer の mail メソッド の返り値から 本文を取得する方法について書いています。 送ったあとで本文を取得する場合は下記の記事が参考になります。

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

既存のRailsアプリにVuetifyを導入した

はやさを求めるなら

 Vue.jsが無事にできるようになったので、Vuetifyも、という手筈になった。Vuetifyとは素敵なコンポーネントがつまったフレームワークである。いちから作らなくてもボタンとかスライドとか、いちいちかっこいいやつがある。かっこいい。

 早速ドキュメントをみながら導入を試みた。しんどかった。全部英語だった。

https://vuetifyjs.com/en/getting-started/installation/

無からインストール

 はじめは既存のものに導入とか考えずに、無からやってみた。まずはVueのCLIというやつが必要らしい。公式を参考に入れよう。

https://cli.vuejs.org/

$ yarn global add @vue/cli

 そしてvueコマンドでつくる。

$ vue create my-app

 数多くのファイル群ができるが、そこで以下のコマンドを入力する。

$ yarn serve

 このyarn serveはpackage.jsonで定義されたオリジナルのスクリプト。
 "serve": "vue-cli-service serve",となっている。

 実行すると以下のようにハローワールドができる。やったぜ。

スクリーンショット 2020-11-09 5.30.44.png

 こんなページが表示された。成功だ。

既存のあれに入れる

 既存のアプリに入れ込むやり方だが、ここが参考になった。というよりほぼこの記事。ここの「3. Vuetifyをインストール」からやっていった。

【Rails6】10分でRails + Vue + Vuetifyの環境を構築する

 ただ、おかしなエラーが出た。当時の記憶は定かではないが、そのままではうまくいかなった気がする。エラーログに以下のissueへのリンクがのっていた。

https://github.com/vuetifyjs/vuetify/discussions/4068

"$attrs is readonly" and "$listeners is readonly" 

というやつだった。

$attrs is readonly" and "$listeners is readonly への対処

 上記のリンクを追っていくと、えらい人が回答を述べていた。
https://github.com/vuetifyjs/vuetify/discussions/4068#discussioncomment-24984

 こうするといいらしい。

config/webpack/environment.js
//中略 
// 以下を追記

environment.config.merge({
  resolve: {
    alias: {
      'vue$': 'vue/dist/vue.esm.js'
    }
  }
})

 そうしたらできた。ボタンが表示できた。

 やったね。

一難去ってまた一難

 さて、無事にボタンが導入できたけどもとあった文字がすっごいしたのほうへ行ってしまった。ここらへんのデザインを修正しないといけない。どうすればいいのだろう。

 次回もお楽しみに。

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

【Rails】updateアクション実行時にパスワードなどのバリデーションを通過させる方法

ユーザーのプロフィール情報を更新して保存するといった処理を実装するとき、ユーザーが変更したい情報のみを入力して、そのほかのフォームのバリエーションチェック(特にパスワード)はスキップさせたい!といった場面ってあるかと思います。今回はそのやり方を紹介します。

実装方法

バリデーションオプションにある、:onオプションを使います。例えばon: :createとすることによってレコードの新規作成時のみバリデーションチェックを行うことができます。
私の場合、利用規約と個人情報のチェック(acceptedカラム)にもバリデーションチェックを行っていたため、updateアクション時にもこのバリデーションチェックが発動してしまい、ユーザー情報の変更ができないという状態になってしまっていました。:onオプションでユーザーの新規登録時のみバリデーションチェックを行うようにすることで、これを回避することができます。

user.rb
validates :password, presence: true, length: { minimum: 6 }, on: :create
validates :accepted, acceptance: { message: 'をチェックしてください' }, on: :create

このようにアクションを指定することができます。
他にも:onオプションではカスタムコンテキストも定義できるそうです!
詳しくはRailsガイドのこちらの項目をご覧ください。

また、パスワードの場合は allow_nil: true としてしてやっても同じことができます。
これは、値がnilの場合はバリデーションを行わないオプションです。
え?これ指定したら空のパスワード保存されちゃわない??と思いますが、has_secure_passwordで(だいだいgem 'bcrypt'を使っていますよね)オブジェクトが作られるタイミングで存在性の検証が行われるようになっているので、空のパスワードがDBに保存されることはありません。
ですので、うまいことupdateアクション実行時のみバリデーションをスキップさせることができます。

今回のは個人開発でまだ規模は小さいですが、規模が大きいサービスでは何気ないバリデーションの変更はアプリに大きな影響を与えそうだなと感じましたね。。
バリデーションはグローバルに定義するのではなく、なるべく条件を限定して影響する範囲を少なくするよう心掛けたいです!

最後まで読んでいただきありがとうございます!

日々学んだことをアウトプットしてます!何かご指摘などありましたらコメント頂けますと幸いです。

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

メタプログラミングRuby(第I部・水曜日まで)を読んでいく

1章 頭文字M

  • メタプログラミングとは、コードを記述するコードを記述することである。

イントロスペクションとは?

プログラミングやRubyでは一般に、イントロスペクションとは実行時にオブジェクト、クラスなどを見てそのことを知る能力です。

cf. Rubyのイントロスペクション

例:

# 以下は、クラス定義です

class A
   def a; end
end

module B
   def b; end
end

class C < A
   include B
   def c; end
end

## イントロスペクション
# Q. Cのインスタンスメソッドとは何ですか?
# A. C.instance_methods # [:c, :b, :a, :to_json, :instance_of?...]

# Q. Cのみ宣言するインスタンスメソッドは何ですか?
# A. C.instance_methods(false) # [:c]

# Q. クラスCの祖先は何ですか?
# A. C.ancestors # [C, B, A, Object,...]

# Q. Cスーパークラス?
# A. C.superclass # A

メタプログラミング

  • ORMのクラスを作りたいとする。
# the_m_word/orm.rb
class Entity
  attr_reader :table, :ident

  def initialize(table, ident)
    @table = table
    @ident = ident
    Database.sql "INSERT INTO #{@table} (id) VALUES (#{@ident})"
  end

  def set(col, val)
    Database.sql "UPDATE #{@table} SET #{col}='#{val}' WHERE id=#{@ident}"
  end

  def get(col)
    Database.sql("SELECT #{col} FROM #{@table} WHERE id=#{@ident}")[0][0]
  end
end

以上のようなクラスを記述しなくても、
ActiveRecord::Base を継承するだけで、実行時にアクセサメソッドが定義されるようなコードを書くことができる。

  • クラスのアトリビュートごとにアクセサメソッドを書くのではなく、 ActiveRecord::Base を継承するだけで、実行時にアクセサメソッドが定義されるようなコードを書く( コードを記述するコードを記述する )ことができる。

第2章 月曜日:オブジェクトモデル

オープンクラス(モンキーパッチ)

  • class の主な仕事は、あなたをクラスのコンテキストに連れて行くこと。

    • クラスの宣言というよりもスコープ演算子のようなもの。
    • 標準クラスを含め、既存のクラスを再オープンして、その場で修正できるということ。
  • 元の同名のメソッドを上書きしてしまった場合など、クラスへの安易なパッチはモンキーパッチという蔑称で呼ばれている。

インスタンス変数

  • Rubyのオブジェクトのクラスとインスタンス変数には何のつながりもない。
    • インスタンス変数は値が代入された時に初めて出現する。
    • インスタンス変数の名前と値は、ハッシュのキーとバリューのようなもの。
      • キーとバリューはどちらもオブジェクトによって異なる可能性がある。
    • インスタンス変数はオブジェクトに住んでおり、メソッドはクラスに住んでいる。

メソッド

  • 同じメソッドであっても、クラスに着目しているときはインスタンスメソッドと呼び、オブジェクトに着目しているときはメソッドと呼ぶ。

クラス

  • クラスはオブジェクト
    • クラスはオブジェクトであり、クラスのクラスはClassクラス。
    • Classクラスのインスタンスはクラスそのもの。
# 引数の"false"は「継承したメソッドは無視せよ」という意味
Class.instance_methods(false) # => [:allocate, :new, :superclass]

モジュール

  • ClassクラスのスーパークラスはModule
    • クラスは、オブジェクトの生成やクラスを継承するための3つのインスタンスメソッド(new, allocalte, superclass)を追加したモジュールである。
Class.superclass # => Module
  • 通常、どこかでインクルードするときはモジュールを選択し、インスタンスの生成や継承をするときはクラスを選択する。

定数

  • 大文字で始まる参照は、クラス名やモジュール名も含めて、すべて定数。

    • 定数の値を変更することもできる。
      • Stringクラスの値を変更して、Rubyをぶっ壊すこともできる。
  • Rubyの定数とファイルの類似性

    • プログラムにあるすべての定数は、ファイルシステムのようにツリー状に配置されている。
      • モジュールおよびクラスがディレクトリ で、定数がファイル。

定数のパス

  • 定数のパスはコロン2つで区切る。
  • 定数ツリーの奥のほうにいるときは、ルートを示すコロン2つで書き始めれば、外部の定数を絶対パスで指定できる。

  • インスタンスメソッド Module#constants

    • 現在のスコープにあるすべての定数を戻す。 # => [:C, :Y]
      • ファイルシステムのlsコマンドのようなもの。
  • クラスメソッド Module.constants

    • 現在のプログラムのトップレベル定数を戻す。
  • クラスメソッド Module.nesting

    • パスを含んだ定数を返す。 # => [M::C::M2, M::C, M]

オブジェクトとクラスのまとめ

オブジェクトとは?

  • インスタンス変数の集まりにクラスへのリンクがついたもの。
  • オブジェクトのメソッドは、オブジェクトではなくオブジェクトのクラスに住んでいて、クラスのインスタンスメソッドと呼ばれている。

クラスとは?

  • オブジェクト(Classクラスのインスタンス)にインスタンスメソッドの一覧とスーパークラスへのリンクがついたもの。
    • ClassクラスはModuleクラスのサブクラス。
    • Classクラスにはインスタンスメソッドがある。
      • 例: new
    • クラス名を使って、クラスを参照する。

ネームスペース

  • クラスとモジュールの名前の衝突を回避するために、クラスをネームスペースにラップする。
    • 安易にモンキーパッチを適用すると、予期しない結果を招いてしまう。

loadとrequire

load

  • ファイルをロードしてコードを実行するために使う。
    • 変数はファイルのロード時にスコープを外れるが、スコープは外れない。
  • 呼び出すたびにファイルを実行する。

require

  • ライブラリをインポートするために使う。
  • ファイルを一度しか読み込まない。

Rubyのオブジェクトモデル

  • MyClass
    • class => Class
    • superclass => Object
  • Object
    • class => Class
  • Class
    • class => Class
    • superclass => Module
  • Module
    • superclass => Object

メソッド探索

  • Rubyがレシーバのクラスに入り、メソッドを見つけるまで継承チェーンを上ること。
  • 「右へ一歩、それから上へ(one step to the right, then up)」ルール
    • レシーバのクラスに向かって右へ一歩進み、メソッドが見つかるまで継承チェーンを上へ進むこと。
MySubClass.ancestors # => [MySubclass, MyClass, Object, Kernel, BasicObject]

レシーバ

  • 呼び出すメソッドが属するオブジェクト

継承チェーン

  • クラスの継承チェーンの例
    • クラス → スーパークラス → スーパークラス ...(BasicObjectまで続ける)

モジュールとメソッド探索

  • モジュールをクラス(あるいは別のモジュール)にインクルードすると、Rubyはモジュールを継承チェーンに挿入する。
  • それはインクルードするクラスの 真上 に入る。
module M1
  def my_method
    'My#my_method()'
  end
end

class C
  include M1
end

class D < C; end

D.ancestors # => [D, C, M, Object, Kernel, BasicObject]

prependメソッド

  • include と同じように動作するが、インクルード下クラスの にモジュールが挿入される。

メソッドの実行

  • メソッドが呼び出されたときに、レシーバの参照を覚えておくことでメソッド実行時に誰がレシーバなのかを思い出せる。

selfキーワード

  • Rubyのコードは カレントオブジェクトself の内部で実行される。
  • メソッドを呼び出す時は、メソッドのレシーバがselfになる。
    • その時点から、全てのインスタンス変数はselfのインスタンス変数になる。
    • レシーバを明示しないメソッド呼び出しは全てselfに対する呼び出しになる。
    • 他のオブジェクトを明示してメソッドを呼び出すと、今度はそのオブジェクトがselfになる。

トップレベルコンテキスト

  • メソッドを呼び出していないとき、あるいは呼び出したメソッドが全て戻ってきた時の状態
self # => main
self.class # => Object

クラス定義とself

  • クラスやモジュールの定義の内側(メソッドの外側)では、selfの役割はクラスやモジュールそのものになる。
class MyClass
  self # => MyClass
end

privateキーワード

  • 明示的なレシーバをつけてprivateメソッドを呼び出すことはできない

    • privateメソッドは、暗黙的なレシーバ、selfに対するものでなければいけない。
  • privateのついたメソッドを呼び出すのは自分しかできない。

    • 例1
      • オブジェクトxは同じクラスのオブジェクトyのprivateメソッドを呼び出せない。
        • クラスが何であれ、他のオブジェクトのメソッドを呼び出すには、明示的にレシーバを指定する必要があるため。
    • 例2
      • スーパークラスから継承したprivateメソッドは呼び出せる。
        • 継承したメソッドは自分のところにあるので、呼び出す時に明示的にレシーバを指定する必要がないため。

Refinements

  • モンキーパッチとよく似ているが、変更がグローバルに及ばないようにする方法。
module StringExtensions
  refine String do
    def reverse
      "esrever"
    end
  end
end

module StringStuff
  using StringExtensions
  "my_string".reverse # => "esrever"
end

"my_string".reverse # => "gnirts_ym"
  • Refinementsが有効になるのは2箇所だけ。

    1. refineブロックそのものと、
    2. usingを呼び出した場所からモジュールの終わりまで(モジュール定義にいる場合)、またはファイルの終わりまで(トップレベルにいる場合)
  • Refinementsが有効になっている限定されたスコープの中では、Refinementsはオープンクラスやモンキーパッチと同じ。

    • 新しいメソッドを定義することができるし、既存のメソッドを再定義することもできるし、モジュールのincludeやprependもできる。
  • Refinementsが有効になっているコードは、リファインされた側のクラスのコードよりも優先される。

  • クラスをリファインするというのは、元のコードにパッチを貼り付けるようなもの。

2章まとめ

- オブジェクトは複数のインスタンス変数とクラスへのリンクで構成されている。
- オブジェクトのメソッドはオブジェクトのクラスに住んでいる(クラスから見れば、それはインスタンスメソッドと呼ばれる)。
- クラスはClassクラスのオブジェクトである。クラスメイは単なる定数である。
- ClassはModuleのサブクラスである。モジュールは基本的にはメソッドをまとめたものである。それに加えてクラスは、newでインスタンス化したり、superclassで階層構造を作ったりできる。
- 定数はファイルシステムのようなツリー上に配置されている。モジュールやクラスの名前がディレクトリ、通常の定数がファイルのようになっている。
- クラスはそれぞれBasicObjectまで続く継承チェーンを持っている。
- メソッドを呼び出すと、Rubyはレシーバのクラスに向かって一歩右へ進み、それから継承チェーンを上へ向かって進んでいく。メソッドを発見するか継承チェーンが終わるまでそれは続く。
- クラスにモジュールをインクルードすると、そのクラスの継承チェーンの真上にモジュールが挿入される。モジュールをプリペンどすると、そのクラスの継承チェーンの真下にモジュールが挿入される。
- メソッドを呼び出す時には、レシーバがselfになる。
- モジュール(あるいはクラス)を定義する時には、そのモジュールがselfになる。
- インスタンス変数は常にselfのインスタンス変数とみなされる。
- レシーバを明示的に指定せずにメソッドを呼び出すと、selfのメソッドだと見なされる。
- Refinementsはクラスのコードにパッチを当てて、通常のメソッド探索オーバーライドするようなものである。ただし、Refinementsはusingを呼び出したところから、ファイルやモジュールの定義が終わるところまでの限られた部分でのみ有効になる。

第3章 火曜日:メソッド

重複コードを排除する

  1. 動的メソッド
  2. method_missing

1. 動的メソッド

  • 「メソッドを呼び出すというのは、オブジェクトにメッセージを送っていることなんだ」

  • メソッドを呼び出すには、ドット記法か Object#send を使う。

obj = MyClass.new
obj.my_method(3) # => 6
obj.send(:my_method, 3) # => 6

動的ディスパッチ

  • send を使うことによって、呼び出したいメソッド名が通常の引数になり、コードの実行時に呼び出すメソッドを決められること。

  • Pry#refresh の例

def refresh(options={})
  defaults = {}
  attributes = [ :input, :output, :commands, :print, :quiet,
                 :exception_handler, :hooks, :custom_completions,
                 :prompt, :memory_size, :extra_sticky_locals ]

  attributes.each do |attribute|
    defaults[attribute] = Pry.send attribute
  end
  # ...
  defaults.merge!(options).each do |key, value|
    # memory_size= といったアトリビュートのアクセサを呼び出している。
    send("#{key}=", value) if respond_to?("#{key}=")
  end

  true
end
  • 動的ディスパッチを追加したコード例
class Computer
  def initialize(computer_id, data_source)
    @id = computer_id
    @data_source = data_source
  end

  def mouse
    component :mouse
  end

  def cpu
    component :cpu
  end

  def keyboard
    component :keyboard
  end

  def component(name)
    info = @data_source.send "get_#{name}_info", @id
    price = @data_source.send "get_#{name}_price", @id
    result = "#{name.capitalize}: #{info} ($#{price})"
    return "* #{result}" if price >= 100
    result
  end
end

メソッド名とシンボル

  • シンボルは文字列にある文字と違ってイミュータブル(変更不能)であるため、名前に適している。

動的メソッド

  • 実行時にメソッドを定義する技法。
    • 実行時にメソッド名を決定できる。
Module#define_method
class MyClass
  define_method :my_method do |my_arg|
    my_arg * 3
  end
end

obj = MyClass.new
obj.my_method(2) # => 6
  • define_methodを追加したコード例
class Computer
  def initialize(computer_id, data_source)
    @id = computer_id
    @data_source = data_source
  end

  def self.define_component(name)
    define_method(name) do
      info = @data_source.send "get_#{name}_info", @id
      price = @data_source.send "get_#{name}_price", @id
      result = "#{name.capitalize}: #{info} ($#{price})"
      return "* #{result}" if price >= 100
      result
    end
  end

  define_component :mouse
  define_component :cpu
  define_component :keyboard
end
  • data_sourceをインストロスペクションしてさらにリファクタリングした例
class Computer
  def initialize(computer_id, data_source)
    @id = computer_id
    @data_source = data_source
    # Array#grepにブロックを渡すと、正規表現にマッチした要素全てに対してブロックが評価される。
    # 正規表現のかっこにマッチした文字列は、グローバル変数$1に格納される。
    # 例: data_sourceがget_cpu_infoとget_mouse_infoの2つのメソッドを持っていれば、
    # Computer.define_componentを2回呼び出すことになる(それぞれ「cpu」と「mouse」の文字列を渡す)
    data_source.methods.grep(/^get_(.*)_info$/) { Computer.define_component $1 }
  end

  def self.define_component(name)
    define_method(name) do
      # ...
    end
  end
end

method_missing

  • メソッド探索をした時、メソッド が見つからなければ method_missing メソッドを呼び出して負けを認める。
    • method_missing は全てのオブジェクトが継承する BasicObjectprivate インスタンスメソッド。
nick.send :method_missing, :my_method
# => NoMethodError: undefined method `my_method` for #<Lawyer:0x007f801b0f4978>
class Lawyer
  def method_missing(method, *args)
    puts "呼び出した:#{method}(#{args.join(', ')})"
    puts "(ブロックも渡した)" if block_given? 
  end
end

bob = Lawyer.new
bob.talk_simple('a', 'b') do
  # ブロック
end
# => 呼び出した:talk_simple(a, b)
#  (ブロックも渡した)
  • BasiObject#method_missing の返答は、 NoMethodErrorになる。
    • 宛先不明なメッセージが最終的に行き着く場所であり、NoMethodErrorの誕生する場所。

method_missingのオーバーライド

ゴーストメソッド
  • method_missingで処理されるメッセージであり、呼び出し側からは通常の呼び出しのように見えるが、レシーバ側には対応するメソッドが見当たらない。

  • Hashieの例

module Hashie
  class Mash < Hashie::Hash
    def method_missing(method_name, *args, &blk)
      # 呼び出されたメソッドの名前がハッシュのキーであれば[]メソッドを呼び出して、対応する値を戻す。
      return self.[](method_name, &blk) if key?(method_name)
      # メソッドの名前の最後が「=」であれば、「=」を削除してアトリビュートの名前を取り出してから、その値を保持する。
      # どちらにも合致しなければ、method_missingはデフォルト値を返す。
      match = method_name.to_s.match(/(.*?)([?=!]?)$/)
      case match[2]
      when "="
        self[match[1]] = args.first
      else
        default(method_name, *args, &blk)
      end
    end
  end
end
動的プロキシ
  • メソッド呼び出しを method_missing に集中させゴーストメソッドを補足して、他のオブジェクトに転送するようなラップしたオブジェクトのこと。

  • Ghee(GitHubのHTTP APIに簡単にアクセスできるライブラリ)の例

class Ghee
  class ResourceProxy
    # ...

    def method_missing(message, *args, &block)
      # my_gist.urlのようなメソッド呼び出しを、Hashie::Mash#method_missingに転送する。
      subject.send(message, *args, &block)
    end

    def subject
      # GitHubオブジェクトをJSONで受け取って、Hashie::Mashに変換する。
      @subject ||= connection.get(path_prefix){|req| req.params.merge!params }.body
    end
  end
end
  • Gheeの2つのポイント

    1. GitHubのオブジェクトを動的なハッシュに保持している。ハッシュのアトリビュートは、 urldescription などのゴーストメソッドの呼び出しでアクセスできる。
    2. これらのハッシュをプロキシオブジェクトでラップしている。プロキシオブジェクトには、追加のメソッドが用意されている。プロキシは2つのことを行っている。
      1. star のような処理が必要なメソッドの実装。
      2. ラップしたハッシュに url などのデータを読み取るだけのメソッドを転送する。
  • Geeはこうした2段階の設計によって、コードを簡潔に保っている。

    • データを読み取る時にはゴーストメソッドがあるので、メソッドを定義する必要がない。
    • star のような特定のコードが必要な時だけメソッドを定義する。
  • これらの動的な手法のもう一つの利点は、GitHub APIの変更に自動的に対応できること。

    • GitHubが新しいフィールドを追加したとしても、それもゴーストメソッドとなるため、Gheeはソースコードに修正を加えることなく、呼び出しをサポートできる。
  • method_missingを利用したComputerクラスのリファクタリング

class Computer
  def initialize(computer_id, data_source)
    @id = computer_id
    @data_source = data_source
  end

  # BasicObject#method_missingをオーバーライド
  def method_missing(name)
    super if !@data_source.respond_to?("get_#{name}_info")
    info = @data_source.send("get_#{name}_info", @id)
    price = @data_source.send("get_#{name}_price", @id)
    result = "#{name.capitalize}: #{info} ($#{price})"
    return "* #{result}" if price >= 100
    result
  end

  # ゴーストメソッドがあるかどうかを確認するrespond_to_missing?を、適切にオーバーライドする。
  def respond_to_missing?(method, include_private = false)
    @data_source.respond_to?("get_#{method}_info") || super
  end
end
Module#const_missing
  • 新しいクラスメイトネームスペースのない古いクラスメイを併用できるようにするメソッド。

  • TaskがアップグレードによりRake::Taskという名前に変更された場合の例

class Module
  def const_missing(const_name)
    case const_name
    when :Task
      # 廃止されたクラス名を使っていることに対する警告を出す。
      Rake.application.const_warning(const_name)
      Rake::Task
    when :FileTask
      Rake.application.const_warning(const_name)
      Rake::FileTask
    when :FileCreationTask
      # ...
    end
  end
end

バグのあるmethod_missing

  • 変数 number がブロックの中で定義されていて、 method_missing の最終行でスコープを外れており、最終行の実行時に、Rubyは number が変数だとは気づかず、 self に対するかっこのないメソッド呼び出しだと思ってしまう。
    • 通常であれば、明示的にNoMethodErrorが発生して問題が明らかになるが、ここでは、 method_missing を自分で定義していて、その中で number を呼び出しているため、同じイベントチェーンが何度も繰り返し起き、最終的にはスタックオーバーフローが発生してしまう。
class Roulette
  def method_missing(name, *args)
    person = name.to_s.capitalize
    3.times do
      number = rand(10) + 1
      puts "#{number}..."
    end
    "#{person} got a #{number}"
  end
end
  • 必要もないのにゴーストメソッドを導入しない。
    • 通常のメソッドを書くところから始めて、コードが動いていることを確認してから、method_missingを使うようにリファクタリングする。

ブランクスレート

  • ゴーストメソッド(method_missingで定義されたメソッド)の名前と継承した本物のメソッドの名前が衝突すると、後者が勝ってしまう。

    • 継承したメソッドを削除しておき、最小限のメソッドしかない状態のクラスをブランクスレートと呼ぶ。
  • Rubyのクラス階層のルートであるBasicObjectには、必要最低限のメソッドしか存在せず、Rubyでブランクスレートを手っ取り早く定義するには、BasicObjectを継承すれば良い。

3章まとめ

- ゴーストメソッドは基本的な忠告(常にsuperを呼び出す、常にrespond_to_missing?を再定義する)に従えば、ほとんどの問題は回避できるが、それでも複雑なバグを引き起こす。
- ゴーストメソッドは本物のメソッドではないため、実際のメソッドとは振る舞いが異なる。
- メソッド呼び出しが大量にあったり、呼び出すメソッドが実行時にしかわからなかったりする時など、ゴーストメソッドしか使えない場面もある。
- 「可能であれば動的メソッドを使い、仕方がなければゴーストメソッドを使う」

第4章 水曜日:ブロック

  • ブロックは「呼び出し可能オブジェクト」大家族の一員。
  • ブロックの家族はオブジェクト指向とは血筋が違っていて、LISPなどの「関数型プログラミング言語」の流れをくんでいる。

ブロックの基本

  • ブロックを定義できるのはメソッドを呼び出す時だけ。
    • ブロックはメソッドに渡され、メソッドはyieldキーワードを使ってブロックをコールバックする。
    • ブロックをコールバックするときは、メソッドと同じように引数を渡せる。
    • ブロックは、メソッドと同じように最終行を評価した結果を戻す。
  • メソッドの内部では、 Kernel#block_given? メソッドを使ってブロックの有無を確認できる。

ブロックはクロージャ

  • ブロックのコードを実行するには、ローカル変数、インスタンス変数、selfといった環境が必要になり、これらをまとめてから実行の準備をする。
  • ブロックには、コードと束縛の集まりの両方が含まれる

ブロックと束縛

  • ブロックを定義すると、その時点でその場所にある束縛を取得する。
    • ブロックをメソッドに渡したときは、その束縛も一緒に連れて行く。
def my_method
  # メソッドにある束縛はブロックからは見えない。
  x = "Goodbye"
  yield("cruel")
end

x = "Hello"
# xのようなローカル束縛を包み込み、それからブロックをメソッドに渡す。
my_method = {|y| "#{x}, #{y} world"} # => "Hello, cruel world"

スコープ

  • JavaやC#といった言語には、「内部スコープ」から「外部スコープ」の変数を参照する仕組みがあるが、Rubyにはこうした可視性の入れ子構造は存在せず、スコープはきちんと区別されている。
  • 新しいスコープに入ると(プログラムがスコープを変えると)、以前の束縛は新しい束縛と置き換えられる

グローバル変数とトップレベルのインスタンス変数

# グローバル変数はどのスコープからもアクセスできる。
def a_scope
  $var = "some value"
end

def another_scope
  $var
end

a_scope
another_scope # => "some value"
# トップレベルのインスタンス変数は、トップレベルにあるmainオブジェクトのインスタンス変数であり、
# mainがselfになる場所であればどこからでも呼び出せる。
# 他のオブジェクトがselfになれば、トップレベルのインスタンス変数はスコープから外れる。
@var = "トップレベルの変数@var"

def my_method
  @var
end

my_method # => "トップレベルの変数@var"

class MyClass
  def my_method
    @var = "トップレベルの変数@varではない!"
  end
end

スコープゲート

  • プログラムがスコープを切り替えて、新しいスコープをオープンする場所は3つあり、それぞれを印づけるキーワードをスコープゲートと言う。
    1. クラス定義(class)
    2. モジュール定義(module)
    3. メソッド(def)

スコープのフラット化

  • スコープゲートを超えて束縛を渡し、他のスコープの変数が見えるようにする方法。
    • この魔術を、入れ子構造のレキシカルスコープとも呼ぶ。
    • 2つのスコープを一緒の場所に押し込めて、変数を共有する魔術をフラットスコープと呼ぶ。
my_var = "成功"

MyClass = Class.new do  # class MyClass
  puts "クラス定義のなかは#{my_var}!"

  define_method :my_method do  # def my_method
    "メソッド定義の中も#{my_var}!"
  end
end

共有スコープ

  • 変数をスコープゲートで守りつつ、変数を共有する方法。
def define_methods
  shared = 0

  Kernel.send :define_method, :counter do
    shared
  end

  Kernel.send :define_method, :inc do |x|
    shared += x
  end
end

define_methods

counter # => 0
inc(4)
counter # => 4

クロージャのまとめ

- Rubyのスコープには多くの束縛がある。
    - スコープはclass、module、defといったスコープゲートで区切られている。
- スコープゲートを飛び越えて、束縛にこっそり潜り込みたい時には、ブロック(クロージャ)が使える。
    - ブロックを定義すると、現在の環境にある束縛を包み込んで、持ち運ぶことができる。
        - スコープゲートをメソッド呼び出しで置き換え、現在の束縛をクロージャで包み、そのクロージャをメソッドに渡す。
- classはClass.new、moduleはModule.newと、defはModule.define_methodと置き換えることが可能。
    - これがフラットスコープであり、クロージャに関する基本的な魔術。
- 同じフラットスコープに複数のメソッドを定義して、スコープゲートで守ってやれば、束縛を共有できる。
    - これは、共有スコープと呼ばれる。

instance_eval

  • BasicObject#instance_eval は、オブジェクトのコンテキストでブロックを評価する
  • instance_eval に渡したブロックのことをコンテキスト探索機と呼ぶ。
    • オブジェクトの内部を探索して、そこで何かを実行するコードだから。
class MyClass
  def initialize
    @v = 1
  end
end

obj = MyClass.new

# instance_evalに渡したブロックは、レシーバをselfにしてから評価されるので、
# レシーバのprivateメソッドや@vなどのインスタンス変数にもアクセスできる。
obj.instance_eval do
  self  # => #<MyClass:0x3340dc @v=1>
  @v    # => 1
end

instance_evalとinstance_execの違い

class C
  def initialize
    @x = 1
  end
end

# instance_eval
class D
  def twisted_method
    @y = 2
    # instance_evalがselfをレシーバに変更すると、
    # 呼び出し側(D)のインスタンス変数はブロックから抜け落ちてしまう。
    C.new.instance_eval { "@x: #{@x}, @y: #{@y}" }
  end
end

D.new.twisted_method # => "@x: 1, @y: "

# instance_exec
class D
  def twisted_method
    @y = 2
    # Cのインスタンス変数とDのインスタンス変数を同じスコープに入れることができる。
    C.new.instance_exec(@y) {|y| "#{@x}, @y: #{y}" }
  end
end
D.mew.twisted_method # => "@x: 1, @y: 2"

カプセル化の破壊について

  • コンテキスト探索機(instance_evalに渡したブロック)を使うと、カプセル化を破壊できてしまうため、以下のような場合にユースケースが限られる。

    • irbからオブジェクトの中身を見たい場合
    • テストの場合
  • Padrinoのテストの例

describe "PadrinoLogger" do
  context 'for logger functionality' do
    context "static asset logging" do
      ...
      should 'allow turning on static assets logging' do
        Padrino.logger.instance_eval { @log_static = true } 
        # ...
        get "/images/something.png"
        assert_equal "Foo", body
        assert_match /GET/, Padrino.logger.log.string
        Padrino.logger.instance_eval { @log_static = false }
      end
    end
  end
end

クリーンルーム

  • ブロックを評価するためだけにオブジェクトを生成すること。ブロックを評価する環境。

ブロック以外でコードを保管できるところ

  • 呼び出し可能オブジェクト
    • Rubyで、「コードを保管しておいて、あとで呼び出す」方式
      • Procのなか。これはブロックがオブジェクトのになったもの。
      • lambdaのなか。これはProcの変形。
      • メソッドのなか。

Procオブジェクト

  • Rubyではほぼ全てがオブジェクトだが、ブロックは違う。
  • Proc
    • ブロックをオブジェクトにしたもの。
    • Procを生成するには、Proc.newにブロックを渡す。
      • オブジェクトになったブロックをあとで評価(遅延評価)するには、 Proc#call を呼び出す。
inc = Proc.new {|x| x + 1 }
inc.call(2) # => 3

ブロックをProcに変換する

  • ブロックをProcに変換する2つのカーネルメソッド
    • lambda
    • proc
dec = lambda {|x| x - 1 }
# dec = ->(x) { x - 1 }
dec.class # => Proc
dec.call(2) # => 1

&修飾

  • ブロックはメソッドに渡す無名引数のようなもの。
  • yieldでは足りないケース。

    • 他のメソッドにブロックを渡したいとき
    • ブロックをProcに変換したいとき
  • ブロックに束縛を割り当てる(ブロックを指し示す「名前」をつける) &

    • メソッドの引数列の最後に置いて、名前の前に & の印をつける。
    • & を付けると、メソッドに渡されたブロックを受け取って、それをProcに変換したい、という意味になる。
  • ブロックをProcに変換する例

def math(a, b)
  yield(a, b)
end

# &修飾を行うと、メソッドに渡されたブロックを受け取って、それをProcに変換する!
def do_math(a, b, &operation) 
  math(a, b, &operation)
end

do_math(2, 3) {|x, y| x * y } # => 6
def my_method(&the_proc)
  the_proc
end

p = my_method {|name| "Hello, #{name}!" }
p.class # => "Proc"
p.call("Bill") # => "Hello, Bill!"
  • Procをブロックに戻す例
    • Procをブロックに変換する &
def my_method(greeting)
  "#{greeting}, #{yield}"
end

my_proc = proc { "Bill" }
my_method("Hello", &my_proc)
  • &によるProc変換は「Proc coercion」(coercion: 強制、強要など)と呼ぶ。
    • &の、一つの演算子が与える側と受け取る側の両方で使えて,ちょうど逆の働きをする,という点は多重代入における * とよく似ている。

lambda

  • lambdaで作ったProcは、他のProcとは違う。

    • lambdaで作られたProcオブジェクトはlambdaと呼ばれる。
    • もう一方は、単純にProcと呼ばれる。
      • Procがlambdaかどうかは、Proc#lambda?メソッドで確認できる。
  • Procとlambdaの2つの違い

    1. returnキーワードに関すること
    2. 引数のチェックに関すること

Procとlambdaとreturn

  • lambdaとProcでは、returnキーワードの意味が違う。
# lambdaの場合は、returnは単にlambdaから戻るだけ。
def double(callable_object)
  callable_object.call * 2
end

1 = lambda { return 10 }
double(1) # => 20

# Procの場合は、Procが定義されたスコープから戻る。
def another_double
  p = Proc.new { return 10 }
  result = p.call
  return result * 2 # ここまでは来ない!
end

another_double # => 10

def double(callable_object)
  callable_object.call * 2
end

p = Proc.new { return 10 }
double(p)  # => LocalJumpError

p = Proc.new { 10 }
double(p) # => 20

Procとlambdaと項数

  • 一般的にlambdaの方がProc(や普通のブロック)よりも引数の扱いに厳しい
    • 違った項数でlambdaを呼び出すと、ArgumentErrorになる。
    • 一方、Procは引数列を期待に合わせてくれる。
p = Proc.new
p.call(1, 2, 3) # => [1, 2]
p.call(1)  # => [1, nil]

Proc vs lambda

  • 一般的に言えば、lambdaの方がメソッドに似ているので、Procよりも直感的であると言われている。
  • また、項数に厳しく、returnを呼ぶと単に終了してくれる。
  • Procの機能が必要でない限り、Rubyistの多くは最初にlambdaを選んでいる。

Methodオブジェクト

  • メソッドもlambdaのような呼び出し可能オブジェクトになる。
class MyClass
  def initialize(value)
    @x = value
  end

  def my_method
    @x
  end
end

object = MyClass.new(1)
m = object.method :my_method  # メソッドそのものをMethodオブジェクトとして取得できる
m.call  # => 1  # Object#methodで取得したオブジェクトをMethod#callを使って実行できる
  • MethodMethod#to_procProc に変換できるし、ブロックは define_method でメソッドに変換できる。

  • lambdaは定義されたスコープで評価される(クロージャである)が、Methodは所属するオブジェクトのスコープで評価される。

UnboundMethod

  • 元のクラスやモジュールから引き離されたメソッドのようなもの。
module MyModule
  def my_method
    42
  end
end

unbound = MyModule.instance_method(:my_method)
unbound.class  # => UnboundMethod

呼び出し可能オブジェクトのまとめ

- ブロック(「オブジェクト」ではないが「呼び出し可能」):定義されたスコープで評価される
- Proc:Procクラスのオブジェクト。ブロックのように、定義されたスコープで評価される。
- lambda:これもProcクラスのオブジェクトだが、通常のProcとは微妙に異なる。ブロックやProcと同じくクロージャであり、定義されたスコープで評価される。
- メソッド:オブジェクトに束縛され、オブジェクトのスコープで評価される。オブジェクトのスコープから引き離し、他のオブジェクトに束縛することもできる。

- メソッドとlambdaでは、returnで呼び出し可能オブジェクトから戻る。
    - 一方、Procとブロックでは、呼び出し可能オブジェクトの元のコンテキストから戻る。
- メソッドは呼び出し時の項数の違いに対して厳密であり、lambdaもほぼ厳密である。
    - Procとブロックは寛容である。
- 上記のような違いは、 `Proc.new` 、 `Method#to_proc` 、 `&修飾` などを使って、
ある呼び出し可能オブジェクトから別の呼び出し可能オブジェクトに変換することができる。

ドメイン特化言語

  • 例1
def event(description)
  puts "ALERT: #{description}" if yield
end
loda 'events.rb'

# events.rb
event '常に発生するイベント' do
  true
end

event '絶対に発生しないイベント' do
  false
end

# => ALERT: 常に発生するイベント
  • 例2
def setup(&block)
  @setups << block
end

def event(description, &block)
  @events << {:description => description, :condition => block }
end

@setups = []
@events = []
load 'events.rb'

@events.each do |event|
  @setups.each do |setup|
    setup.call
  end
  puts "ALERT: #{event[:description}" if event[:condition].call
end

グローバル変数の削除

  • 例2・改
lambda {
  # setupsとeventsはlambadのローカル変数のため、外部から見えない
  setups = []
  events = []

  Kernel.send :define_method, :setup do |&block|
    setups << block
  end

  Kernel.send :define_method, :event do |description, &block|
    events << {:description => description, :condition => block}
  end

  Kernel.send :define_method, :each_setup do |&block|
    setups.each do |setup|
      block.call event
    end
  end

  Kernel.send :define_method, :each_event do |&block|
    events.each do |event|
      block.call event
    end
  end
}.call

each_event do |event|
  each_setup do |setup|
    setup.call
  end
  puts "ALERT: #{event[:description]}" if event[:condition].call
end

# あるいはObjectのコンテキストにおけるクリーンルームを使って、event同士でインスタンス変数を共有させないようにできる。

each_event do |event|
  env = Object.new
  each_setup do |setup|
    env.instance_eval &setup
  end
  puts "ALERT: #{event[:description]}" if env.instance_eval &(event[:condition])
end

4章まとめ

- スコープゲートと、Rubyはどのようにスコープを管理しているのか。
- フラットスコープと共有スコープを追加、スコープを横断して束縛を見えるようにする方法。
- オブジェクトのスコープで(instance_evalやinstance_execを使って)コードを実行する方法や、クリーンルームでコードを実行する方法。
- ブロックとオブジェクト(Proc)を相互に変換する方法。
- メソッドとオブジェクト(MethodやUnboundMethod)を相互に変換する方法。
- 呼び出し可能オブジェクト(ブロック、Proc、Lambda)の種類とその違い。通常のメソッドとの違い。
- 独自の小さなDSLの書き方。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む