- 投稿日:2020-11-17T23:48:59+09:00
いらないコメントを一括削除する方法
これを覚えると時間の節約になるのでおすすめです。
前提として、環境はMacでエディターはVSCodeです。(Atomでや他のエディターでも同様のコマンドで可能かと思います)
Railsでテストアプリを作っている時などによく出る長文のコメント結構邪魔ですよね。
そんな時は
- command+Fで任意のページで検索して
# .*\n
- このコマンドを打ちますと(今回はVScode使ってます)
- 結果はありませんとなってしまいましたが、正規表現を表す
- 全て置換すると
コメントが一気に消えます。
ちなみにバックスラッシュの打ち方は日本語キーボードですと
Option + ¥ = \です。
是非試してみてくださいね。
- 投稿日:2020-11-17T23:34:08+09:00
LINEみたいに自分のメッセージと自分以外のメッセージの表示場所を変えたい
はじめに
現在作成中のチャットアプリのメッセージ画面をLINEのように自分の送ったメッセージは右、自分以外のメッセージは左に表示させよと一日苦戦した。ググってもあまり記事が見つからなかったので、ここに記録しておく。
ポイントとして使ったもの
- renderメソッド
- if文
- deviseのメソッド
コントローラー
messages_controller.rbdef index @class_room = ClassRoom.find(params[:class_room_id]) @messages = @class_room.messages.includes(:user) end
@class_room
にチャットルームをparamsから呼び出し代入
@messages
にそのチャットルームのメッセージを全て入れる。
.includes(:user)
はN+1問題を解消するため。ビュー
※divタブはコードが読みにくくなるため、ここでは割愛。
<% if message.user.id == current_user.id %> <%= l message.created_at %> <%= message.content %> <% else %> <%= message.user.last_name %> <%= l message.created_at %> <%= message.content %> <% end %>if文で、メッセージのユーザーIDと現在のログインしているユーザーが同じか照会し、同じであれば(つまり、自分の送ったメッセージ)上の処理が行われ、違えば、下の処理が行われる。
自分のメッセージに送り主の表示は必要ないので、上の処理にmessage.user.last_name
がない。その他
CSSを整える。
終わりに
他のやり方として、コントローラーの時点で、自分のメッセージと自分以外のメッセージを別の変数に入れる方法も考えられる。
- 投稿日:2020-11-17T23:12:13+09:00
【Ruby】値渡しと参照渡し
「値渡し」「参照渡し」とは
関数やメソッドにおける引数の渡し方の種類を表す用語のこと。
Rubyだけではなくどのプログラミング言語でも共通する。値渡し
- 実引数の値をコピーして仮引数に渡す
- コピーを渡すので、実引数には影響なし
- Rubyは値渡し(他の言語も)
参照渡し
- 実引数の変数があるメモリ上の場所(メモリ番地)をコピーして仮引数に渡す
- 与えられたメモリ番地から実引数の値を辿って値を変更する
- 仮引数に変更を加えると実引数も変更される
参照の値渡し?
- 変数が参照(メモリ番地)を保持している場合、参照がコピーされる
- 「オブジェクトの参照(メモリ番地)」をコピーして実引数に渡すので、同じオブジェクトを共有しているということになる
- つまり、引数が指すオブジェクトを変更すると元の変数のオブジェクトも変更される
- あたかも「仮引数を変更したら実引数も変更された」ように見えるだけで、実際は値渡し
- 「変数を変更する」ことと「変数が指すオブジェクトを変更する」ことは別!
引用元
メモリ番地でイメージするととてもわかりやすかったです。
今日学んだことを可視化するためにとりあえず書いてみました。後程自分の中で噛み砕いてまとめます。
- 投稿日:2020-11-17T22:47:20+09:00
【Rubyによるデザインパターン】ストラテジーパターンのメモ
プログラムの設計力向上のため『Rubyによるデザインパターン』を読んでおり、気になるデザインパターンを、1つずつまとめていきます。
今回は、ストラテジーパターンについてまとめました。
ストラテジーパターンについて
委譲をベースにした、複数の処理のバリエーションを実装するためのデザインパターンです。
継承を使いサブクラスごとにバリエーションを実装する実装するテンプレートメソッドパターンと異なり、ストラテジーパターンはバリエーションごとにばらばらのクラスを生成します。
サンプルコード
趣味の筋トレにちなんでサンプルコードを書きます。
(テンプレートメソッドパターンの記事との類似した例を題材としています。)ベンチプレスと懸垂はどちらもトレーニングの内容は同じものの、具体的な内容はそれぞれ異なっています。
そこで、トレーニングごとの内容を出力するプログラムを書きます。
ストラテジーパターン適用前はこのとおりです。
class Training def initialize(type) @type = type end def start prepare execute cleanup end def prepare if @type == :bench_press puts 'バーベルをセットします' end end def execute puts 'トレーニングをします' end def cleanup puts 'アルコール消毒します' if @type == :bench_press puts 'バーベルを戻します' end end end呼び出し時は引数にトレーニングの種類を渡します。
実行結果はこのとおりです。bench_press = Training.new(:bench_press) bench_press.start # バーベルをセットします # トレーニングをします # アルコール消毒します # バーベルを戻します tinning = Training.new(:tinning) tinning.start # トレーニングをします # アルコール消毒します # バーベルを戻しますインスタンス変数 @type によって if 分で分岐をしています。トレーニングが2種類ならばよいかもしれませんが、トレーニング数が増えるたびにこの条件分岐は増え、1つのメソッドは長く複雑になってしまいます。
また、 @type が :tinning のときは、 prepare が呼び出されているにも関わらず何も実行されていません。
これは、@type が :bench_press と :tinning で同じインターフェース(つまり、パブリックメソッド)である start を共有しているからです。次に、ストラテジーパターンを適用して書き換えた場合です。
class Training def initialize(training_menu) @training_menu = training_menu end def start @training_menu.start end end class BenchPress def start prepare execute cleanup end def prepare puts 'バーベルをセットします' end def execute puts 'トレーニングをします' end def cleanup puts 'アルコール消毒します' puts 'バーベルを戻します' end end class Tinning def start execute cleanup end def execute puts 'トレーニングをします' end def cleanup puts 'アルコール消毒します' end end呼び出し時はトレーニングのバリエーションごとのオブジェクトを生成し、 Training 初期化時に渡します。
bench_press = BenchPress.new training = Training.new(bench_press) training.start # バーベルをセットします # トレーニングをします # アルコール消毒します # バーベルを戻します tinning = Tinning.new training = Training.new(tinning) training.start # トレーニングをします # アルコール消毒します # バーベルを戻しますBenchPress と Tinning で同じ start というインターフェースを持ちつつも、処理の内容が異なることがおわかりいただけるかと思います。
このように、処理の内容のを個々のオブジェクトに委ねられるため変更に強くなります。テンプレートメソッドパターンとの比較
ストラテジーパターンはテンプレートメソッドパターンに似ているのも特徴ですね。
テンプレートメソッドパターンは継承を使うため、基底クラスの処理内容を生かしてサブクラスを実装します。基底クラスの処理内容に変更がない場合、実装するコードは少なくできる反面、サブクラスは基底クラスの処理内容に依存しているため基底クラスは変更しづらくなります。ストラテジーパターンは処理内容自体をオブジェクトに委譲できる点がメリットが大きいです。
テンプレートメソッドパターンとストラテジーパターンどちらを使うか迷った場合は、基本の処理内容が同じ場合はテンプレートメソッドパターン、処理内容のバリエーションが幅広い場合はストラテジーパターンが良さそうです。まとめ
ストラテジーパターンはポリモーフィズムを使ったオブジェクト指向らしいデザインパターンです。
テンプレートメソッドパターンと合わせてストラテジーパターンも使いこなせるようになりたいです。
- 投稿日:2020-11-17T21:58:10+09:00
未経験のおっさんがプログラミングを学ぶまで
はいどうも!
某プログラミングスクールに通い始めた37歳のおっさんです。
プログラミングは完全に未経験。
そんなおっさんが転職するまでの軌跡(奇跡?)をぼちぼち綴っていこうと思います。投稿は今日が初めてですがスクールは今日で9日目。
HTML&CSSをやってRubyに入り、Railsをやって試験を受けて、というところです。簡単に私の経歴を
↪️
地方出身、東京の文系私立大学卒、東京で就職し営業職(toC & toB)でマネジメント経験あり。
インディーズですが学生時代にやっていたバンドでCDを1枚だけ出しています。
キャンプとジム通い、音楽掘りが趣味。
ジャンルはRock、JazzからEDMまで幅広く聴きます。
今はちょっと難しくなってしまいましたが海外旅行もそこそこ行きました。
なぜプログラミングを始めたのか、どうして転職を決意したのかなどは後々ゆっくりとupしていこうと思います。
とりあえずまだ始めたばかりですが感想としては
「めちゃくちゃやることがいっぱいある」
「勉強すること、覚えることが今までやってきたことの比じゃないレベルで多い」
この二つですね。
日々思ったこと、できなかったこと、疑問点などなど
自分が見返した時に
「あ〜こんなことあったな」とか、
「こんなことで悩んでたんだな」とかとか、
あとはこれを読んだ他の方に少しでも役に立てればなと思います。そんな具合ですが本日は頭がパンクしているのでこれにて
- 投稿日:2020-11-17T21:07:09+09:00
【アウトプット】find_by メソッド
今回はfind_byについてアウトプットをしていく。
find_byはある特定のIDを取得すときに使われる。
3番目のIDのデータを取得するとか5番目とかモデル名.find_by(カラム名: 値)と書くことでその値を持ったデータのデータベースから取得ができるようになる。
例)post = Post.find_by (id:3)
ちなみにpostはeachのときに使った変数このようにして使われる。
変数paramsとよく使われる?らしいのでこれも併用して覚えるとGOOD
- 投稿日:2020-11-17T20:25:58+09:00
LeetCodeをRubyで解く #1 [ Top Interview Questions, easy ]
GAFA等のコーディング試験対策としてよく使われている「LeetCode」をRubyで解きました。
まとまったRubyの解答例がなかったので、Top Interview Questionsの難易度easyで検索した46問についてまとめていきます。#1 Two Sum (Acceptance:45.9%)
問題
配列numsと整数targetが与えられ、 異なる2つの要素の和がtargetと同値なる配列要素のインデックスを出力する Example 1: Input: nums = [2,7,11,15], target = 9 Output: [0,1] Output: Because nums[0] + nums[1] == 9, we return [0, 1]. Example 2: Input: nums = [3,2,4], target = 6 Output: [1,2] Example 3: Input: nums = [3,3], target = 6 Output: [0,1]まず自分で書いてみた例です。
結果はTime Limit Exceeded
となりました…結果: Time Limit Exceeded def two_sum(nums, target) nums.each_with_index do |n1, idx1| pair = target - n1 nums.each_with_index do |n2, idx2| if n2 == pair && idx1 != idx2 return [idx1, idx2] end end end end一見合っていそうですが、以下の条件だったので、
each
を2重にするという愚直な方法では時間超過になるようです…Constraints: 2 <= nums.length <= 10^5 -10^9 <= nums[i] <= 10^9 -10^9 <= target <= 10^9 Only one valid answer exists.投稿されていた解法を探してみました。一部Wrongとなりましたが、
Acceptedされた解答例は以下の通りです。結果: Accepted def two_sum(nums, target) hash = Hash.new nums.each_with_index do |num, i| return [hash[num], i] if hash.key?(num) hash[target - num] = i end.clear end結果: Accepted def two_sum(nums, target) hash = {} nums.each_with_index do |number, index| hash[number] = index end nums.each_with_index do |n, i| complement = target - n if hash.has_key?(complement) && hash[complement] != i return [hash[complement], i] end end end結果: Accepted def two_sum(nums, target) hash = {} result = nil nums.each_with_index do |num, i| value = target - num if (hash.has_key? value) result = [hash[value], i] break end hash[num] = i end result end#7 Reverse Integer (Acceptance:25.8%)
問題
与えられた32ビットの整数Xを逆の桁にする ただし変換後に−2^31〜2^31 − 1の範囲外であれば0とする Example 1: Input: x = 123 Output: 321 Example 2: Input: x = -123 Output: -321 Example 3: Input: x = 120 Output: 21 Example 4: Input: x = 0 Output: 0 Example 5: Input: x = 1534236469 Output: 0 ※9646324351 > 2^31 − 1のためまず自分で書いてみた例です。
なんとかAccepted
となりました…
Railsチュートリアル感がある書き方ですね…結果: Accepted def reverse(x) answer = x.to_s.reverse.to_i answer *= (-1) if x < 0 if answer > 2 ** 31 - 1 || answer < 2 ** 31 * (-1) answer = 0 end return answer endその他、Acceptedされた解答例は以下の通りです.
結果: Accepted def reverse(x) reversed_num = x < 0 ? ('-' + x.to_s.reverse).to_i : x.to_s.reverse.to_i (-2**31..(2**31 - 1)).include?(reversed_num) ? reversed_num : 0 end結果: Accepted def reverse(x) number = x.abs reversed = 0 # the while loop is based on absolute value while number != 0 # guard against overflow before multiplying return 0 if reversed >= 2**31/10 + 0.8 # 0.8 since the last digit: 2147483648 last_digit = number % 10 reversed = (reversed * 10) + last_digit number = number / 10 end x.negative? ? -reversed : reversed end自分の回答と似ているけど、こっちのほうがスマート。
結果: Accepted def reverse(x) s_i = x.to_s s_i = s_i.reverse.to_i s_i *= -1 if (x < 0) return 0 if s_i.bit_length > 31 return s_i end#13 Roman to Integer (Acceptance:56.2%)
問題
ローマ数字を整数に変換する。 Symbol Value I 1 V 5 X 10 L 50 C 100 D 500 M 1000 例えば、XXVIIとなれば、27=10+10+5+1+1 ローマ数字は通常、左から右に最大から最小の順に書かれる。ただし、4の数字はIIIIではなく、IVと書かれる。同じ原則が、IXと書かれる9にも当てはまります。 V(5)とX(10)の前にI(1)を置くと4と9を表します。 L(50)とC(100)の前にX(10)を置くと40と90を表します。 D(500)とM(1000)の前にC(100)を置くと、400と900を表します。 Example 1: Input: s = "III" Output: 3 Example 2: Input: s = "IV" Output: 4 Example 3: Input: s = "IX" Output: 9 Example 4: Input: s = "LVIII" Output: 58 Explanation: L = 50, V= 5, III = 3. Example 5: Input: s = "MCMXCIV" Output: 1994 Explanation: M = 1000, CM = 900, XC = 90 and IV = 4.まず自分で書いてみた例です。 ちょっとカンニング気味でしたが、なんとかAcceptedとなりました。
結果: Accepted def roman_to_int(s) hash = {'M' => 1000, 'D' => 500 , 'C' => 100, 'L' => 50, 'X' => 10, 'V' => 5, 'I' => 1} answer = 0 for i in 0..s.length-1 do if !hash[s[i+1]].nil? && hash[s[i]] < hash[s[i+1]] answer -= hash[s[i]] else answer += hash[s[i]] end end return answer end本問題はサブスク課金しないと解答が見れませんでしたが、
stackoverflow等を調べた中でAcceptedされた解答例は以下の通りです。
downcase
する必要はあるのか?(丁寧なだけ?)というポイントと、三項演算子を使っているか程度の違いです。結果: Accepted ROMAN_TO_INT = { i: 1, v: 5, x: 10, l: 50, c: 100, d: 500, m: 1000 } def roman_to_int roman value_map = roman.split('').map { |e| ROMAN_TO_INT[e.downcase.to_sym] } value_map.map.with_index do |e, idx| unless value_map[idx + 1].nil? then value_map[idx + 1] > e ? -e : e else e end end.sum endこれは正規表現を使うスマートで高難易度の解法ですね…
結果: Accepted H = {"VI"=>" 4", "XI"=>" 9", "LX"=>" 40", "CX"=>" 90", "DC"=>" 400", "MC"=>" 900", "I"=>" 1", "V"=>" 5", "X"=>" 10", "L"=>" 50", "C"=>" 100", "D"=>" 500", "M"=>" 1000"} def roman_to_int(s) s.reverse.gsub(Regexp.union(H.keys), H).split.sum(&:to_i) end #ちょっと解説 k = H.keys #=> ["VI", "XI", "LX", "CX", "DC", "MC", "I", "V", "X", "L", "C", "D", "M"] r = Regexp.union(H.keys) #=> /VI|XI|LX|CX|DC|MC|I|V|X|L|C|D|M/ t = s.gsub(r, H) #=> " 1 5 10 10 100 100" a = t.split #=> ["1", "5", "10", "10", "100", "100"] a.sum(&:to_i) # => 226#14 Longest Common Prefix (Acceptance:35.8%)
問題
文字列の配列の中から最も長い共通のプレフィックス文字列を見つける. 共通のプレフィックスがない場合は、空の文字列を返す。 Example 1: Input: strs = ["flower","flow","flight"] Output: "fl" Example 2: Input: strs = ["dog","racecar","car"] Output: "" Explanation: There is no common prefix among the input strings.自分の回答。Acceptedもらえる!と意気込んでsubmitしたら、
Wrong Answer
でした。結果: Wrong Answer def longest_common_prefix(strs) unless strs[0].nil? base = strs[0].split('') answer = base.dup for i in 1..strs.length-1 do base.each do |b| if !strs[i].split('').include?(b) answer.delete(b) end end end end return answer.join unless answer.nil? return answer = "" endWrongとなった入出力を見ると以下の通りで、連続した文字列でないものもひろってしまっていたためでした。(問題をよく見ていなかった…)
Wrong Answer > Details Input ["cir","car"] Output "cr" Expected "c"本問題に関してRubyの解答は投稿されていませんでしたが、
調べた中でAcceptedされた解答例は以下の通りです。結果: Accepted def longest_common_prefix(strs) strs.map {|str| str.split('') }.tap {|str_arrays| break str_arrays[0] ? str_arrays[0].zip(*str_arrays[1..-1]) : [] }.reduce('') do |result, arr| if arr.uniq.size == 1 result << arr[0] else return result end result end end結果: Accepted def longest_common_prefix(strs) loop do str_a = strs.shift str_b = strs.shift return str_a.to_s if str_b.nil? common = longest_common_prefix_between(str_a, str_b) strs.unshift(common) end end def longest_common_prefix_between(str_a, str_b) len = [str_a.length, str_b.length].min result_str = "" len.times do |i| if str_a[i] == str_b[i] result_str += str_a[i] else break end end result_str end#20 Valid Parentheses (Acceptance:39.3%)
問題
'('と')'、'{'と'}'、'['と']'が有効に入力されているかどうかを判定する。 次の場合に有効とする。 開いた括弧は、同じタイプの括弧で閉じる必要があり。 開いた括弧は正しい順序で閉じる必要があり。 Example 1: Input: s = "()" Output: true Example 2: Input: s = "()[]{}" Output: true Example 3: Input: s = "(]" Output: false Example 4: Input: s = "([)]" Output: false Example 5: Input: s = "{[]}" Output: true難問でした。手も足も出ず、解答を探しました。
調べた中でAcceptedされた解答例は以下の通りです。結果: Accepted def is_valid(s) map = {')': '(', '}': '{', ']': '['} stack = [] s.each_char do |i| if map.has_key?(i.to_sym) top_element = stack ? stack.pop : '#' return false unless map[i.to_sym] == top_element else stack.push(i) end end stack.empty? end似たような解答ですが、もう一つ。
結果: Accepted def is_valid(s) return true if s == "" return false if s.length % 2 == 1 stack = [] map = { ']': '[', '}': '{', ')': '(' } s.each_char do |bracket| if stack.last && map[bracket.to_sym] top = stack.empty? ? '#' : stack.pop return false if top != map[bracket.to_sym] else stack << bracket end end stack.length == 0 end問題の訳し方や解法等で問題があれば、お手数をおかけしますがコメントいただけると幸いです。
あと41問続きます…
- 投稿日:2020-11-17T20:02:51+09:00
混乱しがちなアソシエーションのclass_nameやforeign_keyを理解する
Railsでお知らせ機能やフォロー機能を実装しているとclass_nameやforeign_keyが出てくると思います。これらを理解するのに時間がかかったので、備忘録もかねて投稿します。
今回はお知らせ機能を例に挙げる。
イメージしやすいようER図を載せる。(最低限のカラムしか書いてません。)お知らせ機能を実装していると@user.passive_notificationsのようにして自分に来ている通知のレコードをまとめて取得したいことがある。そういう場合、下のように書く。
user.rbhas_many :passive_notifications, class_name: "Notification", foreign_key: "visited_id"少し複雑なので、簡単な例を挙げる。
Userモデルで下記のように定義した場合
よく使うhas_many :tweetsは実は色々省略されている。
user.rbhas_many :tweets # 同じ意味 has_many :tweets, class_name: "Tweets", foreign_key: "user_id"Railsで開発をするとhas_manyやbelongs_toを使ってモデルでリレーションを組むが、これを書くことによって、Userクラスへのticketsメソッドの定義が裏で行われている。例でいうと、ticketsメソッドの対象となるClassはTweetクラスで、ticketsテーブルのuser_idの値を参照してレコードを取得する。
Railsの規則によりモデルの複数形でメソッドを定義する場合、クラス名、外部キーを省略することができる。なぜならメソッド名により暗黙的にクラス名、外部キーが決定されるからである。
上記のようにメソッドを定義する(リレーションを組む)ことで、コントローラーなどで下のメソッドが使えるようになる。
user.rb@user = User.find(params[:id]) @user.tweets少し脱線したが、このことを頭に入れてお知らせ機能のリレーションをもう一度見てみる。
user.rbhas_many :passive_notifications, class_name: "Notification", foreign_key: "visited_id"ここの記述では、Userモデルにpassive_notificationsメソッドを定義している。
メソッド名からはクラス名が不明なため、class_name: "Notification"として対象のクラスを明示している。また、外部キーを設定しない場合、notificationsテーブルからuser_idのカラムを探しにいってしまうため、foreign_key: "visited_id"として定義する。これにより、@userのidとnotificationsテーブルのvisited_idとが一致したレコード、つまり@user宛の通知のレコードがまとめて取得できるようになる。
user.rb@user = User.find(params[:id]) @user.passive_notifications #ユーザ宛の通知のレコードを全件取得以上です。
まちがっていたらご指摘お願いします。
- 投稿日:2020-11-17T19:29:25+09:00
Railsのユーザ入力情報の保護機能
本投稿の目的
・Rails学習の議事録です。
学習に使った教材
Udemyの "はじめてのRuby on Rails入門-RubyとRailsを基礎から学びWebアプリケーションをネットに公開しよう" を教材として使用しました。
①Validates(バリデート)
・modelファイルに対して実施
・ユーザーが入力した値をdb保存する際の条件を指定できる
・Ex)空入力禁止,最大文字数10文字まで...qiita.rbdef model名 validates :column名, 条件: 値の指定 end【解説】
○validates :column名
⇒条件を課したいcolumn名を記載○条件: 値の指定
⇒実施したい制約に応じた記述法を記述
⇒以下に2つ例を記述する・空入力禁止
qiita.rbpresence: true・最大文字数10文字まで
qiita.rblength: { maximum: 10 }②params
・配列を入れるための箱(ハッシュ)
・ユーザーが送信したデータを一時的にparamsに格納
・その中から,必要なハッシュにヒットする値を取り出す【例: ユーザーが名前と年齢をフォームから送信した場合】
・フォームからparams へ次のように格納されるqiita.rbparams = [:name,'naoto', :age,24]・名前を取得したい場合
qiita.rbparams[:name]・年齢を取得したい場合
qiita.rbparams[:age]③strong_parameters
・指定したcolumn情報以外のフォーム受信値を無視するフィルター
【いつ役に立つ?】
・ECサイトを作成したと想定
・ユーザーがフォームでソースを操作
・ポイントcolumnを修正し残高を高めに変更
・こういった改ざん処理を止めるための設定【使いかた】
・controller中に記述(フォーム送信後のインスタンス生成時)
・引数にこのメソッドを()で指定する
・フィルタされた値がdbへ格納【例:question_paramsメソッド】
qiita.rbparams.require(:question).permit(:name, :title, :content)【paramsには以下が格納された場合を想定】
qiita.rbparams = [ question={name: 'naoto',age: 24, content: '質問内容'}, answer={name: 'kanopyo',age: 27, content: '回答内容'} ]【解説】
〇.requireについて
⇒モデル名のキーを指定〇.permitについて
⇒.requierで指定したモデルのプロパティのキーを指定
- 投稿日:2020-11-17T18:25:59+09:00
logを確認しながらrakeタスクを実装して、対象appを指定して起動しよう(Heroku)
こんにちは、株式会社ベストティーチャーでサーバーサイドエンジニアとして働き出したばかりのタワラです。
業務ではじめて学んだTipsを紹介したいと思います。
今回はrakeタスクを作成してHerokuで動作確認をする手順です。Rakeタスクを実装する手順
Tips1. ローカルでRakeタスクを作成する際には、Rails.loggerを活用する
こんな感じでコードを書いていたのですが、、、キャッシュを問い合わせるだけだから、これではログに何の情報も出ないな、動作確認はどうしよう、、、と思っているところ、、、
namespace :hogehoge do desc "適当な説明をいれる" task :foobar => :environment do Rails.cache.fetch('hoge') end end先輩「Rails.loggerを使うんだ!」
ボク「なるほど!」namespace :hogehoge do desc "適当な説明をいれる" task :foobar => :environment do Rails.logger.info('foobar started!') Rails.cache.fetch('hoge') Rails.logger.info('foobar finished!') end endこのように記述しておけば、例えばログに結果が出ない場合の処理でも、rakeタスクの作業確認ができます。こんなふうに↓
log/development.log略 foobar started! foobar finished! 略きちんと動作していることが一目瞭然なのですごい便利!
Tips2. Heroku上で確認するときはアプリ名を指定する
続いて、Heroku上で動作確認をする必要があります、↓のコマンドでrakeタスクを走らせることはできますが、、、
heroku run rake hogehoge:foobar先輩「ちょっとお待ち!」
先輩「オプションでプロジェクト名を指定するのを忘れないで!」heroku run rake hogehoge:foobar -a omosiro-project先輩「きちんとrakeタスクを起動する対象のプロジェクトなのかを確認しよう!」
先輩「こういうことでバグが起きる可能性はゼロじゃないからね」
ボク「なるほど!」終わりに
今回のTipsはこの2つ!
Tips1. ローカルでRakeタスクを作成する際には、Rails.loggerを活用する
Tips2. Heroku上で確認するときはアプリ名を指定する業務ではやはり学ぶことが多いです。
本記事が業務駆け出しの方などの役に立てば幸いです。
- 投稿日:2020-11-17T16:30:05+09:00
Railsを使ったToDoリストの作成(3.Railsの基本概念)
概要
本記事は、初学者がRailsを使ってToDoリストを作成する過程を記したものです。
私と同じく初学者の方で、Railsのアウトプット段階でつまづいている方に向けて基礎の基礎を押さえた解説をしております。
抜け漏れや説明不足など多々あるとは思いますが、読んでくださった方にとって少しでも役に立つ記事であれば幸いです。環境
Homebrew: 2.5.10
-> MacOSのパッケージ管理ツールruby: 2.6.5p114
-> RubyRails: 6.0.3.4
-> Railsnode: 14.3.0
-> Node.jsyarn: 1.22.10
-> JSのパッケージ管理ツールBundler: 2.1.4
-> gemのバージョン管理ツールiTerm$ brew -v => Homebrew 2.5.10 $ ruby -v => ruby 2.6.5p114 $ rails -v => Rails 6.0.3.4 $ npm version => node: '14.3.0' $ yarn -v => 1.22.10 $ Bundler -v => Bundler version 2.1.4第3章 Railsの基本概念
第3章では、Railsで本格的にアプリ開発をする前にRailsにおいて重要な概念を説明していきます。
1 Railsを使ってWebサイトを表示する
まずは、RailsがどのようにWebサイトを表示しているのか説明します。
Webサイトは、クライアント(ブラウザ)がリクエストを送り、サーバ(ここではRails)がレスポンス(HTMLやCSSファイルをブラウザに送る)したものを、ブラウザが解釈することで表示されています。ではRailsでは具体的にどのようなことが行われているか見ていきます。
①まず、ブラウザからGETリクエストが飛んできたら
routes.rb
で処理をします。
routes.rb
はURLを作る場所です。config/routes.rbRails.application.routes.draw do root to: 'boards#index' end?♂️root(localhost:3000)を表示してくださいというリクエストが飛んできたらBoardsControlerのindexメソッドを実行してください
②次に、コントローラです。
コントローラはroutes.rb
からブラウザからのリクエストを受け取り、モデルやビューなどと連携し結果をブラウザに返す役割を担っています。
routes.rb
の「BoardsControlerのindexメソッドを実行してください」という情報をもとに、コントローラが処理を行います。app/controllers/boards_controller.rbclass BoardsController < ApplicationController def index render 'boards/index' #この部分は省略可能 end end?♂️viewsのboardsのindex.html.hamlを表示してください
ちなみにコントローラには命名規則があります。
名前 例 コントローラ名 boards コントローラのクラス名 BoardsController コントローラのファイル名 boards_controller.rb 上記のように、コントローラ名を「boards」とした場合、コントローラのクラス名は「BoardsController」のようにコントローラ名の先頭を大文字にしてControllerを付けます。このクラスが記載されているファイル名は「コントローラ名_controller.rb」になります。
③最後に、viewsです、viewsにはブラウザに表示したいことを記述します。
app/views/boards/index.html.haml%h1 Webサイトの表示
上の画像のように表示されていれば完了です。
以上が、RailsがWebサイトを表示する仕組みです。2 MVC
1 MVCとは
MVCとは、Webフレームワークで一般的に取り入れられているアプリケーションの設定を整理するための概念の一つです。
「Model」「Views」「Controller」の頭文字をとって名付けられています。
それぞれに役割があり、ControllerがModelからデータを取得してViewsに表示するという処理が行われています。
(画像の引用元:https://snome.jp/framework/mvc-model/)2 モデルの作成
実際に、操作の対象となる「モデル」を作成します。
モデルを作成するためにはターミナルにて$rails g model [モデル名(単数形)]
のコマンドを実行します。iTerm$ rails g model Board =>create db/migrate/20201117041911_create_boards.rb create app/models/board.rb?♂️Boardモデルを作成してください
?Boardに対応するデータベースのテーブル(migrationファイル)を作成しました
?Boardに対応するRubyのクラスを作成しましたモデルの作成が完了していたら以下のようなファイルが作成されているはずです。
コメントアウトされている部分は現在のデータベースの構造(schema)のメモであり、第1章で'annotate'をインストールしたため表示されている。app/models/board.rb# == Schema Information # # Table name: boards # id :bigint not null, primary key # created_at :datetime not null # updated_at :datetime not null # class Board < ApplicationRecord endモデルが作成できたら、migrationファイルにcolumnを追加していきます。migrationファイルはデータベースのテーブルを作成するファイルです。
今回はボード(タスクのまとまり)の名前と説明をデータとして扱いたいので、'name'と'description'という2つのcolumnを追加します。
columnを追加する際はt.[データ型] :[column名]
と入力します。20201115023512_create_boards.rbclass CreateBoards < ActiveRecord::Migration[6.0] def change create_table :boards do |t| t.string :name, null: false t.text :description, null: false t.timestamps end end end※
null: false
オプション→このcolumnには絶対に値が入っていないといけないという指定ができます。今回はタスク名とタスクの説明は必須項目のため指定しています。マイグレーションをデータベース(PostgreSQL)に適用するには、ターミナルにて
$rails db:migrate
を忘れずに実行しましょう。3 ActiveRecord
ActoveRecordとは、Railsで採用されているORマッパーです。
ORマッパーとは、オブジェクトとデータベース間の関係を定義するだけで、データベースへのアクセスが行えるシステムのことです。
つまりRubyを使うことでSQLを書かなくてもデータベースにアクセスすることができるということです。
ActiveRecordはRailsにデフォルトでインストールされており、モデルの中で使うことができます。
では、なぜモデルの中で使うことができるのでしょうか?
以下2つのファイルを見てみましょう。app/models/board.rbclass Board < ApplicationRecord endapp/models/application_record.rbclass ApplicationRecord < ActiveRecord::Base self.abstract_class = true endBoardクラスはApplicationRecordクラスを継承しています。
ApplicationRecordクラスはActiveRecord::Baseクラスを継承しています。
つまりBoardクラスがActiveRecord::Baseクラスを継承しているため、モデルの中でActiveRecordを使うことができます。ActiveRecord::Baseクラスには様々なメソッドが定義されており、それらのメソッドを実行することにより、データベースにアクセスすることができます。
では具体的にどのようなメソッドが定義されているのか?
代表的なものを見ていきましょう。◆レコードを取得する
Board.find(1) #テーブルから引数に入っているidのレコードを取得する Board.find_by({column名: ''}) #テーブルからcolumn名でレコードを取得する #Board.find_by(column名: '') -> {}を省略しても良い Board.first,second,third #テーブルからidが一番若いレコードを取得する Board.last #テーブルからidが一番大きいレコードを取得する Board.all #テーブルから全てのレコードをを配列として取得する Board.all.order(:id) #レコードををid順に並び替えて取得する #逆順に並べ替えて取得する時は(id: :desc) Board.all.limit(3) #テーブルから引数に入っている数だけidの若い順にレコードを取得する Board.where('id > ?', 2) #引数の条件に合ったレコードを全て取得する #条件は文字列で渡す必要がある Board.count #データの件数を数える◆データの作成・削除
Board.create({name: '', description: ''}) #引数の値を元にインスタンスを作成し、DBに保存する #board = Board.new({name: 'new', description: 'new'}) -> 空の箱を作る #board.save -> レコードをDBに保存する Board.save #createとは違って保存機能のみ Board.update #取得したレコードを更新し保存する #Board.last -> 対象となるデータを取ってくる #Board.last.update({column名: ''}) -> 更新する Board.assign_attributes #取得したレコードを更新するが保存はしない #board = Board.last #board.assign_attributes(title: 'assigned') #board.save Board.destroy #取得したレコードを削除する #board = Board.last -> 削除する対象のデータを取得する #board.destroy`では実際に、ターミナルでコンソールを立ち上げてデータを作成してみましょう。
ターミナルでコンソールを立ち上げるためには以下のコマンドを実行します。iTerm$ rails cコンソールが立ち上がったら、createメソッドを実行しましょう。
iTermirb(main):001:0> Board.create(name: 'console-name1', description: 'console-description1') (0.2ms) BEGIN Board Create (4.4ms) INSERT INTO "boards" ("name", "description", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["name", "console-name1"], ["description", "console-description1"], ["created_at", "2020-11-17 05:38:06.272799"], ["updated_at", "2020-11-17 05:38:06.272799"]] (1.5ms) COMMIT => #<Board id: 1, name: "console-name1", description: "console-description1", created_at: "2020-11-17 05:38:06", updated_at: "2020-11-17 05:38:06">createメソッドを実行することにより、SQLが実行され、テーブルにデータが保存されます。
では、実際に保存されているのかActiveRecord::Baseクラスのallメソッドを使って確かめてみましょう。iTermirb(main):002:0> Board.all Board Load (0.9ms) SELECT "boards".* FROM "boards" LIMIT $1 [["LIMIT", 11]] => #<ActiveRecord::Relation [#<Board id: 1, name: "console-name1", description: "console-description1", created_at: "2020-11-17 05:38:06", updated_at: "2020-11-17 05:38:06">]>下記のようにデータが作成されていることがわかると思います。
Board id: 1,
name: "console-name1",
description: "console-description1",
created_at: "2020-11-17 05:38:06",
updated_at: "2020-11-17 05:38:06">以上でActiveRecordの説明は終わりです。
コンソールを終了したい時はexit
と入力しましょう。
- 投稿日:2020-11-17T14:06:46+09:00
【Rails6.0】ポートフォリオに必須な「かんたんログイン」実装手順
概要
Railsでアプリを作成する際、deviseを用いてログイン機能を実装する方は多いかと思います。
中でも、ポートフォリオには「かんたんログインをつけよう!」
と耳にした方も多いかと。しかし、私の経験談として、
実装には結構苦労しました
。
理由としては、シンプルに「devise関連で、知らなかったことをやる必要があったから」
です。なので今回の記事では、
- deviseのコントローラをいじらなきゃいけないの?
- ルーティングはどうやって設定する?
このあたりを整理しつつ、「かんたんログイン実装手順」をアウトプットしたいと思います。
なお、この記事はこちらの素晴らしい記事を参考にしています。
YouTubeでの詳しい解説もありますので、ぜひ御覧ください。Qiita | 簡単ログイン・ゲストログイン機能の実装方法(ポートフォリオ用)
環境
- macOS Catalina 10.15.6
- ruby 2.6.5
- Rails 6.0.3.4
- MySQL : 5.6.47
- devise : 4.7.3
実装の流れを確認
実装に入る前に、まず
かんたんログインの流れ
をおさえましょう。かんたんログインの流れ1. かんたんログインボタンクリック 2-1. 任意のメアド(guest@gmail.com)がusersテーブルにない場合は他カラムを追加しユーザー作成 2-2. 任意のメアド(guest@gmail.com)がusersテーブルにある場合はそれを取得 3. 作成・取得したユーザーでログイン 4. 好きなページにリダイレクト(大体はトップページ)つまり、必要なのは次の工程です。
必要な工程1. ルーティングにかんたんログイン用コントローラのパスを設定 2. かんたんログイン用コントローラーを作成 3. リンクをビューに設置この工程に沿って実装していきます。
実装 : ファイルを3つ編集すればOK
それでは、
ルーティング→コントローラ→ビューの順番で実装していきます
。
1. かんたんログイン用ルーティングの設定
ルーティングファイルに以下の記述を追加して下さい。
routes.rbdevise_scope :user do post 'users/guest_sign_in', to: 'users/guest_sessions#new_guest' endこれにより、次のルーティングが生成します。
Prefix Verb URI Pattern Controller#Action users_guest_sign_in POST /users/guest_sign_in users/guest_sessions#new_guest この内容を整理します。
- post, users_guest_sign_in_pathで
app/controllers/users/guest_sessions_controller.rb
のnew_guestアクション
を指定というわけで、指定したディレクトリにコントローラを作成して、かんたんログインアクションを実装しましょう。
2. かんたんログイン用コントローラの作成
app/controllers/users/guest_sessions_controller.rb
にnew_guest
アクションを実装していきます。
- 名前空間とRailsの決まりごとに従ってクラス名を選択
- DeviseのSessionsControllerを継承
guest@gmail.com
の有無でユーザーの作成or取得を変更以上の点に注意しましょう。
app/controllers/users/guest_sessions_controller.rbclass Users::SessionsController < Devise::SessionsController def new_guest user = User.find_or_create_by!(email: "guest@gmail.com") do |user| # ブロックで必要カラムを追加(自分の場合はnicknameを追加) user.nickname = "テストユーザー" user.password = SecureRandom.urlsafe_base64 # user.confirmed_at = Time.now # Confirmable を使用している場合は必要 end # ログイン(deviseのメソッド) sign_in user # トップページへリダイレクト redirect_to root_path end end
find_or_create_by!
は次のようなメソッドです。
- find_by(カラム: 値)で該当するレコードを取得
- 見つからない場合は、createで新規レコードを作成
- ! は例外を発生させる記述 (! がない場合は、ログインされないままただリダイレクトされる)
ちなみに、今回は実装するコードについての示すのが目的なので、名前空間とパスワードの部分については以下の記事を見て補完して下さい (なお、Qiitaは僕が書いた記事です)。
- Rubyリファレンスマニュアル | SecureRandomモジュール
- Qiita | 【Rails/ルーティング】自作したディレクトリ内のコントローラを参照する方法
- Qiita | 【Ruby/namespace】Rubyでよく見る「:: ←これ」を深ぼる
3. ビューにリンクを設置
かんたんログインボタンを実装したいビューにリンクを設置しましょう。
僕のアプリの場合は、トップページに以下のコードを追加しました。トップページ<%= link_to 'ゲストログイン', users_guest_sign_in_path, method: :post %>これで完了です!
(以下のGIFでは自分のアプリ用に詳細を変更しています)
4. (補足) 一部をUserモデルに移植する
DBとやりとりしてレコードを取得・生成するのはモデルの仕事です。
なので次のように切り離すとベターかと思います。app/controllers/users/guest_sessions_controller.rbclass Users::SessionsController < Devise::SessionsController def new_guest user = User.guest # ログイン(deviseのメソッド) sign_in user # トップページへリダイレクト redirect_to root_path end endapp/models/user.rbclass User < ApplicationRecord # (略) def self.guest find_or_create_by!(email: "guest@gmail.com") do |user| user.nickname = "テストユーザー" user.password = SecureRandom.urlsafe_base64 end end end
まとめ
- かんたんログイン実装には、以下のファイルを作成or編集すればOK
- かんたんログインアクションへのルーティング
- deviseコントローラを継承した「かんたんログイン用コントローラ」
- ボタンを設置したいビュー
いろんな記事があると思いますが、基本はこちらに示したとおりなので、ぜひ参考にして下さい!
- 投稿日:2020-11-17T11:58:09+09:00
[Rails]カテゴリー機能
はじめに
アプリ開発において、ancestryというgemを用いてカテゴリー機能を加えたのでまとめました。
目次
- カテゴリー選択・保存
- カテゴリー検索表示
- カテゴリー検索結果表示
1. カテゴリー選択・保存
categoriesテーブルの作成
ancestryをインストールします。
gemfilegem 'ancestry'次にcategoryモデルを作成します。
ターミナル
rails g model categoryhas_ancestryを記述します。
app/models/category.rbclass Category < ApplicationRecord has_many :posts has_ancestry end以下のようにマイグレーションファイルに記述します。
indexについてはこちらdb/migrate/20XXXXXXXXXXXX_create_categories.rbclass CreateCategories < ActiveRecord::Migration[6.0] def change create_table :categories do |t| t.string :name, index: true, null: false t.string :ancestry, index: true t.timestamps end end endgoogleスプレッドシートにカテゴリーを記述していきます。
Aの列がid、Bの列がname(カテゴリー名)、Cの列がancestry(親子孫を見分ける数値)となります。
データの保存方法は、ファイル → ダウンロード → カンマ区切りの値(.csv 現在のシート) の手順で保存できます。ダウンロードしたcsvファイルはdbフォルダに配置します。
seeds.rbファイル内へ以下の通り記述します。
db/seeds.rbrequire "csv" CSV.foreach('db/category.csv') do |row| Category.create(:id => row[0], :name => row[1], :ancestry => row[2]) endターミナルでrails db:seedコマンドを実行するとcsvファイルを読み込み自動でDBのレコードが生成されます。
foreachの後に読み込みたいファイルの指定を行います。
その下の記述については、モデル名.create(カラム名 => 読み込みたい列)となります。
row[0] → Aの列がid
row[1] → Bの列がname(カテゴリー名)
row[2] → Cの列がancestry(親子孫を見分ける数値)ルーティング
子、孫カテゴリーをjson形式でルーティングを設定します。
config/routes.rbRails.application.routes.draw do ~略~ resources :posts do collection do get 'top' get 'get_category_children', defaults: { format: 'json' } get 'get_category_grandchildren', defaults: { format: 'json' } get 'name_search' end ~略~ endコントローラー
postsコントローラーに親カテゴリーを定義します。
複数箇所で使用するためbefore_actionを使って定義します。app/controllers/posts_controller.rbdef set_parents @parents = Category.where(ancestry: nil) endpostsコントローラーに子、孫カテゴリーのメソッドを定義します。
app/controllers/posts_controller.rbdef get_category_children @category_children = Category.find("#{params[:parent_id]}").children end def get_category_grandchildren @category_grandchildren = Category.find("#{params[:child_id]}").children endjson.jbuilderファイルを作成し、jsonデータへ変換します。
app/views/posts/get_category_children.json.jbuilderjson.array! @category_children do |child| json.id child.id json.name child.name endapp/views/posts/get_category_grandchildren.json.jbuilderjson.array! @category_grandchildren do |grandchild| json.id grandchild.id json.name grandchild.name endビュー
javascriptでカテゴリー選択時の動作を設定します。
:app/javascript/category_post.js $(function(){ function appendOption(category){ var html = `<option value="${category.id}">${category.name}</option>`; return html; } function appendChildrenBox(insertHTML){ var childSelectHtml = ""; childSelectHtml = `<div class="category__child" id="children_wrapper"> <select id="child__category" name="post[category_id]" class="serect_field"> <option value="">---</option> ${insertHTML} </select> </div>`; $('.append__category').append(childSelectHtml); } function appendGrandchildrenBox(insertHTML){ var grandchildSelectHtml = ""; grandchildSelectHtml = `<div class="category__child" id="grandchildren_wrapper"> <select id="grandchild__category" name="post[category_id]" class="serect_field"> <option value="">---</option> ${insertHTML} </select> </div>`; $('.append__category').append(grandchildSelectHtml); } $('#item_category_id').on('change',function(){ var parentId = document.getElementById('item_category_id').value; if (parentId != ""){ $.ajax({ url: '/posts/get_category_children/', type: 'GET', data: { parent_id: parentId }, dataType: 'json' }) .done(function(children){ $('#children_wrapper').remove(); $('#grandchildren_wrapper').remove(); var insertHTML = ''; children.forEach(function(child){ insertHTML += appendOption(child); }); appendChildrenBox(insertHTML); if (insertHTML == "") { $('#children_wrapper').remove(); } }) .fail(function(){ alert('カテゴリー取得に失敗しました'); }) }else{ $('#children_wrapper').remove(); $('#grandchildren_wrapper').remove(); } }); $('.append__category').on('change','#child__category',function(){ var childId = document.getElementById('child__category').value; if(childId != ""){ $.ajax({ url: '/posts/get_category_grandchildren', type: 'GET', data: { child_id: childId }, dataType: 'json' }) .done(function(grandchildren){ $('#grandchildren_wrapper').remove(); var insertHTML = ''; grandchildren.forEach(function(grandchild){ insertHTML += appendOption(grandchild); }); appendGrandchildrenBox(insertHTML); if (insertHTML == "") { $('#grandchildren_wrapper').remove(); } }) .fail(function(){ alert('カテゴリー取得に失敗しました'); }) }else{ $('#grandchildren_wrapper').remove(); } }) });新規投稿ページにカテゴリーセレクトボックスを表示させます。
app/views/posts/new.html.erb<div class="append__category"> <div class="category"> <div class="form__label"> <div class="weight-bold-text lavel__name "> カテゴリー </div> <div class="lavel__Required"> <%= f.collection_select :category_id, @parents, :id, :name,{ include_blank: "選択してください"},class:"serect_field", id:"item_category_id" %> </div> </div> </div> </div>2. カテゴリー検索表示
コントローラー
app/controllers/posts_controller.rbdef top respond_to do |format| format.html format.json do if params[:parent_id] @childrens = Category.find(params[:parent_id]).children elsif params[:children_id] @grandChilds = Category.find(params[:children_id]).children elsif params[:gcchildren_id] @parents = Category.where(id: params[:gcchildren_id]) end end end endビュー
javascriptでどの親カテゴリーにの上にマウスがいるのか、それに属する子カテゴリーや孫カテゴリーを取得しています。
:app/javascript/category.js $(document).ready(function () { // 親カテゴリーを表示 $('#categoBtn').hover(function (e) { e.preventDefault(); e.stopPropagation(); $('#tree_menu').show(); $('.categoryTree').show(); }, function () { // あえて何も記述しない }); // 非同期にてヘッダーのカテゴリーを表示 function childBuild(children) { let child_category = ` <li class="category_child"> <a href="/posts/${children.id}/search"><input class="child_btn" type="button" value="${children.name}" name= "${children.id}"> </a> </li> ` return child_category; } function gcBuild(children) { let gc_category = ` <li class="category_grandchild"> <a href="/posts/${children.id}/search"><input class="gc_btn" type="button" value="${children.name}" name= "${children.id}"> </a> </li> ` return gc_category; } // 親カテゴリーを表示 $('#categoBtn').hover(function (e) { e.preventDefault(); e.stopPropagation(); timeOut = setTimeout(function () { $('#tree_menu').show(); $('.categoryTree').show(); }, 500) }, function () { clearTimeout(timeOut) }); // 子カテゴリーを表示 $('.parent_btn').hover(function () { $('.parent_btn').css('color', ''); $('.parent_btn').css('background-color', ''); let categoryParent = $(this).attr('name'); timeParent = setTimeout(function () { $.ajax({ url: '/posts/top', type: 'GET', data: { parent_id: categoryParent }, dataType: 'json' }) .done(function (data) { $(".categoryTree-grandchild").hide(); $(".category_child").remove(); $(".category_grandchild").remove(); $('.categoryTree-child').show(); data.forEach(function (child) { let child_html = childBuild(child) $(".categoryTree-child").append(child_html); }); $('#tree_menu').css('max-height', '490px'); }) .fail(function () { alert("カテゴリーを選択してください"); }); }, 400) }, function () { clearTimeout(timeParent); }); // 孫カテゴリーを表示 $(document).on({ mouseenter: function () { $('.child_btn').css('color', ''); $('.child_btn').css('background-color', ''); let categoryChild = $(this).attr('name'); timeChild = setTimeout(function () { $.ajax({ url: '/posts/top', type: 'GET', data: { children_id: categoryChild }, dataType: 'json' }) .done(function (gc_data) { $(".category_grandchild").remove(); $('.categoryTree-grandchild').show(); gc_data.forEach(function (gc) { let gc_html = gcBuild(gc) $(".categoryTree-grandchild").append(gc_html); let parcol = $('.categoryTree').find(`input[name="${gc.root}"]`); $(parcol).css('color', 'white'); $(parcol).css('background-color', '#b1e9eb'); }); $('#tree_menu').css('max-height', '490px'); }) .fail(function () { alert("カテゴリーを選択してください"); }); }, 400) }, mouseleave: function () { clearTimeout(timeChild); } }, '.child_btn'); // 孫カテゴリーを選択時 $(document).on({ mouseenter: function () { let categoryGc = $(this).attr('name'); timeGc = setTimeout(function () { $.ajax({ url: '/posts/top', type: 'GET', data: { gcchildren_id: categoryGc }, dataType: 'json' }) .done(function (gc_result) { let childcol = $('.categoryTree-child').find(`input[name="${gc_result[0].parent}"]`); $(childcol).css('color', 'white'); $(childcol).css('background-color', '#b1e9eb'); $('#tree_menu').css('max-height', '490px'); }) .fail(function () { alert("カテゴリーを選択してください"); }); }, 400) }, mouseleave: function () { clearTimeout(timeGc); } }, '.gc_btn'); // カテゴリー一覧ページのボタン $('#all_btn').hover(function (e) { e.preventDefault(); e.stopPropagation(); $(".categoryTree-grandchild").hide(); $(".categoryTree-child").hide(); $(".category_grandchild").remove(); $(".category_child").remove(); }, function () { // あえて何も記述しないことで親要素に外れた際のアクションだけを伝搬する }); // カテゴリーを非表示(カテゴリーメニュから0.8秒以上カーソルを外したら消える) $(document).on({ mouseleave: function (e) { e.stopPropagation(); e.preventDefault(); timeChosed = setTimeout(function () { $(".categoryTree-grandchild").hide(); $(".categoryTree-child").hide(); $(".categoryTree").hide(); $(this).hide(); $('.parent_btn').css('color', ''); $('.parent_btn').css('background-color', ''); $(".category_child").remove(); $(".category_grandchild").remove(); }, 800); }, mouseenter: function () { timeChosed = setTimeout(function () { $(".categoryTree-grandchild").hide(); $(".categoryTree-child").hide(); $(".categoryTree").hide(); $(this).hide(); $('.parent_btn').css('color', ''); $('.parent_btn').css('background-color', ''); $(".category_child").remove(); $(".category_grandchild").remove(); }, 800); clearTimeout(timeChosed); } }, '#tree_menu'); // カテゴリーボタンの処理 $(document).on({ mouseenter: function (e) { e.stopPropagation(); e.preventDefault(); timeOpened = setTimeout(function () { $('#tree_menu').show(); $('.categoryTree').show(); }, 500); }, mouseleave: function (e) { e.stopPropagation(); e.preventDefault(); clearTimeout(timeOpened); $(".categoryTree-grandchild").hide(); $(".categoryTree-child").hide(); $(".categoryTree").hide(); $("#tree_menu").hide(); $(".category_child").remove(); $(".category_grandchild").remove(); } }, '.header__headerInner__nav__listsLeft__item'); });トップ画面にカテゴリー選択ウィンドウをセットします。
app/views/posts/top.html.erb<div class="item-categories"> <h2> カテゴリー一覧 </h2> <%= link_to posts_path, class: "category-button", id: 'categoBtn' do %> カテゴリーから探す <% end %> <div id="tree_menu"> <ul class="categoryTree"> <% @parents.each do |parent| %> <li class="category_parent"> <%= link_to search_post_path(parent) do %> <input type="button" value="<%= parent.name %>" name="<%= parent.id %>" class="parent_btn"> <% end %> </li> <% end %> </ul> <ul class="categoryTree-child"> </ul> <ul class="categoryTree-grandchild"> </ul> </div> </div>3. カテゴリー検索結果表示
ルーティング
カテゴリーをidで区別するため、memberを用いてsearchアクションを定義しています。
config/routes.rbresources :posts do ~略~ member do get 'search' end ~略~ endコントローラー
クリックしたカテゴリーが、親カテゴリー、子カテゴリー、孫カテゴリーのどれなのかで条件分岐しています。
app/controllers/posts_controller.rbdef search @category = Category.find_by(id: params[:id]) if @category.ancestry == nil category = Category.find_by(id: params[:id]).indirect_ids if category.empty? @posts = Post.where(category_id: @category.id).order(created_at: :desc) else @posts = [] find_item(category) end elsif @category.ancestry.include?("/") @posts = Post.where(category_id: params[:id]).order(created_at: :desc) else category = Category.find_by(id: params[:id]).child_ids @posts = [] find_item(category) end end def find_item(category) category.each do |id| post_array = Post.where(category_id: id).order(created_at: :desc) if post_array.present? post_array.each do |post| if post.present? @posts.push(post) end end end end endビュー
app/views/posts/search.html.erb<div class="item-categories"> <h2> カテゴリー一覧 </h2> <%= link_to posts_path, class: "category-button", id: 'categoBtn' do %> カテゴリーから探す <% end %> <div id="tree_menu"> <ul class="categoryTree"> <% @parents.each do |parent| %> <li class="category_parent"> <%= link_to search_post_path(parent) do %> <input type="button" value="<%= parent.name %>" name="<%= parent.id %>" class="parent_btn"> <% end %> </li> <% end %> </ul> <ul class="categoryTree-child"> </ul> <ul class="categoryTree-grandchild"> </ul> </div> </div>参考リンク
https://qiita.com/k_suke_ja/items/aee192b5174402b6e8ca
https://qiita.com/Sobue-Yuki/items/9c1b05a66ce6020ff8c1 https://qiita.com/dr_tensyo/items/88e8ddf0f5ce37040dc8
https://qiita.com/ATORA1992/items/bd824f5097caeee09678
https://qiita.com/misioro_missie/items/175af1f1678e76e59dea
https://qiita.com/Rubyist_SOTA/items/49383aa7f60c42141871
- 投稿日:2020-11-17T11:46:27+09:00
HTML.CSS.JavaScript.Ruby.Rails概要
HTML
HTML(Hyper Text Markup Language)とはWEBページを作成する際に使用されるマークアップ言語である。
WEBページのほとんどにHTMLが使用されている。
HTMLはタグを使いコンピューターに命令を出す事により見出しを付けたり段落を付けたりと、WEBページのレイアウト、構成を形作ることができる。CSS
CSS(Cascading Style Sheets)は先ほどのHTMLと組み合わせて使用する言語である。
CSSは文字の色やサイズ、レイアウトを変えたり、WEBページを装飾する言語である。
HTMLでもWEBページの装飾をすることは出来るが、CSSの役割なので分けて使う必要がある。JavaScript
WEBサイトに動きをつけるためのプログラミング言語である。
具体的には文章や画像を拡大表示したり、より動的なWEBサイトを作ることができる。
サーバーを介さずにブラウザ上で動かすことができる。またこのようなプログラムをクライアントサイド・スクリプトというRuby
Rubyとは日本人であるまつもとゆきひろ氏によって作成されたオブジェクト指向スクリプト言語である。
WEBサイトやECサイトなどの製作、SNS開発など様々なことができる。有名なサイトではぐるなびや食べログなど
Rubyは他の言語に比べシンプルなコードのため開発スピードが早く読みやすいRails
Ruby on RailsとはRubyを使用したフレームワークである。
フレームワークとは雛形のことで、一からプログラミングをしなくても枠組みが用意されているため開発時間を大幅に削減することができる。他にもSinatraやHANAMIなどがある
- 投稿日:2020-11-17T11:24:25+09:00
Rails5.1+puma ローカルのproduction環境でSSL接続する
Rails5.0から5.1にアップグレードし、ローカルでproductionの動作確認しようとしたら、何かとつまずいたので諸問題の解決方法をまとめておきます。
まず、
rails s -e production
を叩いたところ、この接続にはセキュリティの問題があるのでWEBページを表示できません的なエラーが出ました。httpsにしないとダメなようです。SSLで接続する
基本的なやり方は、こちらの記事を参照させていただきました。
Rails5 + pumaのローカル環境でSSL/HTTPSを有効にする上記記事は、opensslで証明書発行してましたが、エラーが出たので証明書の発行をmkcertでやりました。
mkcertでの証明書の発行のやり方は、こちらの記事が参考になりました。
ローカル環境でSSLをオレオレ証明書で行っていて警告が出てる人に朗報まず、上記記事に従ってmkcertで証明書を発行、appフォルダ以下の適当な場所にserver.key、server.crtファイルを置きます。
Rails5 + pumaのローカル環境でSSL/HTTPSを有効にする を参考にpuma.rbを設定。
bundle exec pumactl start -e production
でサーバーを起動。css/jsが表示されなくなった問題の解決
SSLで接続できたものの、
HTTP parse error, malformed request ()
というエラーが出て、application.js、application.cssが読み込まれませんでした。
jsとcssのサーバーがhttp://0.0.0.0:3000 になっていたので、asset_hostの設定を以下のように修正。ローカルと本番環境で変えられるよう、環境変数で定義しました。config/environments/production.rbconfig.action_controller.asset_host = "https://#{ENV['HOST_URI']}"参考
- StackOverflow::Ruby on Railsの本番環境でpublic/assetsが参照できないpublic/images以下のファイルの表示問題解決
上記の対応でcssとjsは出るようになったけれど、public/images以下に置いていた画像ファイルがまだ表示されませんでした。
Rails5.1からは、asset_pipelineでプリコンパイルしない静的なファイルは、ヘルパーで呼び出すときには以下の用に書きます。= image_tag 'hogehoge.png', skip_pipeline: trueしかし、これが設定してあってもダメだったので、別の問題のようです。
対応1
config.public_file_server.enabled
をtrueにconfig/environments/production.rb- config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? + config.public_file_server.enabled = true対応2
assets:precompileをやり直す。# 思い切って一度全部消したければ、先にこちら RAILS_ENV=production bundle exec rake assets:clobber # assets:cleanは古いバージョンのファイルを消す。clobberをやってたらcleanは不要 RAILS_ENV=production bundle exec rake assets:precompile assets:clean上記対応で、assetすべて表示されるようになりました。
参考
- 投稿日:2020-11-17T11:22:48+09:00
Railsを使ったToDoリストの作成(2.hamlの導入と使い方)
概要
本記事は、初学者がRailsを使ってToDoリストを作成する過程を記したものです。
私と同じく初学者の方で、Railsのアウトプット段階でつまづいている方に向けて基礎の基礎を押さえた解説をしております。
抜け漏れや説明不足など多々あるとは思いますが、読んでくれた方にとって少しでも役に立つ記事であれば幸いです。環境
Homebrew: 2.5.10
-> MacOSのパッケージ管理ツールruby: 2.6.5p114
-> RubyRails: 6.0.3.4
-> Railsnode: 14.3.0
-> Node.jsyarn: 1.22.10
-> JSのパッケージ管理ツールBundler: 2.1.4
-> gemのバージョン管理ツールiTerm$ brew -v => Homebrew 2.5.10 $ ruby -v => ruby 2.6.5p114 $ rails -v => Rails 6.0.3.4 $ npm version => node: '14.3.0' $ yarn -v => 1.22.10 $ Bundler -v => Bundler version 2.1.4第2章 hamlの導入
第2章では、Railsでアプリ開発をする時にhamlテンプレートを使えるように設定していきます。
Railsはデフォルトはerbテンプレートが使われていますが、erbテンプレートは使いづらい上に、現場レベルでもあまり使われていないみたいなので、今回の開発においてはhamlテンプレートを使っていきたいと思います。1 hamlの導入
まずは、hamlテンプレートをRailsで使えるようにしていきます。
hamlitというgemとerb2hamlというgemをインストールします。Gemfilegem 'hamlit' group :development do gem 'erb2haml' end?♂️
hamlit
というgemをインストールしてください
?♂️erb2haml
という変換ツールのgemをインストールしてくださいターミナルで
$bundle install
を行えばgemのインストールは完了です。既存のerbファイルをhamlに置き換えるためにはターミナルにて以下のコマンドを実行する必要があります。
iterm$ bundle exec rake haml:replace_erbs⚠︎hamlitをインストールしてエラーが起こってしまった場合は
$rails s
でサーバを立ち上げ直しましょう
以上でRailsの開発においてhamlテンプレートを使うことができるようになりました。
次は、実際にhamlの書き方を見ていきます。2 hamlの基本ルール①
① タグの書き方
erb<header> <p></p> </header>haml%header %p
- hamlでは閉じタグが必要ない
- 入れ子構造にする際はスペースキーを2回押すこと
② class/id/attributeの書き方
erb<header class="header"> <p id="id"></p> <a href="https://haml.com"></a> </header>haml%header.header %p#id %a{href: "https://haml.com"}
- classは
.class名
で指定する- idは
#id名
で指定する- attributeはハッシュで指定する
③divタグの書き方
erb<div class="container"></div>haml.container
- divタグを作成したい時はタグの表記は必要ない。hamlが勝手に察してdivタグを付けてくれる
3 hamlの基本ルール②
①Rubyのコードの埋め込み(画面に表示する場合)
erb<%= board.title %>haml= board.title
- <%=%>を表現する際は
=
を使う②Rubyのコードの埋め込み(画面に表示しない場合)
erb<% Board.all.each do |board| %> <%= board.title %> <% end %>haml- Board.all.each do |board| = board.title
- <%%>を表現する際は
-
を使う- <% end %>はhamlが勝手に付けてくれる
※もしhamlの書き方がわからなくなったら下記のサイトで確認しましょう。
- 投稿日:2020-11-17T11:22:48+09:00
Railsを使ったToDoリストの作成(2.hamlの導入と書き方)
概要
本記事は、初学者がRailsを使ってToDoリストを作成する過程を記したものです。
私と同じく初学者の方で、Railsのアウトプット段階でつまづいている方に向けて基礎の基礎を押さえた解説をしております。
抜け漏れや説明不足など多々あるとは思いますが、読んでくださった方にとって少しでも役に立つ記事であれば幸いです。環境
Homebrew: 2.5.10
-> MacOSのパッケージ管理ツールruby: 2.6.5p114
-> RubyRails: 6.0.3.4
-> Railsnode: 14.3.0
-> Node.jsyarn: 1.22.10
-> JSのパッケージ管理ツールBundler: 2.1.4
-> gemのバージョン管理ツールiTerm$ brew -v => Homebrew 2.5.10 $ ruby -v => ruby 2.6.5p114 $ rails -v => Rails 6.0.3.4 $ npm version => node: '14.3.0' $ yarn -v => 1.22.10 $ Bundler -v => Bundler version 2.1.4第2章 hamlの導入と書き方
第2章では、Railsでアプリ開発をする時にhamlテンプレートを使えるように設定していきます。
Railsはデフォルトはerbテンプレートが使われていますが、erbテンプレートは使いづらい上に、現場レベルでもあまり使われていない(らしい)ので、今回の開発においてはhamlテンプレートを使っていきたいと思います。1 hamlの導入
まずは、hamlテンプレートをRailsで使えるようにしていきます。
hamlitというgemとerb2hamlという変換ツールのgemをインストールします。Gemfilegem 'hamlit' group :development do gem 'erb2haml' end?♂️
hamlit
というgemをインストールしてください
?♂️erb2haml
というgemをインストールしてくださいターミナルで
$bundle install
を行えばgemのインストールは完了です。既存のerbファイルをhamlに置き換えるためにはターミナルにて以下のコマンドを実行する必要があります。
iterm$ bundle exec rake haml:replace_erbs⚠︎hamlitをインストールしてエラーが起こってしまった場合は
$rails s
でサーバを立ち上げ直しましょう
以上でRailsの開発においてhamlテンプレートを使うことができるようになりました。
次は、実際にhamlの書き方を見ていきます。2 hamlの基本ルール①
① タグの書き方
erb<header> <p></p> </header>haml%header %p
- hamlでは閉じタグが必要ない
- 入れ子構造にする際はスペースキーを2回押すこと
② class/id/attributeの書き方
erb<header class="header"> <p id="id"></p> <a href="https://haml.com"></a> </header>haml%header.header %p#id %a{href: "https://haml.com"}
- classは
.class名
で指定する- idは
#id名
で指定する- attributeはハッシュで指定する
③divタグの書き方
erb<div class="container"></div>haml.container
- divタグを作成したい時はタグの表記は必要ない。hamlが勝手に察してdivタグを付けてくれる
3 hamlの基本ルール②
①Rubyのコードの埋め込み(画面に表示する場合)
erb<%= board.title %>haml= board.title
- <%=%>を表現する際は
=
を使う②Rubyのコードの埋め込み(画面に表示しない場合)
erb<% Board.all.each do |board| %> <%= board.title %> <% end %>haml- Board.all.each do |board| = board.title
- <%%>を表現する際は
-
を使う- <% end %>はhamlが勝手に付けてくれる
※もしhamlの書き方がわからなくなったら下記のサイトで確認しましょう。
- 投稿日:2020-11-17T11:20:54+09:00
10の倍数との差を求める
今取り組んだものは
正の整数を入力して、それが10の倍数との差が2以下であるならばTrue、そうでないならFalseを出力するというものだった。以下が自分の回答である。
def near_ten(num) basis = (num + 5) / 10 * 10 difference = (num - basis).abs if difference <= 2 puts "True" else puts "False" end endまず
num
に一番近い10の倍数をbasis
とする。
basis
にふさわしい数字を計算しているのが2行目。basis = (num + 5) / 10 * 10例として、15~24までの整数だと一番近い10の倍数は20であるから、20を基準にしたい。
この整数たちにそれぞれ5を加えると、20~29になる。
この整数たちは10の位が2なので、それぞれ/ 10 * 10
を行うと20が出てきて、基準にしたい数字となる。他の整数でも同じようにうまくいく。
これで
basis
の部分は終了。次に、元々の数字とこの基準との差を計算している。
num - basisこのままでは差がマイナスになることもあるので、絶対値を出すメソッドを使っている。
(num - basis).absあとはこれを代入
difference = (num - basis).absこうして求めた差である
difference
が2以下であれば良いからそれを判定して終了。if difference <= 2 puts "True" else puts "False" end
- 投稿日:2020-11-17T11:08:57+09:00
Rails SecureRandom.alphanumericを使ってテストアカウントのパスワードを生成する
railsでアプリケーションを作成しています。
テストアカウントでのログイン機能を実装したときにエラーが発生したため、自分が行った解決方法を記録として残しておきます。開発環境
Ruby 2.6.5
Rails 6.0.3.3実装内容
以下のコードでテストログイン機能の実装を行いました。
コントローラー
class Users::SessionsController < Devise::SessionsController # ゲストユーザーとしてログイン def new_guest user = User.find_or_create_by!(nickname: 'ゲストユーザー', email: 'user@example.com') do |user| user.password = SecureRandom.alphanumeric end sign_in user redirect_to root_path, notice: 'ゲストユーザーとしてログインしました。' end end
モデル
#半角英数字のみを許可するバリデーションを設定 PASSWORD_REGEX = /\A(?=.*?[a-z])(?=.*?[\d])[a-z\d]+\z/i.freeze validates_format_of :password, with: PASSWORD_REGEX, on: :create, message: 'には半角英字と半角数字の両方を含めて設定してください'開発環境でもエラーが発生することなく、テストも通ったため、本番環境にデプロイをしたところ「Sorry, something went wrong.」が表示されてしまいました。
原因
SecureRandom.alphanumeric
で英字のみのパスワードが発行されていたため。
そもそもSecureRandomはレファレンスでは以下のように説明がされています。安全な乱数発生器のためのインターフェースを提供するモジュールです。 HTTP のセッションキーなどに適しています。
そして
alphanumeric
はSecureRandomモジュールのメソッドの一種であり、ランダムな英数字を生成してくれます。しかし必ずしも英数字混合で生成されるわけではありません。コンソールpry(main)> SecureRandom.alphanumeric => "NNCMHbfUbHRQmbwW"確率は高くはないですが、上記のように英字のみのパスワードが生成されてしまうことがあります。
解決策
class Users::SessionsController < Devise::SessionsController # ゲストユーザーとしてログイン def new_guest user = User.find_or_create_by!(nickname: 'ゲストユーザー', email: 'user@example.com') do |user| user.password = SecureRandom.alphanumeric(10) + [*'a'..'z'].sample(1).join + [*'0'..'9'].sample(1).join end sign_in user redirect_to root_path, notice: 'ゲストユーザーとしてログインしました。' end end力技になってしまいましたが、
[*'a'..'z'].sample(1).join + [*'0'..'9'].sample(1).join
とすることでパスワードの最後に必ず英字+数字の2文字が入るようにして英字のみor数字のみのパスワードが生成されないようにしました。
- 投稿日:2020-11-17T09:47:13+09:00
Rubyによるデザインパターン テンプレートメソッドパターンのメモ
プログラムの設計力向上のため『Rubyによるデザインパターン』を読んでおり、気になるデザインパターンを、1つずつまとめていきます。
今回は、テンプレートメソッドパターンについてまとめました。
テンプレートメソッドパターンについて
基底クラスに不変の部分を記述し、変わる部分はサブクラスに定義するメソッドにカプセル化するパターンです。
変わるものと変わらないものを分離する、という設計の考えに基づいています。
サンプルコード
趣味の筋トレにちなんでサンプルコードを書きます。
ベンチプレスと懸垂はどちらもトレーニングの流れは同じものの、具体的な流れはそれぞれ異なっています。そこで、トレーニングごとの内容を出力するプログラムを書きます。
class Training def initialize(type) @type = type end def start prepare execute cleanup end def prepare if @type == :bench_press puts 'ベンチプレスを始めます' puts 'バーベルをセットします' elsif @type == :tinning puts '懸垂を始めます' puts '踏み台に乗ります' end end def execute puts 'トレーニングをします' end def cleanup puts 'アルコール消毒します' if @type == :bench_press puts 'バーベルを戻します' elsif @type == :tinning puts '踏み台から降ります' end end end呼び出し時は引数にトレーニングの種類を渡します。
実行結果はこのとおりです。bench_press = Training.new(:bench_press) bench_press.start # ベンチプレスを始めます # バーベルをセットします # トレーニングをします # アルコール消毒します # バーベルを戻します tinning = Training.new(:tinning) tinning.start # 懸垂を始めます # 踏み台に乗ります # トレーニングをします # アルコール消毒します # 踏み台から降りますインスタンス変数 @type によって if 分で分岐をしています。トレーニングが2種類ならばよいかもしれませんが、トレーニング数が増えるたびにこの条件分岐は増え、1つのメソッドは長く複雑になってしまいます。
次に Template Method パターンを適用して書き換えた場合です。
class Training def start prepare execute cleanup end def prepare end def execute puts 'トレーニングをします' end def cleanup puts 'アルコール消毒します' end end class BenchPress < Training def prepare puts 'ベンチプレスを始めます' puts 'バーベルをセットします' end def cleanup puts 'バーベルを戻します' super end end class Tinning < Training def prepare puts '懸垂を始めます' puts '踏み台に乗ります' end def cleanup super puts '踏み台から降ります' end end呼び出し時はサブクラスのインスタンスを生成しており、テンプレートメソッドパターン適用前はトレーニングの種目を引数で渡していましたが、それがなくなっています。
実行結果はこのとおりです。bench_press = BenchPress.new bench_press.start # ベンチプレスを始めます # バーベルをセットします # トレーニングをします # アルコール消毒します # バーベルを戻します tinning = Tinning.new tinning.start # 懸垂を始めます # 踏み台に乗ります # トレーニングをします # アルコール消毒します # 踏み台から降ります基底クラス Training にはトレーニングの骨子だけを定義し、具体的なトレーニングの内容はサブクラスで定義しています。
新たにサブクラスを生成する必要になったときは、基底クラスは変えずにサブクラスだけを変えれば良くなり、変更に強くなりました。
まとめ
テンプレートメソッドパターンは、継承を使ったオブジェクト指向らしいデザインパターンでした。
個人的にはデザインパターンの中で一番使用頻度が高いのではと思いますので、使いこなせるようになりたいです。
- 投稿日:2020-11-17T09:47:13+09:00
【Rubyによるデザインパターン】テンプレートメソッドパターンのメモ
プログラムの設計力向上のため『Rubyによるデザインパターン』を読んでおり、気になるデザインパターンを、1つずつまとめます。
今回は、第一弾ですがテンプレートメソッドパターンについてまとめました。
テンプレートメソッドパターンについて
基底クラスに不変の部分を記述し、変わる部分はサブクラスに定義するメソッドにカプセル化するパターンです。
変わるものと変わらないものを分離する、という設計の考えに基づいています。
サンプルコード
趣味の筋トレにちなんでサンプルコードを書きます。
ベンチプレスと懸垂はどちらもトレーニングの流れは同じものの、具体的な流れはそれぞれ異なっています。そこで、トレーニングごとの内容を出力するプログラムを書きます。
まずはテンプレートメソッドパターン適用前のコードです。
class Training def initialize(type) @type = type end def start prepare execute cleanup end def prepare if @type == :bench_press puts 'ベンチプレスを始めます' puts 'バーベルをセットします' elsif @type == :tinning puts '懸垂を始めます' puts '踏み台に乗ります' end end def execute puts 'トレーニングをします' end def cleanup puts 'アルコール消毒します' if @type == :bench_press puts 'バーベルを戻します' elsif @type == :tinning puts '踏み台から降ります' end end end呼び出し時は引数にトレーニングの種類を渡します。
実行結果はこのとおりです。bench_press = Training.new(:bench_press) bench_press.start # ベンチプレスを始めます # バーベルをセットします # トレーニングをします # アルコール消毒します # バーベルを戻します tinning = Training.new(:tinning) tinning.start # 懸垂を始めます # 踏み台に乗ります # トレーニングをします # アルコール消毒します # 踏み台から降りますインスタンス変数 @type によって if 分で分岐をしています。トレーニングが2種類ならばよいかもしれませんが、トレーニング数が増えるたびにこの条件分岐は増え、1つのメソッドは長く複雑になってしまいます。
次に Template Method パターンを適用して書き換えた場合です。
class Training def start prepare execute cleanup end def prepare end def execute puts 'トレーニングをします' end def cleanup puts 'アルコール消毒します' end end class BenchPress < Training def prepare puts 'ベンチプレスを始めます' puts 'バーベルをセットします' end def cleanup puts 'バーベルを戻します' super end end class Tinning < Training def prepare puts '懸垂を始めます' puts '踏み台に乗ります' end def cleanup super puts '踏み台から降ります' end end呼び出し時はサブクラスのインスタンスを生成しており、テンプレートメソッドパターン適用前はトレーニングの種目を引数で渡していましたが、それがなくなっています。
実行結果はこのとおりです。bench_press = BenchPress.new bench_press.start # ベンチプレスを始めます # バーベルをセットします # トレーニングをします # アルコール消毒します # バーベルを戻します tinning = Tinning.new tinning.start # 懸垂を始めます # 踏み台に乗ります # トレーニングをします # アルコール消毒します # 踏み台から降ります基底クラス Training にはトレーニングの骨子だけを定義し、具体的なトレーニングの内容はサブクラスで定義しています。
新たにサブクラスを生成する必要になったときは、基底クラスは変えずにサブクラスだけを変えれば良くなり、変更に強くなりました。
まとめ
テンプレートメソッドパターンは、継承を使ったオブジェクト指向らしいデザインパターンですね。
個人的にはデザインパターンの中で一番使用頻度が高いのではと思いますので、使いこなせるようになりたいです。
- 投稿日:2020-11-17T02:10:32+09:00
Ruby で URL を作るときのエスケープ処理
https://xxxx
といった文字列のURLに、 クエリパラメータをつけて URL を作る場合、 クエリパラメータは 特別な形に変換する必要があります。JavaScript でいう
encodeURIComponent
を実行してやる必要があります。
これにより+
は%2B
に、&
は%26
になります。基本
Ruby では
CGI.escape
を使います。require 'cgi' CGI.escape('+ &') # => "%2B+%26"使用例
CGI.escape
を使って URL を組み立ててみます。
ハッシュを使うなどいろいろと改善できるところはありますが、ここではシンプルなパターンを記載します。"https://samole.com?key={CGI.escape(value)}"
key
に特殊な文字を使うことはまずないと考えてvalue
のみ escape をしています。
key
も escape する場合は、
https://github.com/rails/rails/blob/3-0-stable/activesupport/lib/active_support/core_ext/object/to_query.rb が参考になります。その他参考
- 投稿日:2020-11-17T02:04:00+09:00
Rubyの継承と委譲
継承と委譲の使い分けについてあまりわかっていないのでまとめました。
継承
他のクラスから機能を引継ぎ、新たにクラスを作成することです。
例
Baseクラスからすべのたメソッドを引継ぎ、Fooクラスを作成する
class Base def message 'ok' end end class Foo < Base; end foo = Foo.new puts foo.message # => ok委譲
特定の処理(責任)を他のクラスのメソッドに委ねることです。
例
BaseクラスのmessageメソッドをFooクラスにmessageメソッドを委ねる
class Base def message 'ok' end end require 'forwardable' class Foo extend Forwardable attr_reader :mess def initialize @mess = Base.new end def_delegator :mess, :message end foo = Foo.new puts foo.message # => ok継承を使う場面
役割が同じ
駆け出しエンジニアも、経験が豊富なつよつよエンジニアも、プログラマーという役割は一緒です。
なので、Progurammerクラスを継承し、機能をそのまま引き継ぐようにします。class Programmer def initialize(type, langages) @type = type @langages = Array(langages) end def type_is puts "私は#{type}です。" end def langage_is str = '' langages.each do |lang| str += "#{lang} " end puts "私は#{str}の言語を扱えます" end def klass_is str = case self.class.name when 'TsuyoTsuyo' '強強エンジニア' when 'Kakedashi' '駆け出しエンジニア' end puts "私は#{str}です。" end private attr_accessor :type, :langages end class TsuyoTsuyo < Programmer; end class Kakedashi < Programmer; end tsuyo = TsuyoTsuyo.new('Backend', %w[C C++ Python Go Ruby]) tsuyo.type_is tsuyo.langage_is tsuyo.klass_is kake = Kakedashi.new('Frontend', %w[HTML CSS jQuery]) kake.type_is kake.langage_is kake.klass_is委譲を使う場面
役割は異なるが一部の機能を使いたい
プログラマーとデザイナーでは役割が異なります。なのでこの場合は委譲を使い、
type_is
メソッドのみをProgrammerクラスより譲り受けるようにします。require 'forwardable' class Programmer def initialize(type, langages) @type = type @langages = Array(langages) end def type_is puts "私は#{type}です。" end private attr_accessor :type, :langages end class Designer extend Forwardable def initialize(type, skills) @programmer = Programmer.new(type, nil) @type = type @skills = Array(skills) end def_delegator :@programmer, :type_is private attr_accessor :type, :skills end class IkeIke < Designer; end ikeike = IkeIke.new('WebDesigner', %w[HTML CSS Adobe JavaScript]) ikeike.type_is # このtype_isメソッドは`Programmer` classから譲り受けたメソッドまとめ
ここまでお読みいただきありがとうございます。
自分のコードを見直す機会になればと思います。自分は普段はRailsでwebサービスの開発を行っているが、委譲を使う場面があまりない気がします。
今までは何でもかんでも「継承!!」としていた自分がいたのも事実です。
継承関係がいいのか、委譲を使った方がいいのかの判断は難しいですが、今後は委譲を利用するという選択肢も含めた実装をしていこうと思いました。
- 投稿日:2020-11-17T01:30:59+09:00
C10K 問題を解決する Ruby のワンライナー
C10K 問題とは
i18n と L10N は,それぞれ internationalization と localization の略称だ。
internationalization は綴りが長いので,「i」と「n」の間に 18 文字あるというところから「i18n」という表記が生み出された。L10N も同様。
なお,「i18n」が小文字で「L10N」が大文字なのはタイプミスではない。
参考:国際化と地域化 - Wikipediaでは,「c」で始まり「k」で終わり,間に 10 文字ある英単語としてどんなものがあるだろうか。
これが C10K 問題である1。コード
まず,英単語が大量に集められたファイルがあるとよいのだが,幸い,Linux や macOS にはそういうファイルが存在する。
/usr/share/dict
というディレクトリー内にたいていそういうファイルがあるので覗いてみてほしい。
私の macOS では/usr/share/dict/words
が英単語リストだった。
1 行に 1 単語。これがただひたすら並んでいるだけのファイルだ。数えたら全部で 23 万語以上あった。ここから正規表現で抜き出せばいい。Ruby なら 1 行で書ける。
puts IO.foreach("/usr/share/dict/words").grep(/^c.{10}k$/i)IO.foreach にファイルパスを与えると,行とごとにブロックを評価するが,ブロックを与えない場合は Enumerator を返す。
それを Enumerable#grep でフィルタリングしてやるだけ。
grep
に正規表現を与えると,マッチした文字列だけからなる配列ができる。結果は以下のとおり。
concertstuck countercheck counterprick counterstock Czechoslovakな,なんじゃこれは?
Czechoslovak は名詞で「チェコスロバキア人」,形容詞で「チェコスロバキア(人・語)の」だが,あとの単語は見たこともないものばかり。macOS 付属の『ウィズダム英和辞典』にも載っていない。concertstuck はどうもドイツ語の Konzertstück と関係ありそうだ。
Konzertstück は音楽用語で「小協奏曲」などと訳されたり,外来語として「コンツェルトシュテュック」と呼ばれたりするようだが,音楽に疎いのでよく分からん。残りの 3 語はすべて counter- が接頭辞になった複合語だ。
ネットで調べてもいまひとつよく分からないけど,「countercheck」は「対抗手段」とか「ダブルチェック」(二重に点検すること)というような意味があるっぽい。
「counterstock」はチケットなどの半券を指すぽい。
「counterprick」はなんだろうな?
この記事はジョーク記事です。本当の C10K 問題はまったく違うものです。ごめんなさい。 ↩
- 投稿日:2020-11-17T01:23:27+09:00
【Rails】カラムの追加・データ型/カラム名の変更
はじめに
カラムの追加・カラム名、テータ型の変更をする手順として代表的なのが、
1⃣rails db:migrate:status
で現状の確認
2⃣マイグレーションファイルの作成or追加コマンドをターミナルに入力(行いたい操作でコマンドは異なります)
3⃣マイグレーションファイルに変更内容を記述(追加の場合は内容の確認だけになります)
4⃣rails db:migrate
だと思います(違ってたらすみません)。もちろんこれも正解だと思います。(rails db:rollback使うこともあります)毎度コマンドを調べたり誤字があるとテーブルが謎に消えてしまうことがあるかと思います、、、レコードの内容が消えていいような場合は楽な方法があるそうで、、(ご存じの方はすみません)
※レコードの内容が少なかったり消えても良い状態、seeds.rbで必要なデータを作成しているとき向けの内容です。
上記の楽な方法(複数同時変更も可能)
1⃣
rails db:migrate:status
で現状の確認(問題なければ2⃣へ)
2⃣既に作成してあるマイグレーションファイルの変更したい部分を直接書き換えます(追加の場合は書き加えてください)
3⃣rails db:migrate:reset
を実行して完了
schema.rbを確認してみてください!
rails db:migrate:reset
を行うことで、既存のマイグレーションファイルを全て利用して再度テーブルを作成しなおします。なので、書き換えた部分や追記した部分も全て反映されます(誤字でerrorが出ても大体直ります)
ただし、場合によって使い分ける必要があるのでレコードを削除したくない場合は丁寧にコマンドを打ち込みましょう。例
1⃣状態確認
rails db:migrate:status
実行
コマンド実行時に出力された分だけマイグレーションファイルが存在して、この後の工程でそれらを一斉に再度migreateしなおすようなイメージになります。ターミナルvocstartsoft:~/environment/bookers2-task (master) $ rails db:migrate:status #ここで実行 database: /home/ec2-user/environment/bookers2-task/db/development.sqlite3 Status Migration ID Migration Name -------------------------------------------------- up 20200830060820 Devise create users #upなのかdownなのか状態が出てきます。 up 20200830062142 Create books #upは基本的にmigrate済みということになります up 20201101080413 Create book comments #以下省略2⃣マイグレーションファイルに追記・変更
追記・変更したい内容の含まれるマイグレーションファイルをdb/migrateフォルダ内から持ってきます。
今回は投稿に使うtitleカラムとbodyカラム共にデータ型がおかしいのでそこを修正します(booleanとかどんなミスだよ。)db/migrate/20201115102020_create_books.rbclass CreateBooks < ActiveRecord::Migration[5.2] def change create_table :books do |t| t.integer :title #←integerをstringにしたい t.boolean :body #←booleanをtextにしたい t.integer :user_id t.timestamps end end end書き換えます(追加したい場合は追記してください)
db/migrate/20201115102020_create_books.rb#↑省略 t.string :title #integer→string t.text :body #boolean→text #↓省略3⃣rails db:migrate:resetを実行して一気にDBを削除→作成します
ターミナル$ rails db:migrate:resetターミナルに普段のrails db:migrate時と同じような内容が出力されていればおそらく問題ありません。
schema.rbを確認してちゃんと変更・追加ができているか確認してみましょう。
くどいようですが、作成していたuser情報(名前とか)や投稿内容などは消えているはずなので、注意してください。おまけ
rails db:resetとrails db:migrat:resetの違いについて
どちらのコマンドもDBを削除して作成しなおすコマンドですが、大きな違いがあるようです。
rails db:reset
はDBをdropして、現在の scheme.rb をロードして DB を作り直します。db/migrate/~.rb は使われないようです。
rails db:migrate:reset
はDBをdropした後、通常通りのマイグレート(db:migrate)が行われます。つまりdb/migrate/~.rb が古い順から全て実行されます。
最後に上記の方法以外のカラム追加記事はこちら
参考にもなったし、逆にrails db:migrate:resetできなかったときの内容の記事はこちら
長くなりましたが、個人的にDB周りは意外とややこしいエラーが多い気がします、この記事で解決すると嬉しいです!(改善されない方はすみません、、)