- 投稿日:2020-02-06T22:38:53+09:00
【Ruby】scanとmatchの違い
scanとmatchの違いってなんなんだろう
Rubyの入門書のコード例を実行する中で、ある文字列から正規表現にマッチした文字列を取得する方法としてscanとmatchというメソッドを学びました。
両者は同じようにある文字列から正規表現にマッチする文字列返すというメソッドですが、入門書の中でもこの二つのメソッドは度々登場しているのですが、どのように使い分けたらよいのかわからなくなってしまいました。そこで、今回はscanとmatchの違いをまとめてみました。※この記事ではMacにインストールされたRuby 2.6.3を使用しています。
実行環境はRubyファイルをターミナルで実行しています。実際にscanとmatchを実行してみる
matchメソッドを実行してみる
実際に、"123-4567-89"という文字列から数字だけを正規表現を用いてmatchメソッドで取得してみます。
p '123-4567-89'.match(/(\d+)-(\d+)-(\d+)/) #=> #<MatchData "123-4567-89" 1:"123" 2:"4567" 3:"89">scanメソッドを実行してみる
p '123-4567-89'.scan(/(\d+)-(\d+)-(\d+)/) #=> [["123", "4567", "89"]]両者の比較
まず、matchメソッドで実行した場合は以下のように、マッチした文字列をMatchDataオブジェクトの形式で返していることがわかります。
Ruby2.6の公式ドキュメントにも、「指定された文字列 str に対して位置 pos から自身が表す正規表現によるマッチングを行います。マッチした場合には結果を MatchData オブジェクトで返します。マッチしなかった場合 nil を返します。」と説明があります。
引用元: https://docs.ruby-lang.org/ja/2.6.0/class/Regexp.html#<MatchData "123-4567-89" 1:"123" 2:"4567" 3:"89">
また、scanメソッドで実行した場合は以下のように、マッチした文字列を配列として返していることがわかります。
Ruby2.6の公式ドキュメントにも、「マッチした部分文字列の配列を返します。」と説明があります。
引用元: https://docs.ruby-lang.org/ja/2.6.0/class/String.html#[["123", "4567", "89"]]
以上から、matchメソッドはMatchDataオブジェクトの形式で、scanメソッドは配列で返しているという違いがあることがわかりました。
matchはRegexpクラスのインスタンスメソッド、scanはStringクラスのインスタンスメソッドであるという違いがあります。
matchメソッドの実行結果を配列で返す
scanメソッドのように実行結果を配列で返すにはvalues_atメソッドを使用します。values_atメソッドは引数にキーに対応する値指定することで、指定したキーに対応する値を配列として返すというメソッドです。
'123-4567-89'.match(/(\d+)-(\d+)-(\d+)/).values_at(1, 2, 3) #=> ["123", "4567", "89"]ちなみに、scanメソッドの実行結果が正規表現の中で()を使っているため配列の中の配列として返していますが、splat展開することで上の結果と同じ形で配列で返すことができます。
*'123-4567-89'.scan(/(\d+)-(\d+)-(\d+)/) #=> ["123", "4567", "89"]
- 投稿日:2020-02-06T22:20:46+09:00
【Rails】DBのカラムにインデックスを付与する/しない場合での速度比較をしてみた
はじめに
RailsでDBにインデックスを付与するメリットを体感するために、試験的に1万件のレコードを作成し、インデックスの有無で実行速度の比較をしてみました。
今回のケースではおよそ18%の速度UPという結果が出ています
※サクッと作成するため、DBはRailsのデフォルトであるSQLite3を使用しました。2020/02/07追記
10万件のレコードだと20倍の差が出ていることを確認し、結果を追記しました。
@error_401 さんありがとうございます。環境
OS: macOS Catalina 10.15.3 zsh: 5.7.1 Ruby: 2.6.5 Rails: 6.0.2.1事前準備
rails_newする$ rails new sampleapp
sampleappディレクトリに移動$ cd sampleappdb_createする$ rails db:create
Postモデルの作成$ rails g model Post name:string description:string
db_migrateする$ rails db:migrate
事前に1万件のデータを登録irb(main):001:0> 10000.times do |p| Post.create(name: "sample#{p}", description: "this is sample!#{p}") end準備が整ったら、実験開始です!
実行するコマンド
nameカラムを検索対象にし、
find_by
メソッドでランダムな名前のレコードを1万回変数post
に代入するという内容です。
test.rb
を作成します。sampleapp/test.rbrequire 'benchmark' num_iteration = 10000 Benchmark.bm 10 do |r| r.report "no-index" do num_iteration.times do post = Post.find_by(name: "sample#{rand(1..1000)}") end end end1.インデックスなしの場合
rails_consoleで実行irb(main):001:0> require './test.rb'インデックスなしuser system total real 2.400283 0.353093 2.753376 ( 2.840555) # 1回目 2.456253 0.352958 2.809211 ( 2.914810) # 2回目 2.527288 0.383071 2.910359 ( 2.999960) # 3回目測定の対象はuserの数値にします。
3回の平均はおよそ2.46秒でした。
2.インデックスありの場合
ターミナル$ rails g migration add_index_to_post
XXXXXXXXXXXXXXXX_add_index_to_post.rbclass AddIndexToPost < ActiveRecord::Migration[6.0] def up add_index :posts, :name end def down remove_index :posts, :name end endターミナル$ rails db:migrate
これでインデックスが
name
カラムに付与されたので、効果測定です。再度
test.rb
を実行します。rails_consoleで実行irb(main):001:0> require './test.rb'レコードありuser system total real 2.016621 0.349745 2.366366 ( 2.455184) # 1回目 2.037209 0.333974 2.371183 ( 2.465041) # 2回目 2.039167 0.327804 2.366971 ( 2.471504) # 3回目3回の平均はおよそ2.03秒!
結論:およそ18%の速度UP
今回の単純なケースでは、およそ18%検索速度が上がりました!
もちろん
test.rb
の内容によって異なることもあると思いますが、指標の一つとして使えるのではないかと思います。以上です!
2020/02/07追記 レコード10万件の場合
@error_401 さんよりコメントを頂き、早速レコードを増やした場合にどうなるか検証してみました!
検索条件の変化
- レコード数:10万件
- 検索条件:
name
カラムの検索条件をsample1
〜sample100000
へ変更。(回数は1万回で変更なし)結果:インデックスなし
インデックスなしuser system total real 39.969663 14.484736 54.454399 ( 54.855391) # 1回目 40.167824 14.695771 54.863595 ( 55.215927) # 2回目 40.057479 14.542640 54.600119 ( 54.955958) # 3回目3回の平均はおよそ40.05秒
結果:インデックスあり
インデックスありuser system total real 2.007433 0.391915 2.399348 ( 2.546632) # 1回目 1.989761 0.376209 2.365970 ( 2.450146) # 2回目 2.029753 0.386575 2.416328 ( 2.566732) # 3回目3回の平均はおよそ2.00秒!
10万件のレコード数だとなんと20倍速度が早くなりました!
ビックリしたのは、インデックスありだと1万件のときとほとんど数字が変わらないこと。
これはすごいおわりに
最後まで読んで頂きありがとうございました
どなたかの参考になれば幸いです
参考にさせて頂いたサイト(いつもありがとうございます)
- 投稿日:2020-02-06T20:12:15+09:00
中規模以上レガシーRailsシステムのリファクタリング方針
前置き
ここ一年、現職において0→1フェーズからすくすく育ってきたRailsプロジェクトのバックエンド全般を任されており、リファクタリング方針において、ある程度いい感触を掴みつつあるので、その知見を共有してみようってことで書いてみました。
現状
- 一連のビジネス要求に対する判断や分岐、データアクセスといったドメインロジックが、controllerや、方々のmodelに跨って定義されていることによるドメインロジックの見通しの悪さ
- データアクセスと判断や分岐といった処理がmodelに一緒くたに実装されているため、テストコードの実装が難解である
- テストコードの実装が難解であるため、テストの実装を諦め、人力動作確認でリリースまで進めるが、高確率で予期せぬデグレが発生する
- リリース後にデグレ対応に追われるため、スムーズに次の開発へと移行できない
考察
テストがないことに起因した既存実装の考慮漏れによるリリース後のデグレに一番時間が割かれてしまうため、まずはテストコードを書こうと思い辺りました。
ただ、既存の実装のままだと、そのテストコードが書きづらいため、テストを実装していく前に、アプリケーションコードの設計改善が必要だと感じ、今のプロダクトの規模に合わせて、中規模以上でのRailsシステムにおけるテストが書きやすい設計を模索していく必要があります。設計改善
ここでは具体的にどう改善していったのか(改善していこうとしているのか)を説明していきます。
現状は基本的には説明しやすくするために Skinny(かもしれない?) Controller, Fat Model の基本的なRails wayに則って実装されているとします。
それを以下の図のようなアーキテクチャへと変更していきました(一部まで適用できていない願望も含まれています)。
基本的には上図の通りに特に変更が発生しやすかったり、ビジネス的に重要度の高い部分からこちらの設計に実装を改善している段階です。
各層の役割
ここからは各層の役割について説明していきます。
上の図からなんとなく察している方もいらっしゃると思いますが、クリーンアーキテクチャの思想をRailsに合わせた形で表現していこうと試みています。Presentation層
Controller
ここは、PCやスマホの画面といった各Clientからのリクエストを受け取り、後述するusecaseにデータを渡します。
usecaseにデータを渡し、usecase以降で行われた処理の結果をレスポンスとしてて返却することと、渡されてきたデータのバリデーションにのみ責任を負ってもらうようにしています。Domain層
ビジネス要求にたいする判断や分岐のロジックを実装している層です。
usecase
controllerから渡されてきた値に対し、domainとdaoを組み合わせてビジネスロジックを表現しています。
ここで一番注意したいのは、判断や分岐、データアクセスといった処理は調節定義せず、
判断や分岐→domainで定義
データアクセス→daoで定義
といったように処理の実態はそれぞれ別の層で定義することを鉄則としています。なぜそうしたを説明しますと、usecaseで判断や分岐、データアクセスを一緒くたに実装してしまうと、今までmodelに定義されていた、データアクセスと密結合になってしまったビジネスロジックの実装箇所が、ただmodelからusecaseに移動されただけで、データアクセスと密結合になっているが故に、テストを書くことの困難さの解決にならなくなってしまうからです。
domain
ここにビジネス要求における判断や分岐といった、データアクセスを伴わないビジネスロジックをピュアなRubyのクラスとして実装していきます。
もちろん、手続き型ではなく、OOPによる再利用性や、変更容易性といったメリットを享受した形での実装が行っていくため、今後システムとして質の良い実装状態を担保していくためには、ここでの実装力の勝負になってくるかと思っています。dao
DAOとは(Data Access Object)の略です。
ビジネス要求に基づく、データ(主にRDB)への参照や、永続化を表現していく層になります。データの参照や永続化は、modelでも定義してくことが出来るのですが、modelはRDBのテーブルと1対1で紐づくため、システムの中心となるmaster系のテーブルに紐づくmodelがデータの参照や更新のみでみるみる太っていくことが予想されるため、こういった層に切り出そうといった考えに落ち着くました。
では、modelは何をすればいいのかと言いますと、ActiveRecordの場合、リレーションを定義しないと、複数テーブルをjoinするようなデータ参照が行えないため、各modelへのリレーションの定義と、そのmodel単体で済むデータアクセスであればmodelに持たせてもいいのかな? と思っています。
Data層
model
上述した通り、各modelへのリレーションの定義と、そのそのmodel単体で済むデータアクセスのみを表現させます。
other data source
外部で持つデータとのやり取りをこの層に閉じ込めます。
よくある部分としては、DynamoDBやRedisといったデータソースとのやり取りがあるかと思います。
後は、非同期で処理させたいjobの起動や、イベント駆動での連携があるシステムでは、イベントの発火等もこの層に閉じ込められるかと思います。今後の課題
現在はまだまだ既存の辛いままの実装が多く残っているのですが、全ての作業を止めてこの改善にだけ時間を費やすことはできないので、一番故障率が高く、ビジネス的にも重要度の高いところから順次適用を進めています。
インパクトの大きい部分から適用を進め、変更コストが下がり、デグレの発生頻度も下がってきた暁には、そこで空いたリソースを費やして、改善活動を加速していけるのではと考えています。
後、割とリファクタしたと思っても、既存実装の考慮漏れがあって事故ります。
変更しやすく実装し直しているはずなので、事故っても安易に切り戻しに走らず、例え一旦は切り戻したとしても、事故った部分を考慮した変更をリファクタ先の実装に加えていけばよい、加えていくべきだと思っています。
また、改善スピードを増すためには、同じように改善が行えるメンバーの育成についても考えていかなくていけないなと感じています。最後に
本来であれば、中規模以上に成長したシステムは、Railsから卒業して中長期での開発手法であるDDD等と親和性の高い言語やFWに乗り換えていくべきだと思うのですが、スタートアップのような、小さな組織で効率よく開発を進めたいといったニーズと、小さいサービスを最大速度で実装できるためのRailsの犠牲的アーキテクチャの親和性が高く、そのままプロダクトも当たって成長してきたが、リプレースのためのリソースが取れず、Railsで保守や追加開発をせざるを得ないといった組織は、少なからず存在していると思います。
今回のリファクタリング方針が、そういったエンジニア達の助けに少しでも貢献できれば幸いです。
- 投稿日:2020-02-06T19:59:19+09:00
【Rails】railsブログサイトの練習で躓いたところメモ
railsチュートリアル+αでブログサイトを作成していて躓いたところをまとめました。
環境
- ruby on rails
- mysql
- redis
- docker
- macOS
作ってみて
railsのブログアプリを作ること自体はそこまで難しいものではありませんでした。
ただデータベース周りがややこしくてそこにかなり時間をとられてしまったことが今回の反省点です。Rails deviceを使ってユーザー関連機能を作る
ブログアプリにまず必要なのはユーザー関連のあれこれです。Railsはその辺かなり楽に作れるdeviceというgemがあります。
deviceを利用するとログイン認証やアクセス制限などが簡単に実装出来ます。今まで練習も兼ねてユーザー関連は自分で作ってたのですが、一度こういった機能を知ってしまうと戻れなくなってしまいますね。
ここはそこまで詰まらなかったので以下の参考サイトを見れば大丈夫だと思います。
view関連なども「Rails device view」とかで調べたらたくさん出てきます。
【https://qiita.com/Hal_mai/items/350c400e8763ce0487a3】
【https://www.pikawaka.com/rails/devise】
【https://qiita.com/Hal_mai/items/350c400e8763ce0487a3】
Rails scaffoldを使って投稿機能を作る
投稿機能はRailsのscaffoldが便利です。勝手にMVCを作ってくれます。
これも以下の記事を見たら出来るので割愛します。
【https://techacademy.jp/magazine/7204】
注意点
ここで1つ注意点と言いますか、知っておいた方が良いなと個人的に思ったことを書きます。
先にあげたdeviceとscaffoldは大変便利な代物で、私みたいな初心者でも簡単に実装できてしまうものなのですが、これらはあくまで時短や効率化のためにあるものだと思うので初心者が最初から多用するのは危ないと思いました。
私はこのブログ練習サイトの前に3つほどscaffoldとdeviceなしで1から似たようなものを作って練習しているのですが、その練習がなかったらいきなりこれを使っても結局何をしてくれてるのか分からずじまいだったと思います。
分からないのに実装出来ちゃうからわかった気になってしまう危険性があるので、まずはMVCの理解を深めるためにもプロゲートやドットインストールなどの基本的なサイトを見て仕組みをなんとなくでも理解しながら進めていくのがいいのではないかと思いました。
現在のユーザーとアクセス制限
current_user
deviceにはオプションとしてcurrent_user(現在のユーザー)なるものがついています。
例えば「投稿者が他の投稿者の記事の編集や削除を出来ないようにする」といった時に便利な機能です。
今回は投稿の編集と削除を投稿者以外できないようにしたかったので、posts_controller.rbに以下のように記述しました。
posts_controller.rbbefore_action :ensure_correct_user, only: [:edit, :update, :destroy] 省略 private 省略 def ensure_correct_user if current_user.id!=@post.user.id flash[:notice]="Not yours" redirect_to(posts_path) end endprivate以下でensure_correct_userを定義して、before_actionでcurrent_user以外が使えないようにアクションに対して適応させています。
@post.user.idは投稿者のユーザーidのことです。これとcurrent_user.idが違ったら編集(update,edit)も削除(destroy)もできないですよって感じです。
user_signed_in?
user_signed_in?はユーザーがログインしているかどうかを確かめます。
例えば「ログインしている時としていない時で表示内容を変えたい」といった時に使える機能です。
今回はviewにこんな感じで書きました。
show.rb<% if user_signed_in? %> <li class="nav-item active"><%= link_to("新規投稿", new_post_path,{class:"nav-link"}) %></li> <li class="nav-item active"><%= link_to("ログアウト", destroy_user_session_path,{method: :delete,data:{confirm: "ログアウトしますか?"},class:"nav-link"}) %></li> <% else %> <li class="nav-item active"><%= link_to("新規登録", new_user_registration_path,{class:"nav-link"}) %></li> <li class="nav-item active"><%= link_to("ログイン", new_user_session_path,{class:"nav-link"}) %></li> <% end %>user_signed_in?していたら新規投稿・ログアウトを表示、していなかったら新規登録とログインを表示、という単純なものです。
これらもdeviceを使った機能なので基本的に書くだけで簡単に実装出来てしまうのですが、もし使わない場合だとインスタンス変数@current_userを定義するところから始めなければいけません。今回はそこは割愛しますが、その辺の仕組みもまた理解を深めるためにも記事にできたらと思います。
ユーザーと投稿の紐付け
最初に躓いたのはここでした。ユーザーと投稿を用意できたはいいがこれをどうやって紐付けるかが問題です。
私はプロゲートに倣ってPostモデルにuser_idカラムを追加しました。記事を投稿するときにuser_idカラムにcurrent_user.idを入れて紐づけるといった感じです。
posts_controller.rbでscaffoldで自動生成されたpost_paramsのpermitにuser_idを追加します。
posts_controller.rbdef post_params params.require(:post).permit(:title, :content, :user_id) endこれでuser_idに値が届くようになります。
続いてapp/views/posts/_form.html.erbではuser_idを送信できるように以下のように変更します。
app/views/posts/_form.html.erb<div class="field"> <%= form.label :記事内容 %> <%= form.text_area :content,value:@post.content %> <%= form.hidden_field :user_id, value: current_user.id %> </div>form.hidden_fieldを設置して、こっそりuser_idテーブルにvalueに設定したcurrent_user.idを送ります。これで投稿とユーザーの紐付けはおkです。
redisの導入
ブログの形になったので最後にランキングを作ります。
正直ここが1番躓きました。というのも、redisの基本コマンドとかはなんとなく分かっていたのですが具体的にどういった時に使うのかわからなくてそもそもイメージがちゃんと出来てなかったからです。
今回使うredisはブログサイトでよくあるランキングを表示するために使います。
他にはセッション管理などに使っているのが調べてたら多く見られました。
まずRailsとredisの接続からしないといけないのですが、ここで2日くらい躓きました。色んなサイトを見まくって試したけど全然繋がらない状態が続き地獄でした。
結論から申し上げると、基本的にredisはlocalhost:6379に繋ぐのが普通なのですが、開発段階だとRails自体をlocalhostに繋いでるので混同しちゃって上手く繋がらなくなってしまっていたということでした。
なのでredisの設定をlocalhost→redisといった感じに名前を変更して行えばすんなり上手くいった感じです。
出来てしまえば簡単なことだったと思うのですが、やはり初心者には結構辛いところでした。コード打っててエラーならまだしも繋がらなくてエラーは精神的にかなりきます。
以下は変更点と参考サイトです。
【https://qiita.com/sibakenY/items/0fff6398b8f832fb40a6】
【https://teratail.com/questions/115631】
redisでランキング機能の実装
無事redisは導入出来ましたが、「導入出来てしまえばこっちのもん!」というわけではありません。
ランキングを表示しないといけないのでこれまたredisの基礎とcontroller、viewを見直さないといけません。
これも「Rails redis ランキング」と調べたら結構参考サイトは出てくるのですが、仕組みの理解が乏しいので基礎の見直しが必要でした。
redisの特徴としては以下のような感じです。
- インメモリアルデータベース(すごく早い!)…ランキングなどに向いてる
- 永続化(定期的にディスクに書き出す)
- データ構造サーバー
そんなredisをRailsで使うには、methodを利用する必要があります。
今回はredisのソート済みセットを使ってランキングを実装していきました。
この辺は以下のサイトが非常に参考になったので貼っておきます。
【https://qiita.com/yokozawa/items/aae59b53897ca12f7064】
【https://qiita.com/sibakenY/items/0fff6398b8f832fb40a6】
【https://blog.seishin55.com/entry/2016/05/02/214513】
また、pv数の表示はviewに直接以下のように書けば表示されます。
index.rb<ul> <% @ranking_posts.each do |ranking_post| %> <li> <%= link_to(ranking_post.title,"/posts/#{ranking_post.id}") %> (<%= REDIS.zscore("posts/daily/#{Date.today.to_s}", ranking_post.id).to_i %>PV) </li> <% end %> </ul>アクションに設置する方法がないか考えたのですが、これしか方法がわからなかったです。ちょっと見苦しいですがとりあえずこれでPV数が表示されます。
まとめ
以上今回作ったブログサイトの大雑把なまとめでした。
初心者のメモ程度のものなので間違ってたり足りないところとかたくさんあると思いますが、初心者の方とかの参考になれば嬉しいです。
また、アドバイスなどあればコメントなどしてくれたら嬉しいです。お付き合い頂きありがとうございました。
- 投稿日:2020-02-06T18:48:08+09:00
Ruby、PHP、Java、JSでの書き方の違い
はじめに
「Rubyでプログラミングの基礎を学んだから、次はjsやってみようかな!」
↓
「書き方ごちゃごちゃになって、何が何だかよくわかんねー!!」こんな人もいるのでは?
僕自身も以前、詰め込みすぎでパンパンになりましたので、書き方の違いをわかりやすくまとめてみました。
一つ一つ覚える必要はありませんので、この記事を、ストックしていただければと思います。
オブジェクト指向の理解も多少深まると思いますので、最後までご覧ください!文字出力
基本となる文字の出力。言語によって処理のされ方が違うので難しいところですが、基本なのでしっかり押さえましょう。Javaはやっぱり記述が長いですねw
#Ruby puts "こんにちは"#PHP echo "こんにちは";//Java System.out.println("こんにちは");//JavaScript console.log("こんにちは");変数代入
プログラミングの超基本、変数代入です。
#Ruby name = "Tom"#PHP $name = "Tom";//Java(String、intなどの型を指定する) String name = "Tom";//JavaScript let name = "Tom";変数展開、文字列+変数
これもよく使いますね。全部似てますが、違いがあります。※書き方はあくまで一例です
#Ruby "私の名前は#{name}です。"#PHP "私の名前は{$name}です。"//Java "私の名前は"+ name + "です。"//JavaScript(''ではなく``(バッククオーテーション)で囲む) `私の名前は${name}です。`if文
処理の代表と言えばif文でしょう。elsifのところの違いに注意です。
#Ruby if age >= 20 #処理 elsif age >= 10 #処理 else #処理 end#PHP if (age>= 20){ #処理 }elseif (age >= 10){ #処理 }else{ #処理 }//Java & JavaScript if (age>= 20){ //処理 }else if (age >= 10){ //処理 }else{ //処理 }配列と取り出し方
配列の取り出しもよく使いますね、1つづつ取り出す方法も言語によっていろんなのがあります。
#Ruby names = ["Tom","Kenta","John"] names[0]#PHP $names = array("Tom","Kenta","John"); $names[0];//Java String names[] = {"Tom","Kenta","John"}; names[0];//JavaScript const names = ["Tom","Kenta","John"]; names[0];ハッシュ、連想配列、オブジェクトと取り出し方
言語によって呼び方が違うので、検索する際には注意してください。
#Ruby(ハッシュ) user = {name: "Tom",age: 20} user[:name]#PHP(連想配列) $user = array("name" => "Tom","age" => 20) $user["name"]//JavaScript(オブジェクト) const user = {name: "Tom",age: 20}; user.name通常のメソッド、関数
ここからが重要なところです。今回は2つの値を合計するaddメソッド(関数)を作りました。
戻り値も重要なので、しっかり押さえましょう。#Ruby def add(a, b) return a + b end sum = add(5, 2)#PHP function add($a, $b){ return $a + $b; } $sum = add(5, 2);//Java public static int add(int a, int b){ //staticの後のintは戻り値の型を指定、戻り値がないメソッドの場合はvoidを使用 return a + b; } int sum = add(5, 2);//JavaScript const add = function(a, b){ return a + b; }; let sum = add(5,2); //または const add = (a, b) => { return a + b; }; let sum = add(5,2); //または function add (a, b){ return a + b; } let sum = add(5,2);jsはバージョンによって推奨されている記述が違います。
クラスとインスタンス作成
さあ、待ちに待ったオブジェクト指向の始まりです!
Menuクラスを作って、そのクラスをもとに、インスタンスを作成し変数menu1に代入しています。#Ruby class Menu ##処理 end menu1 = Menu.new#PHP class Menu{ ##処理 } $menu1 = new Menu();//Java class Menu{ //処理 } Menu menu1 = new Menu();//JavaScript class Menu{ //処理 } const menu1 = new Menu();インスタンス変数、インスタンスフィールド、プロパティの定義
メニューには名前や値段などの"情報"が含まれます。それをクラス内で事前に宣言しておきます。
#Ruby attr_accessor :name attr_accessor :price#PHP private $name; private $price;//Java private String name; private int price;//JavaScript //事前定義は不要?初期メソッドでのインスタンス変数、プロパティへの代入
さあ、さらに訳わからないところにやってきました!
初期メソッドとはnewされた時(一番最初)に呼ばれるメソッドになります。
今回はnewの引数に指定された値が初期メソッド内で、インスタンスの情報として変数に入ります。#Ruby def initialize(name, price) self.name = name self.price = price end menu1 = Menu.new("ハンバーガー",300)#PHP public function __construct($name,$price){ this->name = $name; this->price = $price; } $menu1 = new Menu("ハンバーガー",300);//Java //クラス内でクラスと同名のメソッドを定義する Menu(String name, int price){ this.name = name; this.price = price; } Menu menu1 = new Menu("ハンバーガー",300)//JavaScript constructor (name, price){ this.name = name; this.price = price; } const menu1 = new Menu("ハンバーガー",300)インスタンスメソッドと呼び出し
さて最後になります。インスタンスは"情報"の他に"処理"を持っています。
今回はその処理の定義方法と呼び出し方です。
変数menu1にはMenuクラスから作られたインスタンスが代入されているので、
出力は"こちらのハンバーガーは300円です"となります。#Ruby def show puts "こちらの#{self.name}は#{self.price}円です" end menu1.show#PHP public function show(){ echo "こちらの{$this->name}は{$this->price}円です"; } $menu1->show();//Java public void show(){ System.out.println("こちらの"+this.name+"は"+this.price+"円です"); } menu1.show();//JavaScript show(){ console.log(`こちらの${this.name}は${this.price}円です`); } menu1.show();終わりに
最後までご覧いただきありがとうございました。
抜けや間違いがあるかもしれませんが大目に見てください!!この世の中では浅く広く学ぶのはあまり良くないとされていますが、知識を深めるために別言語に挑戦してみるのもいいと私は思います。
- 投稿日:2020-02-06T17:06:59+09:00
初学者によるプログラミングMemo #22 素因数分解
はじめに
今回は素因数分解のお話です
最近問題を解くことが多くなって、アルゴリズム関係に面白みを見出しています
Rails関係なくても書いている下記はすでに決まり文句ともなりました
なお、本記述はMacにおいて、Railsでの開発を前提としています
また、まだまだひよっこですので、不備等ございましたらご指摘いただけると幸いです→よく間違えますが、生暖かく見守ってくださると幸いです目次
- 素因数分解とは
- 問題を解いてみる
素因数分解とは
素因数分解とは、ある正の整数を素数の積で表すことです
なんのこっちゃですかね?
具体的に見てみましょう
例えば「30」を素因数分解してみましょう30 = 10 * 3 10 = 5 * 2 # ↑を合わせるとこう(↓)なる 30 = 2 * 3 * 52,3,5はいづれも素数ですね
このように、元の数を素数を使った掛け算で表すことを、素因数分解と言います問題を解いてみる
さて、本題です
素因数分解を用いた問題を解いてみましょう問題
"27156"の約数のうち、"100"以下の約数を全て答えよ
さあ、困った
なんせ約数の話は一切していないですからね
まあでも約数は小学生の範囲なんで割愛します、流石にわかるでしょうからさて、お察しかと思いますが、素因数分解を利用することで、約数を求めることができます
順番にやっていきましょう
なお今回は、前々回の素数の話を使いますので、先にご覧になることを推奨します
メソッドはここに載ってます#挙動確かめるために"20"で行います require 'prime' 20.prime_division.each do |prime| p prime end # => [2, 2] # [5, 1]prime_divisionメソッドを使うと、素因数分解を自動で行ってくれます便利ですねー、いや、本当にありがたい
で、返り値はご覧の通り、"[素数,その数]"となります
ではこの性質を利用してみましょうrequire 'prime' primes = [] 20.prime_division.each do |prime| prime[1].times {primes << prime[0]} end p primes # => [2, 2, 5]素因数分解ができてますね
解説します
先ほどの返り値を元に、"primes"に"prime[0]"(素数自体)を代入しています
しかし、それだけでは1回しか代入されないので、その前に"times"メソッドを使って、"prime[1]"(素数の数)の回数分代入処理を行ってやります
これで、無事素因数分解ができました
あとは約数を作らないといけませんね約数を作る
さて、問題ももう後半に差し掛かりました
あとはこれを組み合わせてあげれば約数が出ますね
そうです組み合わせです
書いていきましょうrequire 'prime' primes = [] divisors = [] 20.prime_division.each do |prime| prime[1].times {primes << prime[0]} end 1.upto(primes.size) do |i| primes.combination(i) do |prime| divisors << prime.inject{|a,b| a *= b} end end p divisors # => [2, 2, 5, 4, 10, 10, 20]先に挙動の解説をします
"combination"メソッドを使って、組み合わせを作ります
組み合わせる数は"primes"に入っている要素分行うので、その前に、"upto"メソッドを使って、数を回します
"prime.inject{|a,b| a *= b}"は、組み合わせで持ってきた各項を掛け算してもらうための処理ですうまいこといきまし…ん?
なんかおかしいですね、2点ありますが何かわかりますか?まず、"2"と"10"が2回出てきてしまっています
そして…
"1"がないですね
致命的です、これではいけません
1がない
修正を加えます
require 'prime' primes = [] divisors = [1] # <= ここ 20.prime_division.each do |prime| prime[1].times {primes << prime[0]} end 1.upto(primes.size) do |i| primes.combination(i) do |prime| divisors << prime.inject{|a,b| a *= b} end end divisors.uniq! # <= ここ p divisors # => [1, 2, 5, 4, 10, 20]これでいけましたね
"divisors = [1]"はなんか無理やり感があってあまり好きではないですが、仕方ありません(←成長を止める要因)
そして"divisors.uniq!"で、重複を削除していますここまでくればもう完成です
require 'prime' primes = [] divisors = [1] ans = [] 27156.prime_division.each do |prime| prime[1].times {primes << prime[0]} end 1.upto(primes.size) do |i| primes.combination(i) do |prime| divisors << prime.inject{|a,b| a *= b} end end divisors.uniq! for i in divisors if i <= 100 ans << i end end p ans # => [1, 2, 3, 31, 73, 4, 6, 62, 93, 12]できました
が、数字の並びが気持ち悪いので、"sort!"しましょう今回の完成形
ついでにおなじみの対話形式に変えちゃいましょう
require 'prime' def divisor(x,y) primes = [] divisors = [1] ans = [] x.prime_division.each do |prime| prime[1].times {primes << prime[0]} end 1.upto(primes.size) do |i| primes.combination(i) do |prime| divisors << prime.inject{|a,b| a *= b} end end divisors = divisors.uniq.sort for i in divisors if i <= y ans << i end end return ans end p "ある数の約数のうち、決まった数値以下の約数を求めたいん?そんなんうちに任しとき" p "ほんならまず約数を求めたい数を教えてやー" x = gets.to_i p "ありがとう、次はなんぼ以下の約数求めなあかんか教えてや" y = gets.to_i if x != 0 && y != 0 if x <= y p "なんでやねん!そんなん約数全部に決まってるやん" p divisor(x,y) else p "うちの労働力は高うつくで(笑)" p divisor(x,y) end else p "もぅ、いけずせんと、ちゃんと入力して!!" end以上、終わり!!
- 投稿日:2020-02-06T15:56:03+09:00
正規表現でTwitterURLからユーザー名とstatusを取得する【Ruby】
TwitterのURLについて
これがツイートのURLの例
https://twitter.com/yurinolog/status/1209290280328814593?s=20
httpsから始まり3つ目のyurinologがユーザー名、5つ目の数字の羅列がstatusと呼ばれる各ツイートに割り振られる番号。(語尾のs=20の正体はいまだに分からない。知ってる方いらしたら教えて頂きたいです)
書いたコードと結果
url = "https://twitter.com/yurinolog/status/1209290280328814593?s=20" /(https|http):\/\/(twitter.com)\/([A-Za-z0-9_]*)\/(status|statues)\/(\d+)/.match(url) puts $1 #=> http or https puts $2 #=> twitter.com puts $3 #=> user puts $4 #=> status or statuses puts $5 #=> status使用した正規表現
正規表現は数度使ったことがあるが、抽象的というかコードから結果がイメージしにくく知識はほぼ皆無なので、一応復習しておくことにする。
キャプチャ()と$について
キャプチャ(後方参照)と言うのは正規表現でマッチした部分の1部を取り出すときに使う。
取り出す予定の部分を
()
で囲うことで$1
や$2
といった風に取り出すことが出来る。/(https|http):\/\/(twitter.com)/.match(url) puts $2 #=> twitter.comいくつかの候補から選択する
今回の場合はhttpsかhttpか、statusかstatusesとなる。
/(https|http)/.match(url)候補となる値を
|
で区切る。英数字とアンダーバーについて
今回は
[A-Za-z0-9_]
と書いた。
他にも
[A-Z]
[a-z]
[0-9]
と分けて表現できる。あとstatusの数字の表現には
/d+
を使ってみた。
*
と+
は同じ文字や単語を繰り返しマッチさせたいと気に使う。
- 投稿日:2020-02-06T14:53:13+09:00
rails new からrails serverまでの流れ
この記事は
rails new
したい時に色々忘れていたりするので自分用のメモです。
都度更新していくかもしれません。rails newまで
gemはvendor/bundleで管理したい
- フォルダ作成からAtomへ移動まで
~ ❯ cd MyApp ~/MyApp ❯ mkdir portfolio ~/MyApp ❯ cd portfolio ~/MyApp/portfolio ❯ bundle init Writing new Gemfile to /Users/kn428/MyApp/portfolio/Gemfile ~/MyApp/portfolio ❯ atom .
- rails5.2の環境にしたかったので、Gemfileの
gem "rails"
のコメントアウトを外し、gem 'rails', '~> 5.2.1'
とする- bundler経由でrailsをインストールする
~/MyApp/portfolio ❯ bundle install --path vendor/bundle --jobs=4
bundle exec
経由でrails new
にopを付けて実行する
.
は現在のディレクトリの意味
opはrails new -h
で確認またはググる~/MyApp/portfolio 59s ❯ bundle exec rails new . -B -d mysql --skip-test exist create README.md create Rakefile create .ruby-version create config.ru create .gitignore conflict Gemfile Overwrite /Users/kn428/MyApp/portfolio/Gemfile? (enter "h" for help) [Ynaqdhm] YGemfileを上書きしていいか聞かれたら
Y
で続行参考 :
新規Railsプロジェクトの作成手順まとめ
rails new 手順書Gemfileに必要なGemを追加
- gemfileに下記をコピペする(※私の場合)
汎用性のためSLIM等は外しておくsource 'https://rubygems.org' git_source(:github) { |repo| "https://github.com/#{repo}.git" } ruby '2.6.4' gem 'rails', '~> 5.2.1' gem 'bootsnap', require: false gem 'mysql2', '~> 0.5.2' gem 'puma', '~> 3.7' gem 'sass-rails', '~> 5.0' gem 'uglifier', '>= 1.3.0' gem 'jquery-rails', '~> 4.3' gem 'turbolinks', '~> 5.0' gem 'coffee-rails', '~> 4.2' gem 'jbuilder', '~> 2.5' group :development, :test do gem 'sqlite3', '~> 1.3.6' gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] gem 'rspec-rails' gem 'factory_bot_rails' gem 'spring' gem 'spring-watcher-listen', '~> 2.0.0' gem 'spring-commands-rspec' gem 'pry-rails' gem 'pry-doc' gem 'pry-byebug' gem 'rails-erd' gem 'annotate' end group :development do gem 'web-console', '>= 3.3.0' gem 'listen', '>= 3.0.5', '< 3.2' gem 'rubocop-airbnb' gem 'bullet' end group :test do gem 'capybara' gem 'webdrivers' end gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]これで
$ bundle update
したところ下記のエラーが発生An error occurred while installing mysql2 (0.5.3), and Bundler cannot continue. Make sure that `gem install mysql2 -v '0.5.3' --source 'https://rubygems.org/'` succeeds before bundling.様々な対処法があるらしいが私は下記で対処できた
1.$ brew info openssl
を実行し
export LDFLAGS="-L/usr/local/opt/openssl@1.1/lib"
export CPPFLAGS="-I/usr/local/opt/openssl@1.1/include"
この箇所の""
内の部分をコピーしてメモ帳などで貼っておく(環境によるのでちゃんとコマンドを実行して確認すること)
2. それを下記のコマンドに繋げる
--with-cppflags
--with-ldflags
3. 最後にbundle config
コマンドに繋げる
$ bundle config --local build.mysql2 "--with-cppflags=-I/usr/local/opt/openssl@1.1/include"
$ bundle config --local build.mysql2 "--with-ldflags=-L/usr/local/opt/openssl@1.1/lib"
すると.bundle/config
内に追加されている(--with-cppflags
のほうが上書きされている気がするが気にしないでおく).bundle/config--- BUNDLE_PATH: "vendor/bundle" BUNDLE_JOBS: "4" BUNDLE_BUILD__MYSQL2: "--with-ldflags=-L/usr/local/opt/openssl@1.1/lib"これで
$ bundle update
したら無事全てインストールできた参考 :
mysql2 gemインストール時のトラブルシュート
Bundlerでビルドオプションを指定するinstallしたGemの設定
Rspec関連を設定する
$ bundle exec rails generate rspec:install
.rspec
のフォルダの中に--format documentation
を追加する
config/application.rb
内を下記の状態に変更する.config/application.rbmodule Portfolio class Application < Rails::Application # ...省略... config.time_zone = 'Tokyo' config.generators do |g| g.test_framework :rspec, view_specs: false, helper_specs: false, controller_specs: false, routing_specs: false, request_specs: false end config.generators.system_tests = false config.generators.stylesheets = false config.generators.javascripts = false config.generators.helper = false end end
spec/rails_helper.rb
に以下を追記RSpec.configure do |config| # ...省略... config.include FactoryBot::Syntax::Methods # 追加 endテスト用DBをマイグレーションしておく
$ bundle exec rails db:migrate RAILS_ENV=test
- Capybaraの初期設定
$ mkdir spec/supports
$ touch spec/supports/capybara.rb
spec/supports/capybara.rb
内に下記を実装RSpec.configure do |config| config.before(:each, type: :system) do driven_by :selenium_chrome_headless end end
spec/spec_helper.rb
に下記を追加
require 'supports/capybara'
SystemとRequest Specの追加
$ mkdir spec/system
$ mkdir spec/requests
Rspecの高速化
$ bundle exec spring binstub --all
$ bin/rspec spec/
のコマンドで動けばok。direnvの設定は次回の機会にまわす参考 :
RSpecを導入してテストを書いてみる
spring と direnv を使って Rails と rspec を高速起動。快適開発はじめるrails server
$ bundle exec rails db:create
$ bundle exec rails db:migrate
$ bundle exec rails s
無事(http://localhost:3000/) に「Yay! You’re on Rails!」が表示されれば完了git init ~ push
使いまわせたら嬉しいのでここまでをgithub等にpushしておく
まず.gitignore
の中に/vendor/bundle
を入れておく
$ echo '/vendor/bundle' >> .gitignore
$ git init $ git add . $ git commit -m "first commit" $ git remote add origin https://github.com/ユーザー名/リポジトリ名.git $ git push -u origin master以上です。何かアドバイス等ありましたらコメントいただけると嬉しいです。
- 投稿日:2020-02-06T13:14:41+09:00
中間テーブルとは??
プログラミング初学者が学習する中間テーブルについて
Railsなどのフレームワークを学習していて『中間テーブル』という言葉がよくわからなかったので備忘録も兼ねて解説します! (初投稿なので温かく見守ってください泣)
中間テーブルってなんなの??
具体的な使い方
中間テーブルってなんなの??
中間テーブルとは・・・データベースで、テーブルとテーブルの多対多の関係を表すテーブルのこと。
これだけ言われてもパッとしないかもしれません。イメージしてみましょう!
データベースにuserテーブルとgroupテーブルというのが存在すると仮定します。
userテーブル
id name 1 山田 2 佐藤 3 鈴木 4 田中 〜 〜 groupテーブル
id group-name 1 Aグループ 2 Bグループ 3 Cグループ 4 Dグループ 〜 〜 以上二つのテーブルから誰がどのグループに属しているかを表現してみると、、
id name group 1 山田 Bグループ 2 佐藤 Cグループ 3 鈴木 Aグループ 4 田中 Cグループ 〜 〜 〜 このようにuserテーブルにgroupカラムを追加することで解決できます。
しかし!
userがいくつものgroupに所属できるようにしたい!と考えました
*例えば通話アプリの『LINE』でも複数の『グループ』に所属できますよね!すると、、
userテーブル
id name group1 group2 group3 group4 1 山田 Bグループ Cグループ Aグループ 2 佐藤 Cグループ Aグループ Dグループ Bグループ 3 鈴木 Aグループ 4 田中 Cグループ Bグループ これじゃ、空のカラムが大量発生するじゃないか!!!(親父ギャグ)
- カラムどんだけ追加しないとダメなんだ・・・
- DB設計時にどれだけカラムを用意しなければならないんだろう・・・予測できない・・
- 空のカラムはエラーの元じゃないか・・・
ここで登場するのが 中間テーブル です
中間テーブルの具体的な使い方
userテーブル
id name 1 山田 2 佐藤 3 鈴木 4 田中 〜 〜 groupテーブル
id group-name 1 Aグループ 2 Bグループ 3 Cグループ 4 Dグループ 〜 〜 新たに以下のテーブルを追加!
group_userテーブル
id user group 1 山田 Bグループ 2 山田 Cグループ 3 山田 Aグループ 4 佐藤 Cグループ 5 佐藤 Aグループ 6 佐藤 Dグループ 7 佐藤 Bグループ 8 鈴木 Aグループ 〜 〜 〜
このように中間テーブルを追加することで空のカラムもなくなり、その都度追加する必要があったカラムも追加せずに済むことができます。さらに誰がどのグループに所属しているかが見やすくなりました。
データベースの設計をしていく際には空のカラムが発生しないか、チームとして開発を進めていく中で見やすいDBができているかを考える必要があると思うので覚えておきましょう!!!
初投稿となりましたが、何かわかりにくい点や誤って理解してしまっている点などあればご教授願います。では!
- 投稿日:2020-02-06T12:35:26+09:00
Rails ユーザーごとに複数の一覧ページのフラグメントキャッシュを作成する
始めに
プログラミング初学者のQiita初投稿です。
至らない点も多くあると思いますが、頑張って最近やったことを書いてみようと思います。なにをするか
まず、ユーザーが投稿した記事を一覧表示したページが複数ある
今回は全ての記事一覧と、ユーザーがお気に入り登録した記事一覧を作成この2つのページのフラグメントキャッシュをユーザーごとに分けて作成する
キャッシュを通して全てのユーザーに同じページが表示されてしまうのを防ぐため、ユーザーごとに分けます。環境
- Ruby 2.6.2
- Rails 5.2.2
- devise 4.7
- Redis 4.1
ModelとControllerの設定
Modelは
User, Post, Likeの3つ
ユーザーと、ユーザーが投稿する記事と、その記事をお気に入り登録するためのモデルです。
ユーザーはdeviseを使って作成しました。app/models/user.rbclass User < ApplicationRecord has_many :posts has_many :likes has_many :like_posts, through: :likes, source: 'post' endapp/models/post.rbclass Post < ApplicationRecord belongs_to :user has_many :likes has_many :like_users, through: :likes, source: 'user' endapp/models/like.rbclass Like < ApplicationRecord belongs_to :user belongs_to :post endControllerはPostsControllerに2つのページのアクションを用意します
app/controllers/posts_controllers.rbclass PostsController < ApplicationController def index @posts = Post.all end def like_index @posts = current_user.like_posts end記事を投稿してお気に入り登録するまでの流れは、今回の本題とは離れるので割愛します。
記事一覧ページの作成
一覧表示部分は部分テンプレートで共通化します
app/views/posts/index.html.erb<h1>記事一覧</h1> <%= render partial: 'posts_index', locals: { posts: @posts } %>app/views/posts/like_index.html.erb<h1>お気に入り記事一覧</h1> <%= render partial: 'posts_index', locals: { posts: @posts } %>app/views/posts/_posts_index.html.erb<% posts.each do |post| %> <%= post.title %> <%= post.user.name %> ↓お気に入り登録ボタン↓ <% unless post.like_users.include?(current_user) %> <%= link_to likes_path(user_id: current_user.id, post_id: post.id), method: :post do %> <p>お気に入り登録</p> <% end %> <% else %> <%= link_to like_path(id: post.id), method: :delete do %> <p>お気に入り解除</p> <% end %> <% end %> ↑お気に入り登録ボタン↑ <% end %>一覧表示部分には、ワンクリックで記事をお気に入り登録・解除できるリンクを記述しました。
キャッシュの作成
ここからキャッシュを作っていきます。
キャッシュストアにはRedisを使用しています。
Railsはデフォルトでキャッシュがオフになっているので、以下のコマンドでキャッシュをオンにします。rails dev:cache設定ファイルで有効期限を設定できます。
config/environments/development.rbconfig.cache_store = :redis_store, { expires_in: 1.hour }フラグメントキャッシュを作成するのは、以下のようにして簡単にできます。
app/views/posts/_posts_index.html.erb<% cache 'post_index' do %> 追加 <% posts.each do |post| %> <%= post.title %> <%= post.user.name %> ↓お気に入り登録ボタン↓ <% unless post.like_users.include?(current_user) %> <%= link_to likes_path(user_id: current_user.id, post_id: post.id), method: :post do %> <p>お気に入り登録</p> <% end %> <% else %> <%= link_to like_path(id: post.id), method: :delete do %> <p>お気に入り解除</p> <% end %> <% end %> ↑お気に入り登録ボタン↑ <% end %> <% end %> 追加これで、'post_index'というキーで指定した範囲をキャッシュできます。
しかし、このままでは2つのページのキャッシュキーが同じなため、ページ内容が同じになってしまいます。
ここでは、アクションで別々のキャッシュキーを作成し、変数に入れることで対処します。ページごとにキャッシュを分ける
app/controllers/posts_controllers.rbclass PostsController < ApplicationController def index @cache_key = 'index' @posts = Post.all end def like_index @cache_key = 'like_index' @posts = current_user.like_posts endapp/views/posts/_posts_index.html.erb<% cache @cache_key do %> 変更 <% posts.each do |post| %> <%= post.title %> <%= post.user.name %>これで、ページごとにキャッシュを分けることができます。
続いてユーザーごとにキャッシュを分けられるようにしましょう。
今のままだと全ユーザーに同一のページが見えてしまいます。
お気に入りに登録した記事を見ようとしたら他人がお気に入りにした記事一覧が表示された、なんてことになります。
もしくは自分のお気に入り記事一覧ページがキャッシュされた場合、他の全ユーザーにそれが行き渡ります。ユーザーごとにキャッシュを分ける
app/controllers/posts_controllers.rbclass PostsController < ApplicationController def index @cache_key = ['index', current_user.id] @posts = Post.all end def like_index @cache_key = ['like_index', current_user.id] @posts = current_user.like_posts endユーザーのIDをキャッシュのキーに含ませることで、ユーザーごとに違うキャッシュが作成されるようになります。
ここまでで、ユーザーごとに複数の一覧ページのキャッシュを作成できましたが、まだ深刻な問題があります。
新しく記事が投稿されたり、ユーザーや記事の名前が変更された場合、キャッシュがあるせいでそれがビューに反映されません。
データが変わった時にはキャッシュのキーも変更することでこれを回避できるので、キーにはデータの最新情報を含ませるようにします。最新のデータを反映できるようにする
app/models/application_record.rbscope :latest, -> { order(updated_at: :desc).first }app/controllers/posts_controllers.rbclass PostsController < ApplicationController def index @cache_key = ['index', current_user.id, User.latest.update_at, Post.latest.update_at, Like.latest.update_at] @posts = Post.all end def like_index @cache_key = ['like_index', current_user.id, User.latest.update_at, Post.latest.update_at, Like.latest.update_at] @posts = current_user.like_posts endこれで、データの変更時には新しくキャッシュが作成されるようになりました。
削除が反映されない問題
ここまでで基本的なフラグメントキャッシュを作成することができました。
しかしまだ問題はあります。
データの変更時にキャッシュを新しく作成することはできましたが、
データを削除した時はキャッシュは変わりません。(最新のデータであれば変わります)例えば、
記事1を投稿する キャッシュ1ができる
記事2を投稿する キャッシュ2ができる
ここで記事1を削除する 最新の更新時間は変わらないためキャッシュはできない
記事一覧ページのロードには最新のキャッシュ2が使われる
キャッシュ2ができた時は記事1は存在したため、記事1は表示されるというように、
削除された記事1が、削除後も表示されてしまいます。
データ変更時の時間を、削除された時間で上書きする必要がありそうです。
そこでデータが削除された時間を取得する方法を探してみたら、論理削除というものを発見しました。
論理削除はデータが削除された時、レコードを消去せずに削除時間を入力して削除されたものとみなすもののようです。
この削除時間が入力された時、更新時間(updated_at)も変わるため、今回の問題にはこれが使えそうです。
もっといい方法があるかもしれませんが、今回は論理削除を活用してみようと思います。論理削除
Userは削除されないものとして、
PostとLikeモデルに論理削除を適用します。
まずパラノイアというGemをインストールします。gem 'paranoia'$ bundle install論理削除を使うモデルに、deleted_atカラムを追加します。
$ rails g migration AddDeletedAtToPosts deleted_at:datetime$ rails g migration AddDeletedAtToLikes deleted_at:datetimeclass AddDeleteAtToPosts < ActiveRecord::Migration[5.2] def change add_column :posts, :deleted_at, :datetime end endclass AddDeleteAtToLikes < ActiveRecord::Migration[5.2] def change add_column :likes, :deleted_at, :datetime end end$ rails db:migrateモデルファイルにacts_as_paranoidを記述します。
app/models/post.rbclass Post < ApplicationRecord acts_as_paranoid belongs_to :user has_many :likes has_many :like_users, through: :likes, source: 'user' endapp/models/like.rbclass Like < ApplicationRecord acts_as_paranoid belongs_to :user belongs_to :post endこれだけで論理削除が適用されるようになりました。
これで記事やお気に入りが削除された際、レコードは実際には消えず
削除時間が追加されるだけになりました。データ検索の際に従来の、
Post.allだと、削除されたデータは除外されますが
Post.with_deleted.allとすると、削除されたデータも含めて検索できます。
改めて、
app/controllers/posts_controllers.rbclass PostsController < ApplicationController def index @cache_key = ['index', current_user.id, User.latest.update_at, Post.with_deleted.latest.update_at, Like.with_deleted.latest.update_at] @posts = Post.all end def like_index @cache_key = ['like_index', current_user.id, User.latest.update_at, Post.with_deleted.latest.update_at, Like.with_deleted.latest.update_at] @posts = current_user.like_posts endとすることで、
記事1を投稿する キャッシュ1ができる
記事2を投稿する キャッシュ2ができる
記事1を削除する 最新の更新時間が変わったためキャッシュ3ができる
記事一覧ページのロードには最新のキャッシュ3が使われる
キャッシュ3ができた時は記事1はいないため、記事1は表示されないというように、
削除された記事1を一覧表示から消すことができました。最後にキャッシュキー作成コードを、DRYにして終わりです。
app/controllers/posts_controllers.rbclass PostsController < ApplicationController def index @cache_key = make_cache_key('index') @posts = Post.all end def like_index @cache_key = make_cache_key('like_index') @posts = current_user.like_posts end def make_cache_key(action) [action, current_user.id, User.latest.update_at, Post.with_deleted.latest.update_at, Like.with_deleted.latest.update_at] end後になって気づいたこと
さて、ここまでやってきた最中で、
今までのことの多くが無駄だったことに気づきます。app/views/posts/_posts_index.html.erb<% cache @cache_key do %> これを <% posts.each do |post| %> <%= post.title %> <%= post.user.name %>app/views/posts/_posts_index.html.erb<% posts.each do |post| %> <% cache post.updated_at do %> こうする <%= post.title %> <%= post.user.name %>これだけでよかった!
eachで回される1つ1つのオブジェクト自体をキャッシュすることで、
自然とユーザーやページごとに別の内容になります。
コントローラーにも何も書かなくていいです。
今思えばこういう風に書いてるサイトが多かったのになぜこうしなかったのか、、、ただ、今回に関してはこれだけではダメです。
今回は一覧表示されている記事1つ1つに、お気に入りボタンがありました。
上記のコードは記事1つ1つの更新時間をキャッシュキーにしているだけなので、お気に入り登録の変更は感知してくれません。
記事作者の情報が変更されても感知しません。
それに、お気に入りボタンは人によって見え方が違うはずなので、
ユーザーを識別するためにユーザーIDをキャッシュキーに含める必要が出てきます。
つまり今までやったようなことが結局必要になります。app/controllers/posts_controllers.rbclass PostsController < ApplicationController def index @cache_key = make_cache_key @posts = Post.all end def like_index @cache_key = make_cache_key @posts = current_user.like_posts end def make_cache_key [Like.with_deleted.latest.update_at] endapp/views/posts/_posts_index.html.erb<% posts.each do |post| %> <% cache [current_user.id, room.updated_at, room.user.updated_at, @cache_key] do %> <%= post.title %> <%= post.user.name %>大体こんな感じになるでしょうか。
結局最初にやったのと同じような感じにはなりました。
一覧表示全体を1つのキャッシュにするか、一覧内容1つ1つをキャッシュするか。
どちらの方が良いかは時と場合によるでしょうか?最後に
今回、お気に入りボタンなどもキャッシュしてみましたが、
そもそもユーザーによって表示が変わる、いわゆる動的な部分はキャッシュしないのが普通なのかもしれません。
論理削除も積極的に使うべきではないと思うので、全体としてあまり有意義なことはできなかったかもです。
それでも今回得られたキャッシュについての知見は、どこかで必ず役に立つとは思います。参考資料
- 投稿日:2020-02-06T12:31:17+09:00
開発環境の Ruby version を上げる
開発環境 (最近変なスペルになった macOS をここでは想定) に Ruby を入れるのは、よくある話だが、Ruby をバージョンアップする手順についてはもサッとできるようにまとめておく。
前提
- macOS にいれるミドルウェアのパッケージマネジメントは Homebrew で行っている
- Ruby は直接 Homebrew でいれるのではなく、rbenv を用いて管理している
- Ruby バージョンアップするのは久しい
つまり macOS > Homebrew > rbenv > bundler > 各フォルダごとに Gem file のような構成である。
コマンド
rbenv にてインストール可能なバージョンは下記で確認できる。
$ rbenv install -l 2.6.0 2.6.1 2.6.2 2.6.3 2.6.4 2.6.5もし、必要なバージョンが存在しない場合は rbenv 自体をバージョンアップしてあげる必要があるだろう。
# Homebrew 本体を更新 $ brew doctor $ brew update # ruby-build を更新 $ brew upgrade ruby-build必要なバージョンがあるかどうか確認して、global と local の version を設定しておく。
# Ruby $ rbenv install -l $ rbenv install 2.6.X $ rbenv global 2.6.X $ cd /to/your/app/path $ rbenv local 2.6.X # Bundler は各バージョンの Ruby ごとにインストール。 $ rbenv versions $ gem install bundlerふぅ
・・・ Docker 化しておいて構成まるごとごと差し替えるのがナウくてハマりづらい方法だとは思う。
- 投稿日:2020-02-06T11:08:16+09:00
Rubyで始めるスクレイピング 〜入門編〜
スクレイピングとは
Webサイトから自分の知りたい情報を抽出すること。
ex) 文章、画像、動画などTL; DR
Qiitaで「ruby」で検索して「いいね順」に並べた検索結果一覧をスクレイピングします。
コードを見ながら説明します。1. まずは前準備。
scraping01.rb# コードのsyntax highlight・補完などをしてくれるgem(無くてもいいけどめっちゃ便利) # https://github.com/janlelis/irbtools require 'irbtools/more' # URLにアクセスした内容をファイルのように扱えるgem require 'open-uri' # スクレイピングのgem require 'nokogiri' # CSV出力するgem require 'csv' url = 'https://qiita.com/search?page=1&q=ruby&sort=like'URLのパラメータの仕組みについて
https://qiita.com/search?page=1&q=ruby&sort=like
?
以降がパラメーターです。
パラメータを複数つなげるには&
で繋ぎます。今回のURLですと、
page
とq
とsort
というパラメータがあります。
page=
の後に続く値がページ番号になります。Qiitaの場合は、
q=
の後に続く値が検索ワードになっています。
試しに、以下のURLを打ってみてください。
https://qiita.com/search?q=アイウエオ
sort=
に続く値が並び順になっているようです。
sort=
は受け取る値によって並び順が変わるようです。
パラメータ 並び順 like いいね順 created 新着順 rel 関連順 stock ストック順 また、上記に該当しない値の場合は、
関連順
になるようです。
https://qiita.com/search?q=ruby&sort=テストトトLet's Parse!!!
scraping02.rb# openブロック内に入る前に変数定義をしておかないと、openブロック内でしか使えないスコープ変数になってしまう為、ここで定義する必要がある。 charset = nil # 「open(url)」でそのサイトの情報を取得。 html = open(url) do |f| # 文字コードはそのサイトの文字コードに合わせる。 charset = f.charset #「.read」でHTML情報を出力 f.read end # html変数に入っている情報はただの文字列です。 html.class #=> String # つまり、HTML要素もただの文字情報として認識されている(文章とHTML要素の区別がなされていない)と分かります。 # なので、HTML要素とそうでない文字列に分解(構文解析「parse」)する必要があります。 # そこで登場するのが、Nokogiriです。 # こんな感じで書きます。 Nokogiri::HTML.parse(構文解析したい文章, URL, 文字コード) # https://www.rubydoc.info/github/sparklemotion/nokogiri/Nokogiri%2FHTML.parse doc = Nokogiri::HTML.parse(html, nil, charset) doc.class #=> Nokogiri::HTML::Document # これでHTML要素とそうでない文字列を分けて扱えるデータになりました。Let's Nokogiri!!!
scraping03.rb# 取得したい情報のHTML要素を「.css」メソッドに渡して取得します。 # 渡すHTML要素は、HTMLにCSSを当てる時のCSSセレクタ(HTMLタグやクラス、ID)を指定する記述方法と同じです。 # 例えば、検索結果一覧から「記事タイトル」を取得するには doc.css('h1.searchResult_itemTitle').text #=> "Markdown記法 チートシートペアプログラミングして気がついた新人プログラマの成長を阻害する悪習プログラミングでよく使う英単語のまとめ【随時更新】非デザイナーエンジニアが一人でWebサービスを作るときに便利なツール32選【まとめ】これ知らないプログラマって損してんなって思う汎用的なツール 100超新人プログラマに知っておいてもらいたい人類がオブジェクト指向を手に入れるまでの軌跡もう保守されない画面遷移図は嫌なので、UI Flow図を簡単にマークダウンぽく書くエディタ作った翻訳: WebAPI 設計のベストプラクティス開設後3週間で収益10万円を得た個人開発サイトでやったことの全部を公開するエンジニアの情報収集法まとめ" # もちろん結果は、セレクタでヒットするかずを全部持ってきます。 doc.css('h1.searchResult_itemTitle') #=> 10 # 結果を見やすくしてみましょう。 # 「.search」は「.css」のエイリアスメソッド # 「.inner_text」は「text」のエイリアスメソッド titles = doc.search('.searchResult_itemTitle').map{ |node| node.inner_text } # これを応用して、「タイトル」と「タグ」と「本文」を取ってきましょう。 results = [] doc.search('.searchResult_main').each_with_index do |node, i| tags = [] title = node.css('.searchResult_itemTitle').inner_text node.css('.tagList_item').each{ |article_tag| tags << article_tag.inner_text } details = node.css('.searchResult_snippet').inner_text results << [ title, tags, details ] p tags end results.each_with_index do |res, i| puts puts "#{i+1}番目の検索結果" puts "Title: #{res[0]}" puts "Tags: #{res[1]}" puts "Details: #{res[2]}" puts '-----------------------------------------' endLet's Scraping!!!
scraping04.rb# 検索ワードを配列で持ちます。 search_terms = ['ruby', 'php', 'python', 'perl'] results = {} search_terms.each do |search_term| query_hash = [] # 検索結果一覧ページが100ページまでしか無いので1から100にしています。 (1..100).each do |i| # URLはベタがきではなく、「ページ番号」と「検索ワード」に式展開を使い、柔軟にしています。 url = "https://qiita.com/search?page=#{i}&q=#{search_term}&sort=like" charset = nil html = open(url) do |f| charset = f.charset f.read end doc = Nokogiri::HTML.parse(html, nil, charset) # 「.searchResult」にタイトルやタグなどの親クラスで、各記事の情報がぶら下っているので、一度ここを取得し、ここから子クラスの情報を抽出します。 # ここからはRubyというより、CSSセレクタの内容になります。欲しい情報に応じてHTMLタグやクラスを書き換えてください。検証ツールで確認できます。 doc.search('.searchResult').each do |node| tags = [] title = node.css('.searchResult_itemTitle').text node.css('.tagList_item').each{ |article_tag| tags << article_tag.text } details = node.css('.searchResult_snippet').text link = "https://qiita.com/" + node.css('.searchResult_itemTitle').css('a')[0][:href] # [0]が「良いね」の数。[1]は「コメント」の数 stars = node.css('.list-unstyled.list-inline.searchResult_statusList li')[0].text.gsub(" ","") # コメント数が0個の場合は、コメントのHTML要素が出力されない為、[1]のところでNilエラーになります。それを回避する為にアンパサンドを使用します。 comments = node.css('.list-unstyled.list-inline.searchResult_statusList li')[1]&.text&.gsub(" ","") author = node.css('.searchResult_header').css('a')&.text query_hash << { stars: stars, title: title, tags: tags, details: details, link: link, comments: comments, author: author } end # この一連の処理は時間がかかります。ここでputsすることで、処理がどれぐらい進んでいるかを確認しながら待つことができます。 puts "#{search_term} #{i}" end results["#{search_term}"] = query_hash end # resultsというハッシュに入った結果を一元配列で管理し、結果をCSV出力する。 ruby_array, php_array, python_array, perl_array = [], [], [], [] results["ruby"].each do |a| ruby_array << [ a[:stars], a[:title], a[:tags], a[:details], a[:link], a[:comments], a[:author] ] end # CSV.openの引数に、書き出すファイル名を指定します。その際にLinuxコマンドのようにPathで指定すれば任意の場所に保存できます。 CSV.open('qiita_ruby.csv', 'w') do |csv| # ヘッダーの設定 csv << ['stars', 'title', 'tags', 'details', 'link', 'comments', 'author'] ruby_array.each do |r| csv << r end end results["php"].each do |a| php_array << [ a[:stars], a[:title], a[:tags], a[:details], a[:link], a[:comments], a[:author] ] end CSV.open('qiita_php.csv', 'w') do |csv| # ヘッダーの設定 csv << ['stars', 'title', 'tags', 'details', 'link', 'comments', 'author'] php_array.each do |r| csv << r end end results["python"].each do |a| python_array << [ a[:stars], a[:title], a[:tags], a[:details], a[:link], a[:comments], a[:author] ] end CSV.open('Desktop/qiita_python.csv', 'w') do |csv| # ヘッダーの設定 csv << ['stars', 'title', 'tags', 'details', 'link', 'comments', 'author'] python_array.each do |r| csv << r end end results["perl"].each do |a| perl_array << [ a[:stars], a[:title], a[:tags], a[:details], a[:link], a[:comments], a[:author] ] end CSV.open('Desktop/qiita_perl.csv', 'w') do |csv| # ヘッダーの設定 csv << ['stars', 'title', 'tags', 'details', 'link', 'comments', 'author'] perl_array.each do |r| csv << r end endまとめ
- 指定したURLの情報を取得
- 取得した情報を構文解析
- CSSセレクタの記法を使って欲しい情報を抽出
- CSVに吐き出す
こんな感じでスクレイピングできます!!!
コメントや指摘をいただけると幸いです!!!P.S.
rubyのスクレイピングは人気なさそう。。。
やっぱりライブラリや記事・文献の多さから、Pythonが圧倒的に人気っぽそうですよねぇ。。。
https://trends.google.co.jp/trends/explore?q=ruby%20scraping,python%20scraping,php%20scraping,perl%20scraping,javascript%20scraping
- 投稿日:2020-02-06T08:43:52+09:00
Leetcode: Swap Nodes In Pairs
require 'minitest/autorun' require 'pry' class SwapNodesInPairs < Minitest::Test def test_run list = ListNode.new(1) list.next = ListNode.new(2) list.next.next = ListNode.new(3) list.next.next.next = ListNode.new(4) new_list = swap_pairs(list) assert_equal(2, new_list.val) assert_equal(1, new_list.next.val) assert_equal(4, new_list.next.next.val) assert_equal(3, new_list.next.next.next.val) list = ListNode.new(1) new_list = swap_pairs(list) assert_equal(1, new_list.val) end def swap_pairs(head) return if head.nil? return head if head.next.nil? head_val = head.val next_head_val = head.next.val head.val = next_head_val head.next.val = head_val swap_pairs(head.next.next) head end end class ListNode attr_accessor :val, :next def initialize(val) @val = val @next = nil end end
- 投稿日:2020-02-06T08:42:53+09:00
Leetcode: Reverse String
require 'minitest/autorun' require 'pry' class ReverseString < Minitest::Test def test_run input = ["h", "e", "l", "l", "o"] output = ["o", "l", "l", "e", "h"] assert_equal(output, reverse_string(input)) end def reverse_string(s, index = 0) return s if index == s.size / 2 first_char = s[index] last_char = s[s.size - 1 - index] s[index] = last_char s[s.size - 1 - index] = first_char index += 1 reverse_string(s, index) s end end
- 投稿日:2020-02-06T08:16:17+09:00
Rubyのメソッドを再実装する勉強法(Enumerable#chunkを再実装)
社内で定期的にやっているRubyのメソッドを再実装する勉強法の紹介です。
※ メソッドの再実装はGrow.rbを参考にしています。Rubyのメソッドを再実装することでRubyの理解を深め基礎力をつけることを目的にしています。
複数メンバーでやることで十人十色な実装、書き方に触れることができるので面白いです。今回は
Enumerable#chunk
を再実装します。
※問題はGitHubにまとめています。
Enumerable#chunk
要素を前から順にブロックで評価し、その結果によって要素をチャンクに分けた(グループ化した)要素を持つ Enumerator を返します。
ブロックの評価値が同じ値が続くものを一つのチャンクとして取り扱います。すなわち、ブロックの評価値が一つ前と異なる所でチャンクが区切られます。
返り値の Enumerator は各チャンクのブロック評価値と各チャンクの要素を持つ配列のペアを各要素とします。そのため、eachだと以下のようになります。[3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5].chunk {|n| n.even? }.each {|even, ary| p [even, ary] } #=> [false, [3, 1]] # [true, [4]] # [false, [1, 5, 9]] # [true, [2, 6]] # [false, [5, 3, 5]]引用:module Enumerable (Ruby 2.7.0 リファレンスマニュアル)
? 問題
chunk.rbmodule Enumerable def chunk puts 'reimprement here' end endここに
chunk
を再実装して正しく動くか確認します。? テストコード
chunk_test.rbrequire 'minitest/autorun' require_relative 'chunk' class ChunkTest < MiniTest::Test # chunk {|elt| ... } -> Enumerator def test_chunk chunk_result = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5].chunk(&:even?) expected_result = [[false, [3, 1]], [true, [4]], [false, [1, 5, 9]], [true, [2, 6]], [false, [5, 3, 5]]] assert_equal expected_result, chunk_result.map { |even, ary| [even, ary] } assert_equal Enumerator, chunk_result.class end end
chunk_test.rb
をchunk.rb
と同じディレクトリに置き以下を実行。
テストが通れば成功です?ruby chunk_test.rb? メンバー回答例
今回は以下の要件はスキップして実装していました。
ブロックの評価値が nil もしくは :separator であった場合、 その要素を捨てます。チャンクはこの前後で区切られます。
ブロックの評価値 :alone であった場合はその要素は 単独のチャンクをなすものと解釈されます。※ 正解では無いのであしからず
回答例1
module Enumerable def chunk &block bool = nil result = [] temp_result = [] # 1つめの真偽値を保存する # 真偽値が切り替わるまでループする each do |item| temp_bool = yield item if temp_bool != bool bool = temp_bool result << temp_result temp_result = [bool, [item]] else temp_result[1] << item end end result << temp_result result.shift result.each end end回答例2
module Enumerable def chunk results = [] now_result = [] each do |item| value = yield item if now_result[0].nil? # はじめて now_result[0] = value now_result[1] = [item] elsif now_result[0] == value # おなじ now_result[1] << item else # ちがう results << now_result now_result = [] now_result[0] = value now_result[1] = [item] end end results << now_result results.each end end回答例3
module Enumerable def chunk(&block) result = [] tmp = [] block_result = nil each do |n| tmp_block_result = yield(n) if !tmp.empty? && block_result != tmp_block_result result << [block_result, tmp] tmp = [] block_result = tmp_block_result end block_result = tmp_block_result tmp << n end result << [block_result, tmp] result.each end end最後に
Rubyのメソッド再実装は
- ドキュメントを読む
- 仕様を正しく理解する
- 仕様通りに実装する
という基本的な力を養うのに良さそうです。
また、同じ課題で各自が実装すると知らなかったメソッドや書き方に出会えたり、比較して議論もしやすいです。再実装しまくって君だけのRubyを作ろう!
- 投稿日:2020-02-06T06:50:14+09:00
最小限の手間でMac上にOACISを導入する方法
この記事ではMac上で最小限の手順でOACISの環境をセットアップする手順について説明します。
OACISは様々な環境で動作する様に汎用的に作られている一方で、Linuxの知識がないとセットアップが難しい部分もあります。ここではMac使い向けに「とにかくこの手順に従えばセットアップできる」という方法をお伝えします。
詳細が知りたくなったら公式ドキュメントを参照してください。
ここでは2020年1月時点で最新のv3.7.0をインストールします。手順は以下の通り。
- Rubyをインストールする
- MongoDBとRedisをインストールする
- localhostに対してSSHログインできるように設定する
- OACISのソースコードをダウンロードする
- OACISを起動する
- OACISにlocalhostを登録する
Rubyをインストールする
rbenvを使ってインストールします。rbenv自体のインストール手順はrbenvのreadme参照してください。
rbenvをインストール後は、rubyをインストールします。rbenv install 2.6.5 && rbenv global 2.6.5 gem update bundler rbenv rehashMongoDBとRedisをインストールする
homebrewを使ってMongoDBをインストールします。
詳細はMongoDBの公式ドキュメントを参照してください。brew tap mongodb/brew brew install mongodb-community@4.2 brew services start mongodb-community@4.2同様にhomebrewを使ってRedisをインストールします。
brew install redis brew services start redislocalhostに対してSSHログインできるように設定する
「システム環境設定」-「共有」を開き、「リモートログイン」にチェックを入れます。
場合によっては「ファイアウォールによりブロック」と表示されているかもしれませんが問題ありません。続いてパスワード無しでログインできるように鍵認証の設定をします。
ssh-keygen -t rsa -N '' -f ~/.ssh/id_rsa cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keysこれで自分自身の端末(localhost)にパスワード無しでログインできるようになります。
ここまでの設定がうまくいっていれば、以下のコマンドを実行してホームディレクトリのパスが表示されることを確認します。ssh localhost pwdOACISのソースコードをダウンロードする
OACISのソースコードをダウンロードします。
git clone --recursive -b master https://github.com/crest-cassia/oacis.git cd oacis bundle installまた、xsubというツールも同様にインストールします。
cd ~ # home directoryに移動 git clone https://github.com/crest-cassia/xsub.git echo 'export PATH="$HOME/xsub/bin:$PATH"' >> ~/.bash_profile echo 'export XSUB_TYPE="none"' >> ~/.bash_profileOACISを起動する
先ほどダウンロードしたOACISのディレクトリに移動して、OACISを起動します
cd ~/oacis bundle exec rake daemon:restartこれで http://localhost:3000 にブラウザからアクセスすると、OACISのトップ画面が表示されます。
OACISにlocalhostを登録する
OACISの画面からlocalhostをジョブの投入先として登録します。
画面上部の"Host"、"New Host"を選択し、以下の項目を入力します。
- Name : localhost
これで
localhost
というホストが登録され、ジョブの投入先として選べるようになります。
- 投稿日:2020-02-06T05:45:43+09:00
Rails Tutorialの知識から【ポートフォリオ】を作って勉強する話 #18.5 環境変数, Gmail送信設定編
こんな人におすすめ
- プログラミング初心者でポートフォリオの作り方が分からない
- Rails Tutorialをやってみたが理解することが難しい
前回:#18 EC2環境構築, Nginx+Puma+Capistrano編
次回:準備中今回の流れ
- Railsの環境変数を設定する
- 本番環境でのGmailの送信を設定する
Railsの環境変数を設定する
Rails5.2以降の環境変数の設定には、credentials.yml.encを使います。
デフォルトで.gitignoreになり暗号化されているのでおすすめです。
以下のように設定します。shellEDITOR=vim rails credentials:editcredentials.yml.enc# aws: # access_key_id: XXXX # secret_access_key: XXXX # Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies. secret_key_base: XXXXおそらくこんな感じかと思うので、以下のように環境変数を追加します。
credentials.yml.enc# 省略 a: b: bbb c: ccc # 省略以上で設定が終わりました。
試しにコンソールで値を引き出してみます。
値を引き出すには、以下のように行います。shell$ rails c
> Rails.application.credentials.a[:b] => "bbb"この後のGmailの送信の設定には、このcredentials.yml.encを使います。
本番環境でのGmailの送信を設定する
前提として本番環境には、EC2を使っています。
またActionMailerの生成はすでに行なっているものとします。
(お済みでない方は#12などをご確認ください。)それではGmailを送信の設定をします。
ここでの手順は以下の通りです。
- Googleアカウントのアプリパスワードを有効にする
- production.rbの設定を変更する
- 環境変数を設定する
Googleアカウントのアプリパスワードを有効にする
お手持ちのGoogleアカウントのアプリパスワードを有効にします。
有効にするまでに、いくつかの手順を踏みます。
- googleアカウント → 画面左ダッシュボード『セキュリティ』 → Googleへのログイン『2段階認証プロセス』 → 有効にする
- googleアカウント → 画面左ダッシュボード『セキュリティ』 → Googleへのログイン『アプリパスワード』 → 16桁のパスワードをコピーする
これで次の手順に使うパスワードを手に入れました。
production.rbの設定を変更する
Gmail用に設定を変更します。
config/environments/production.rbconfig.action_mailer.raise_delivery_errors = true config.action_mailer.delivery_method = :smtp config.action_mailer.default_url_options = { host: Rails.application.credentials.host_server[:ip] } ActionMailer::Base.smtp_settings = { :address => 'smtp.gmail.com', :port => '587', :authentication => :plain, :user_name => Rails.application.credentials.gmail[:user_name], :password => Rails.application.credentials.gmail[:password], :domain => 'gmail.com', :enable_starttls_auto => true }先ほど見かけた、環境変数を引き出す記述があります。
最後はこれが動作するよう、環境変数を設定します。環境変数を設定する
環境変数の設定です。
送信に使うアドレスやアプリパスワードはここに記述します。credentials.yml.enchost_server: ip: XX.XX.XX.XX # EC2のIPアドレス gmail: user_name: hogehoge@gmail.com # Gmailアドレス password: hogehoge # コピーしたアプリパスワードこれで送信の設定が完了しました。
参考になりました↓
【Rails】メール送信設定 〜gmail利用〜
前回:#18 EC2環境構築, Nginx+Puma+Capistrano編
次回:準備中
- 投稿日:2020-02-06T02:05:31+09:00
リファクタリングでモブプロみたいなことをしたお話
弊社内でエンジニア3人が集まって楽しくリファクタリングをした話です。
背景
社内のプロダクトで「手が空いてるならリファクタリングやってくれると嬉しい。手が足らない」というようなSOSを受け取り、
そのプロダクトをある程度知っている人 1人 + 知らない人 2人 の3人でリファクタリング部隊を作ってリファクタリングしていこうという話になりました。今回は、モブプロ「みたいなこと」です。厳密なモブプロではないです。
なので、
- ドライバーも考える (みんなで考える)
- ドライバーは変わらない
という感じでゆるく進行しました。
プロダクトについて
Railsで動いています。Rails3から始まり、今Rails5です。
- 7年ぐらい前から動いているプロダクト
- 新しく書いたところは新しく、古いものは古いという歴史が積み重なっている
- Rails3からのコードが残っている
- Rails何それ?Ruby何それ?から始まったコードが存在している
- ただファイルが分割されただけのコードがConcernsとして存在している
- でもテストはちゃんと書かれている
という感じで、とても歴史のあるプロダクトです。
今回やった人について
A: このプロダクトわかる。設計強い。DDD好き。Ruby, Railsともに得意。
B: このプロダクトわからない。インフラ強い。Rubyそんなに得意じゃない。 (本人談)
C: このプロダクトわからない。設計好き。DDD好き。Ruby得意。型を欲しがる。という感じで思想やスキルの差が若干ありつつ、
- 仲が良いので思想のぶつけ合いになっても険悪にはなりづらい
- 誰か一人が発言しづらいということはない
- お互いの技術に信頼/リスペクトしている
というメンバーです。今回はBさんがドライバーをしました。
今回の流れ
実際にどんな感じでやったのか?ですが、基本は
- つっつき会
- メシ
- 修正会
という3つのフェーズで行いました。
会議室で大画面を使用してみんなで唸ります。つっつき会
今回のメインはモブプロをやることではないので、まずはリファクタリング対象(ディレクトリ/ファイル単位)を決め、
コードを読んでみてよくなさそうなところをつっついて、方針決めをします。大体4時間ぐらい。このつっつきは
TODO: つっつき serviceとして切り出す
というようなTODOコメントとして残し、コミットします。今回は、以下のような観点でつっついていきました。
- そのファイルの構造、ネームスペースが正しいか(分割されただけのファイルがconcernsとして存在しているため)
- そのクラスの責務を逸脱したメソッドがあるか(↑と大体一緒)
- 冗長(わかりづらい)な書き方をしていないか
- これらを大幅な変更せず綺麗にできるか
この時点でも、3人分の目と知識があるので、誰かが「この書き方はなんだ?」「これどうなってるんだ」となっても、知ってる/わかってる人が説明するということもあり、一人でやるときよりもスムーズです。
また、
これらを大幅な変更せず綺麗にできるか
という判断も1人だと消極的な理由でやらないことを選択してしまいそうな部分も、誰かの「実は簡単に修正できるのでは」という知識の後押しにより、積極的な判断をすることもできます。もちろん、やらないという判断もあります。
特に、やるか/やらないか という部分以外にも判断材料を出せる人が増えるというのは多人数の有利な部分です。
メシ
修正会
実際につっつき会でつっついた部分をリファクタリングしていきます。大体2時間ぐらい
基本的につっつき会で方針が出ているのでその方針に沿ってリファクタリングするだけです。
とはいえ、この段階でも「あ、もっといい方法あるじゃん」というのはあるので、随時取り入れていきます。KPT
Keep
- 誰かの「これどうなるんだろう?」に対して「じゃあ、試してみるか」というラフな形でやってみることができる
- コードではわからないお互いの思考フローが知れる
- ドメインエキスパートが居ないことによる「名前から直感的に判断できるか」という部分に焦点をあてれる
Problem
- 仲が良いからこそ、脱線すると脱線し続けそうになる
- ドメインエキスパートが居ないので仕様がわからないのでパスというのがあった
- 複数人いるのでガッツリ集中して書くというのは難しい
Try
- ドメインエキスパートも呼んでみる
- 仕様についてをサクッと聞けるので時間短縮になりそう
- keepの部分とコンフリクトしそう
- 人数を増やしてみる
- 人が増えれば集合知というのは単純な加算になるのか
終わりに
ペアプロ、モブプロは人と人の知識をリアルタイムでつなぎ合わせる というのがコアだと思うので、
「ドライバーは10分」「ナビゲーターの指示に従う」といった感じで厳密にやる必要は別になく、人が集まってみんなでコードに向かうというのは大事だと感じました。
特にkeepでも書いた、コードではわからないお互いの思考フローが知れる
というのはお互いに勉強になります。
また、エンジニア間のコミュニケーションも取れるのでお互いに刺激になり、メインの業務にもハリが出ると思います。更に、人間誰しも見落としや勘違いもあるので、単純に人数が増えることにより、見落としや勘違いなどをし続けることがなくなりやすいです。 (ダブルチェックと一緒で単純に増えればいいわけではないですが)
と、いう感じで、定期/不定期に限らずこういうことをやるのはいい感じだと思います。
おわり。
- 投稿日:2020-02-06T00:05:06+09:00
【Rails】I18n.tはtに省略できるんじゃない、省略したほうが良い
I18n.tの省略記法
まだなんにもわかっていないRails触り始めて1週間くらいの頃、
「RailsはViewファイル内ではI18n.t('hoge')
って書かなくてもt('hoge')
って書けるよ、
先輩たちもそう書いてるから合わせてね」って言われてふむふむなるほど、
よくわからんがviewでは省略できるのかってなった経験はありませんか?実はI18n.tをtにすると文字が4文字節約できるってレベルではない変化が起きていた
つまりどういう事か
詳しい実相はこの辺
TranslationHelper
にtが実装されていたんですねrails/actionview/lib/action_view/helpers/translation_helper.rbdef translate(key, options = {}) options = options.dup if options.has_key?(:default) remaining_defaults = Array.wrap(options.delete(:default)).compact options[:default] = remaining_defaults unless remaining_defaults.first.kind_of?(Symbol) end # If the user has explicitly decided to NOT raise errors, pass that option to I18n. # Otherwise, tell I18n to raise an exception, which we rescue further in this method. # Note: `raise_error` refers to us re-raising the error in this method. I18n is forced to raise by default. if options[:raise] == false raise_error = false i18n_raise = false else raise_error = options[:raise] || ActionView::Base.raise_on_missing_translations i18n_raise = true end if html_safe_translation_key?(key) html_safe_options = options.dup options.except(*I18n::RESERVED_KEYS).each do |name, value| unless name == :count && value.is_a?(Numeric) html_safe_options[name] = ERB::Util.html_escape(value.to_s) end end translation = I18n.translate(scope_key_by_partial(key), **html_safe_options.merge(raise: i18n_raise)) if translation.respond_to?(:map) translation.map { |element| element.respond_to?(:html_safe) ? element.html_safe : element } else translation.respond_to?(:html_safe) ? translation.html_safe : translation end else I18n.translate(scope_key_by_partial(key), **options.merge(raise: i18n_raise)) end rescue I18n::MissingTranslationData => e if remaining_defaults.present? translate remaining_defaults.shift, options.merge(default: remaining_defaults) else raise e if raise_error keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope]) title = +"translation missing: #{keys.join('.')}" interpolations = options.except(:default, :scope) if interpolations.any? title << ", " << interpolations.map { |k, v| "#{k}: #{ERB::Util.html_escape(v)}" }.join(", ") end return title unless ActionView::Base.debug_missing_translation content_tag("span", keys.last.to_s.titleize, class: "translation_missing", title: title) end end alias :t :translateずいぶん色々やってますね
ざっくりいうとI18n.tに引数を渡す前に該当するロケールが存在しなかった場合の保険とか、
挙動にまつわるオプションの分岐とかががっつり書かれているわけですねというわけで今後I18n.tってviewに書くプルリクエストが来たら修正してもらいましょう
- 投稿日:2020-02-06T00:01:48+09:00