- 投稿日:2020-11-14T23:59:57+09:00
安全な数字(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" end1行目のcharsメソッドで例1で例えると["2" "0" "2" "0"]のように1文字づつ分割している
2行目でa.countの返り値4からa.uniq.countの返り値2を引いた数が0より大きければ"NG"を出力するというもの。
uniqメソッド
配列の要素の中で重複している要素を削除して削除後の配列として返すメソッド
countメソッド
- 文字列の特定の文字の出現回数を数えるための機能(「こんばんは」という文字に「ん」は2回みたいに数える機能)
- 配列の要素の数を数えるための機能
以上!
- 投稿日:2020-11-14T21:39:19+09:00
Ralisのポートフォリオでcollection_check_boxesメソッドを使ったので復習
はじめに
user.rb
has_many :habits, dependent: :destroyhabit.rb
belongs_to :useruserモデルが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: []) end29: 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"]一応うまくいったけどもっといいやり方あるんだろうな
- 投稿日:2020-11-14T21:21:32+09:00
Rails 6でjQueryとBootstrapを使えるようにする(Rails 6)
Ruby on Railsでアプリケーションを作る際に,jQueryとBootstrapを使えるようにしたかったので手順をメモ。(Webpackerを使用。)
1.yarnでjQuery、Bootstrap、popper.js(Bootstrapが使う)を導入。
アプリケーションのディレクトリ配下で以下を実行。
yarn add jquery bootstrap popper.js2.webpackの設定
app/config/webpackのenvironment.jsの記述を追加する。
(これによりimportやrequireなしで$やBootstrapのJavascriptが使えるようにする。)environment.jsconst { environment } = require('@rails/webpacker'); //ここから const webpack = require('webpack'); environment.plugins.prepend( 'Provide', new webpack.ProvidePlugin({ $: 'jquery', jQuery: 'jquery', Popper: 'popper.js' }) ); //ここまで追加。 module.exports = environment3.BootstrapのJS,CSSをインポートする
app/javascript/packsのapplication.jsでBootstrapのJSをimport。
app/javascript/stylesheetsにapplication.scssを作成し、BootstrapのCSSをimport。application.jsimport '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' %>これで完了。
- 投稿日:2020-11-14T21:07:23+09:00
jQueryでタブをマウスオーバー した際に表示画面を切り替える
はじめに
今回はjQueryを使って、tabをマウスオーバーした際にページリロードを行わずに表示を切り替える方法を記述していきます。
完成イメージ
環境
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;以上となります。
②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); });以上となります。
終わり
ご覧いただきありがとうございました。
- 投稿日:2020-11-14T20:33:49+09:00
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 1SQL?
いじった覚えがない。。結局リセットしたいわけだから、今回はそんなの関係ない!無視し、自分のリセット方法に誤りがあると仮説を立て、再度リセット方法を調べ直した。行ったこと
データベースのリセットに関しては、以下のコマンドを実行することを知ったため、それで実行した。
% 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
- 投稿日:2020-11-14T19:21:22+09:00
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を利用する方法で良い方法があれば教えて下さい )application_controller.rb (APIの基底のcontroller)
class ApplicationController < ActionController::API include ActionController::Cookies #<- これを追加ActionController::API を継承して作る場合はこの設定も必要だった。
クライアント側
axios(ajaxライブラリ)
axios.create({ withCredentials: true, //... })axios違うドメインCookieを送る機能だが、これ有効しないとsession cookieが使えなかった。
- 投稿日:2020-11-14T19:16:36+09:00
テンプレート: 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_namebundle init を実行して Gemfile ファイルを作成
$ bundle initGemfile を編集する(以下ではnanoエディタを使用)
$ nano GemfileGemfile内の # gem "rails" のコメントを外す
(# gem "rails" を gem "rails" に変更する)bundle install により Rails 関連の Gem を vendor/bundle にインストールする
$ bundle config set path 'vendor/bundle' $ bundle installrails 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! が表示されたら成功です
- 投稿日:2020-11-14T19:13:31+09:00
rails6 form_with(ヘルパー)のラジオボタンにCSSを装飾する方法
目的:rails6にてform_withでラジオボタンを実装、それに対してCSSで装飾を行います。
苦戦したこと:ヘルパーを使っていることにより、ラジオボタンの内部構造が見えなくなってしまった。
①他のオプションと違いラジオボタンはclassをセレクタにしてもCSSを反映する事ができない。
②label forが設定されていない事により、選択作業が反映できないバグが発生。以下、内容を記載。
①ラジオボタンはclassセレクタではCSSが反映されない。
CSS
.second{
font-size: 14px;
font-weight: bold;
display: block; /* ブロックレベル要素化する /
float: left; / 要素の左寄せ・回り込を指定する */
.
.
}このような記述では全くCSSが反映されません。検証ツールで見てみると、
一見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; /* ラジオボタンを非表示にする */
}の記述を加えます。これで外見上は装飾されたラジオボタンのでき上がりです。
しかし、実際に入力作業をしてみると問題発生。
ボタンをクリックしたら変色する仕様を以下のように記述したはずが全く動作しません。input[type="radio"]:checked + label{
background:blue;/* マウス選択時の背景色を指定する /
color: #ffffff; / マウス選択時のフォント色を指定する */
}これはtype="radio"属性(labelの要素まで含めて)がクリックされたら指定の動きをする記述です。
再び検証ツールで確認。一見問題が無いように見えます。ちゃんと選択肢事にid、valueも違う値が登録されています。
②label for属性の指定がないことによるlabel要素の読み込みエラー
この原因を特定するのにかなり苦労しましたが、ヘルパーを使わない場合のlabel設定の仕方を確認し、わかりました。
idに対して、label forが設定されていない事で、それぞれの選択肢がうまくVIEWに反映されていなかった。意味わかんないですね、とりあえずidとlabel forが一致して初めてラジオボタンのCSS設定が反映されるという事です。なので、label forにidと同じ値を設定します。
//
これが完成形です。
label for="id" 選択肢 /label
の書き方はこの指定となりますので、順番も変えました。このように動作しました。ちなみに検証ツールで確認すると、
このようにlabel forが定義されており、idと紐づいている状態です。
※ちなみに、別箇所でcollection_selectでラジオボタンを作っていた箇所は選択肢事の全てのlabel forが同じ値になっており、どれを選択しても最初の選択肢が選択されてしまうというエラーも発生。
railsのフォームヘルパーでラジオボタンを作成する場合はlabel forの設定が必須になると思われますので、注意が必要です。
- 投稿日:2020-11-14T17:58:56+09:00
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 stop3.本番環境
本番環境において、Webサーバーとアプリケーションサーバーを分業化する。理由は先述した通り、アクセスが集中する等のサーバー負荷対策のためである。Nginx(Webサーバー)やUnicorn(アプリケーションサーバー)等を組み合わせ、本番環境を構築する。
参考ページ
なぜrailsの本番環境ではUnicorn,Nginxを使うのか? ~ Rack,Unicorn,Nginxの連携について ~【Ruby On Railsでwebサービス運営】以上
- 投稿日:2020-11-14T17:50:36+09:00
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_namebundle init を実行して Gemfile ファイルを作成
$ bundle initGemfile を編集する(以下ではnanoエディタを使用)
$ nano GemfileGemfile内の # gem "rails" のコメントを外す
(# gem "rails" を gem "rails" に変更する)bundle install により Rails 関連の Gem を vendor/bundle にインストールする
$ bundle config set path 'vendor/bundle' $ bundle installrails 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! が表示されたら成功です
- 投稿日:2020-11-14T17:50:36+09:00
テンプレート: 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_namebundle init を実行して Gemfile ファイルを作成
$ bundle initGemfile を編集する(以下ではnanoエディタを使用)
$ nano GemfileGemfile内の # gem "rails" のコメントを外す
(# gem "rails" を gem "rails" に変更する)bundle install により Rails 関連の Gem を vendor/bundle にインストールする
$ bundle config set path 'vendor/bundle' $ bundle installrails 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! が表示されたら成功です
- 投稿日:2020-11-14T17:28:47+09:00
【初投稿】売れた商品に対して、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と見比べるのではないかと思い付きまずさかのぼって考え、答えに至りました。
プログラミングで目的の動作を考える時は、抽象化された目的に対し細かくプロセスを考えることが大切だと分かりました。
- 投稿日:2020-11-14T17:26:35+09:00
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
- 投稿日:2020-11-14T17:02:12+09:00
whereを用いたparamsの例
こちら完全に備忘録です。。。
見苦しいですが一度投稿させてください。
後で修正いたします。アソシエーション
user.rbhas_many :hoges has_many :hoge_eventshoge.rbbelongs_to :user has_many :hoge_eventshoge_events.rbbelongs_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イベントごとの詳細を画面に表示できた。
- 投稿日:2020-11-14T15:14:10+09:00
【Rails】Bootstrapのtext-lightをlink_toメソッドに継承できないのは、メソッドに引数としてクラスを書いていないのが原因らしい
はじめに
BootstrapをRails環境に導入し、きれいにしていくぞ~って思ったらいきなり躓いたので共有します。
事象
どうしてもlink_toメソッドの要素に継承されない
HTMLで記載したときはテキストに白色が適用されている
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クラスを設定してるので、文字色が白になるはずだが、青になっている。
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>結果、、、できた。
- 投稿日:2020-11-14T14:22:19+09:00
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 endchange_columnでbooleanからinteger型に変更したんですが、complteカラムにdefaultでfalseが入ってるままintegerに変更しようとしたからエラーが発生。
なのでcomplteカラムのfalseとなってるデータを削除してrails db:migrate:reset
これで無事解決
- 投稿日:2020-11-14T13:13:39+09:00
よく理解していないまま使用していたもの一覧: Ruby編
はじめに
この記事は私が勉強をしている中で「よくわからないまま用いていたもの」をまとめることで、私自身の理解を深めることが目的です。以下に一覧として記載いたします。
Ruby編
①文字コードの指定
Rubyなどのプログラムファイルにおいて、以下のようなコードを冒頭に書くことで文字コードを指定する場合があります。
sample1.rb# -*- encoding: utf-8 -*-
これはマジックコメントと呼ばれるものです。私は毎回必要だと思って、挿入していました。しかし、Ruby2.0以降ではデフォルトでUTF-8が入っているため、現在のRubyではわざわざUTF-8を指定する必要がないのです。
私が毎回マジックコメントを書いていた原因としては、Ruby2.0より前のものを使用していたコードを参考にしていたからだと考えられます。
②rubyjemsについて
Rubyのコードにおいて、たまにこのような行を見かけると思います。
sample2.rbrequire "rubyjems"そもそもrubyjemsとは、Rubyにおいてライブラリの作成や公開、インストールを助けるシステムです。以前のRubyでは、gemとしてインストールされるモジュールを使うプログラムを書く場合、まずrubygemsに対してrequireを実行する必要がありました。しかしRuby1.9以降、rubygemsモジュールが標準ライブラリの一部となったため、わざわざrubyjemsをrequireする必要はなくなったのです。
私がこれを書いていた原因も、Ruby1.8以前のものを使用していたコードを参考にしていたからだと考えられます。
まとめと感想
勉強のためにさまざまなコードを参考にする中で、実際は不要なのに使用していたものがあったことに気づきました。
言語のアップデートが理由で不要になったものがある、ということも学んだので、今後は載っているコードで使われている言語のバージョンなども意識して見ていこうと思います。
- 投稿日:2020-11-14T13:12:36+09:00
Railsのルーティングの記述方法
概要
Railsでルーティングを設定する際、私が勉強したことを復習の意味も込めてまとめてみます。
ルーティングとは
Railsにおけるルーティングとは、リクエストされたURLからコントローラとアクションを選択することです。
ルーティングの設定方法
config/routes.rbファイル内にて設定を記述し、その内容がルーティングの設定となります。
config/routes.rbRails.application.routes.draw do #ここにルーティングを記載 endルーティングの記述方法
基本は「メソッド "パス" => "コントローラ#アクション"」にて記載する。
config/routes.rbRails.application.routes.draw do #例 get "about" => "top#about" post "login" => "sessions#login endコントローラ#アクションがパスと同じ場合、省略が可能である
config/routes.rbRails.application.routes.draw do #例 get "info/room" endasオプションを付ければルーティングに名前を付けることができる。"指定した名前_path"をメソッドとして呼び出すと"/指定した名前"で文字列を返す。
config/routes.rbRails.application.routes.draw do #例 get "help" => "document#help", as: "help" end":パラメータ"と記載すると、パラメータをパスとして使用することができる。
config/routes.rbRails.application.routes.draw do #例 get "posts/:year/:month" => "posts#show endリソースベースのルーティングの記述方法
リソースベースでルーティングを設定するにはresourcesメソッドを記載し、リソース名を複数形にします。
また、単数リソースでルーティングを設定するにはresouceメソッドを記載し、リソース名を単数形にします。
Railsのリソースベースとは、RESTに基づきコントローラで扱う対象に名前をつけたものです。config/routes.rbRails.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" endresourcesメソッドで作ったルーティングに追加でアクションを追加する場合は、ブロックで囲み、その中で新たにメソッド、アクションを追加で記述します。
追加のアクションが集合を表す場合は、"on: :collection"、id属性を追加する場合は、"on: :member"を設定します。config/routes.rbRails.application.routes.draw do #例 resources :users do get "search", on: :collection patch "suspend", "restore", on: :member end endresourcesメソッドで作ったアクションの内、必要のないアクションはonlyオプションやexceptアクションを渡し、アクションを設定します。
config/routes.rbRails.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 %>まとめ
ルーティングとパスについて改めて理解を深めました。
ルーティングやパスは簡略化した状態でプログラムを書くことが多いので、本来の形を忘れがちになってしまうので、今回復習できてよかったです。
まだまだ細かく調べるとルーティングの設定方法はあると思うので、必要に応じて追加でまとめていきたいです。
- 投稿日:2020-11-14T09:36:58+09:00
【Ruby/Rails】Herokuでアプリケーションを公開する手順
はじめに
前提
個人的な備忘録
ruby 2.6.5 / Rails 6.0.3.4 / Mysql2手順
rails_12factor インストール
Gemfilegroup :production do gem 'rails_12factor' endbundle install => master にcommit
#追記
こちらのGemのインストールはRails5以降では不要のようです。
すでにメンテナンスがされていないと思われるGemをインストールしておくのは好ましくないので、不要なGemはアンインストールしておいた方がよさそうです。参考URL
【初心者必見】RailsアプリをHerokuに公開するのにrails_12factorは不要Herokuにアプリを作成
ターミナル% heroku create アプリ名アプリ名は _(アンダーバー)使えない
Herokuにアプリができているか確認
ターミナル% git config --list | grep herokuMysqlの設定
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 configHerokuにアプリを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の設定が必要かも
✔︎
- 投稿日:2020-11-14T09:20:42+09:00
[Rails]マイグレーションファイルについて勉強してみた!(テーブルへのカラム追加)
はじめに
Railsを操作していると、何となく存在は知っているけど、それがどういう役割を果たしているのかよく分かっていないファイルやコマンドが数多くあることに気づきました。
その中の一つが、マイグレーションファイルです。モデルを作ると、なぜか知らんがマイグレーションファイルが出来上がっていて、
rails db:migrate
を打ち込むと、schema.rb
にテーブルデータとして反映する...
それぐらいの認識でしたが、そこについて深掘りして解説していきます!マイグレーションファイルとは
マイグレーションファイルは、データベースを生成する際の設計図になるものです。
また、マイグレーションファイルを実行することで、記述した内容に基づいたデータテーブルが生成されます。マイグレーションファイルを生成する
ターミナルに、以下のように打ち込み、モデルを作成すると、自動的にマイグレーションファイルも生成されます。
基本構文rails g model モデル名 #ここでは、モデル名を「book」としますdb/migrate/20201114044025_create_books.rbclass 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 booksupになっているマイグレーションファイルはすでに実行済みのファイルなので
rails db:migrate
コマンドを入力しても読み込まれることはありません。
なのでもし間違った名前でカラムを作成してしまったときにup
になっているマイグレーションファイルを編集しても、読み込まれないので意味がないことになります。なるほど、それでは、今作成したばかりのbooksテーブルに、新たにカラムを追加したいときは、どうすればいいのでしょうか?
ひとまず、スキーマファイルを確認しにいきましょう!スキーマファイルとは
マイグレーションが実行されるとdbフォルダに
schema.rb
というファイルが作成されます。
早速中身をみましょう。db/schema.rbActiveRecord::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.rbclass CreateGenres < ActiveRecord::Migration[5.2] def change create_table :books do |t| t.string :title #ここにtitleカラムを追加!!!! t.timestamps end end endあとは①のパターンと同様に、マイグレーションファイルを実行すれば、
title
カラムが追加されます。コマンドrails db:migratedb/schema.rbActiveRecord::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おわりに
マイグレーションファイルの修正を行う作業は、思っている以上にセンシティブになることが多いので、タイミングや順序などをしっかりと理解した上で取り組むべきだと再認識できました。
また一つ勉強になりました!!
- 投稿日:2020-11-14T06:10:00+09:00
Rails / Ruby : Mail の HTML テキスト を取得する方法
Rails で、 ActionMailer::Base を継承した Mailer クラス では
その返り値のメールオブジェクトのメソッド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 endPlain Text の本文を取得
Plain Text で取得する場合は
text_part
が使えます。mail.text_part.body.to_s
.body
までだと Body class のインスタンスが返ります。関連
この記事では ActionMailer の mail メソッド の返り値から 本文を取得する方法について書いています。 送ったあとで本文を取得する場合は下記の記事が参考になります。
- 投稿日:2020-11-14T05:04:24+09:00
既存のRailsアプリにVuetifyを導入した
はやさを求めるなら
Vue.jsが無事にできるようになったので、Vuetifyも、という手筈になった。Vuetifyとは素敵なコンポーネントがつまったフレームワークである。いちから作らなくてもボタンとかスライドとか、いちいちかっこいいやつがある。かっこいい。
早速ドキュメントをみながら導入を試みた。しんどかった。全部英語だった。
https://vuetifyjs.com/en/getting-started/installation/
無からインストール
はじめは既存のものに導入とか考えずに、無からやってみた。まずはVueのCLIというやつが必要らしい。公式を参考に入れよう。
$ yarn global add @vue/cliそして
vue
コマンドでつくる。$ vue create my-app数多くのファイル群ができるが、そこで以下のコマンドを入力する。
$ yarn serveこの
yarn serve
はpackage.jsonで定義されたオリジナルのスクリプト。
"serve": "vue-cli-service serve",
となっている。実行すると以下のようにハローワールドができる。やったぜ。
こんなページが表示された。成功だ。
既存のあれに入れる
既存のアプリに入れ込むやり方だが、ここが参考になった。というよりほぼこの記事。ここの「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' } } })そうしたらできた。ボタンが表示できた。
やったね。
一難去ってまた一難
さて、無事にボタンが導入できたけどもとあった文字がすっごいしたのほうへ行ってしまった。ここらへんのデザインを修正しないといけない。どうすればいいのだろう。
次回もお楽しみに。
- 投稿日:2020-11-14T00:44:28+09:00
【Rails】updateアクション実行時にパスワードなどのバリデーションを通過させる方法
ユーザーのプロフィール情報を更新して保存するといった処理を実装するとき、ユーザーが変更したい情報のみを入力して、そのほかのフォームのバリエーションチェック(特にパスワード)はスキップさせたい!といった場面ってあるかと思います。今回はそのやり方を紹介します。
実装方法
バリデーションオプションにある、
:onオプション
を使います。例えばon: :create
とすることによってレコードの新規作成時のみバリデーションチェックを行うことができます。
私の場合、利用規約と個人情報のチェック(acceptedカラム)にもバリデーションチェックを行っていたため、updateアクション時にもこのバリデーションチェックが発動してしまい、ユーザー情報の変更ができないという状態になってしまっていました。:onオプション
でユーザーの新規登録時のみバリデーションチェックを行うようにすることで、これを回避することができます。user.rbvalidates :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アクション実行時のみバリデーションをスキップさせることができます。今回のは個人開発でまだ規模は小さいですが、規模が大きいサービスでは何気ないバリデーションの変更はアプリに大きな影響を与えそうだなと感じましたね。。
バリデーションはグローバルに定義するのではなく、なるべく条件を限定して影響する範囲を少なくするよう心掛けたいです!最後まで読んでいただきありがとうございます!
日々学んだことをアウトプットしてます!何かご指摘などありましたらコメント頂けますと幸いです。
- 投稿日:2020-11-14T00:24:18+09:00
メタプログラミング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 endprivateキーワード
明示的なレシーバをつけて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箇所だけ。
- refineブロックそのものと、
- usingを呼び出した場所からモジュールの終わりまで(モジュール定義にいる場合)、またはファイルの終わりまで(トップレベルにいる場合)
Refinementsが有効になっている限定されたスコープの中では、Refinementsはオープンクラスやモンキーパッチと同じ。
- 新しいメソッドを定義することができるし、既存のメソッドを再定義することもできるし、モジュールのincludeやprependもできる。
Refinementsが有効になっているコードは、リファインされた側のクラスのコードよりも優先される。
クラスをリファインするというのは、元のコードにパッチを貼り付けるようなもの。
2章まとめ
- オブジェクトは複数のインスタンス変数とクラスへのリンクで構成されている。 - オブジェクトのメソッドはオブジェクトのクラスに住んでいる(クラスから見れば、それはインスタンスメソッドと呼ばれる)。 - クラスはClassクラスのオブジェクトである。クラスメイは単なる定数である。 - ClassはModuleのサブクラスである。モジュールは基本的にはメソッドをまとめたものである。それに加えてクラスは、newでインスタンス化したり、superclassで階層構造を作ったりできる。 - 定数はファイルシステムのようなツリー上に配置されている。モジュールやクラスの名前がディレクトリ、通常の定数がファイルのようになっている。 - クラスはそれぞれBasicObjectまで続く継承チェーンを持っている。 - メソッドを呼び出すと、Rubyはレシーバのクラスに向かって一歩右へ進み、それから継承チェーンを上へ向かって進んでいく。メソッドを発見するか継承チェーンが終わるまでそれは続く。 - クラスにモジュールをインクルードすると、そのクラスの継承チェーンの真上にモジュールが挿入される。モジュールをプリペンどすると、そのクラスの継承チェーンの真下にモジュールが挿入される。 - メソッドを呼び出す時には、レシーバがselfになる。 - モジュール(あるいはクラス)を定義する時には、そのモジュールがselfになる。 - インスタンス変数は常にselfのインスタンス変数とみなされる。 - レシーバを明示的に指定せずにメソッドを呼び出すと、selfのメソッドだと見なされる。 - Refinementsはクラスのコードにパッチを当てて、通常のメソッド探索オーバーライドするようなものである。ただし、Refinementsはusingを呼び出したところから、ファイルやモジュールの定義が終わるところまでの限られた部分でのみ有効になる。第3章 火曜日:メソッド
重複コードを排除する
- 動的メソッド
- 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 endmethod_missing
- メソッド探索をした時、メソッド が見つからなければ
method_missing
メソッドを呼び出して負けを認める。
method_missing
は全てのオブジェクトが継承するBasicObject
のprivate
インスタンスメソッド。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つのポイント
- GitHubのオブジェクトを動的なハッシュに保持している。ハッシュのアトリビュートは、
url
やdescription
などのゴーストメソッドの呼び出しでアクセスできる。- これらのハッシュをプロキシオブジェクトでラップしている。プロキシオブジェクトには、追加のメソッドが用意されている。プロキシは2つのことを行っている。
star
のような処理が必要なメソッドの実装。- ラップしたハッシュに
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 endModule#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つあり、それぞれを印づけるキーワードをスコープゲートと言う。
- クラス定義(class)
- モジュール定義(module)
- メソッド(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 endinstance_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 } # => 6def 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つの違い
- returnキーワードに関すること
- 引数のチェックに関すること
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) # => 20Procと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を使って実行できる
Method
はMethod#to_proc
でProc
に変換できるし、ブロックは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]) end4章まとめ
- スコープゲートと、Rubyはどのようにスコープを管理しているのか。 - フラットスコープと共有スコープを追加、スコープを横断して束縛を見えるようにする方法。 - オブジェクトのスコープで(instance_evalやinstance_execを使って)コードを実行する方法や、クリーンルームでコードを実行する方法。 - ブロックとオブジェクト(Proc)を相互に変換する方法。 - メソッドとオブジェクト(MethodやUnboundMethod)を相互に変換する方法。 - 呼び出し可能オブジェクト(ブロック、Proc、Lambda)の種類とその違い。通常のメソッドとの違い。 - 独自の小さなDSLの書き方。