- 投稿日:2019-12-14T21:38:01+09:00
rubyの機能を活用し、a~zを繋げた文字列を取得するために冗長に書く
***注意***
以下のコードは かなり冗長 となっています。
「これ(a..z).to_a.join
でええやん^^;」はなしでお願いします。本題
最近友人にRubyを教えていて、これが読めればいいんじゃないかな?ってコードを考えてみたところいい感じのが出来たので置いときます。
コード
# 可読性上げるためインデント調整してます。 __send__( define_method( 25.times .inject(false.to_s[1]) { |s| s + s[s.size.pred].next }.to_sym, -> { __method__.to_s } ) )pryでの実行結果
ワンライナーでの実行
解説
コード貼り付けただけだとしょっぱいので、内側から解説していきますw
25.times
define_method
の第一引数であるメソッド名(シンボル)を作成していきます。
25.times
でEnumerator
オブジェクトを作り、自身をレシーバとしたEnumerable
メソッドの実行回数を決めています。
これにより、inject
を25回実行させることができます。inject(false.to_s[1]) { |s| s + s[s.size.pred].next }.to_sym
ここで
(a..z).to_a.join.to_sym
と同様のコードとなるようにします。引数に
'a'
を与え、ブロックの中で「現在の文字列 + (現在の文字列の最後の文字)の次の文字」を行なっています。# 中身はこのイメージ inject('a') {|'a'| 'a' + 'b'} # 'a'の次は'b' inject('ab') {|'ab'| 'ab' + 'c'} # 'b'の次は'c' # ..., zを取得するまで繰り返すこれにより、次の文字を取得してくっつける処理が25回実行されるためa~zが得られます。
最後に
to_sym
でシンボル化し第一引数は完成です。-> { __method__.to_s }
difine_method
へ渡す第二引数であるProc
オブジェクトを作成しています。
__method__
でこのメソッドを呼び出したメソッド名(シンボル)を取得し、Stringオブジェクトに変換します。define_method(name, proc)
第一引数にa~zを繋げた文字列のシンボル、プロックを渡しレシーバへメソッド定義をさせます。
そのままでは定義するだけで終わるので何かしらのメソッドで実行させます。__send__(symbol)
レシーバに対して引数のメソッド名を実行します。
define_method
の戻り値が定義したメソッド名のシンボルであることを利用しそのまま渡します。結果として
-> { __method__.to_s }
が呼ばれ、ここでa~zの文字列が得られました。他の手段ですが、
method(...).()
でも同じようにできます。
この辺は好みだと思うのでどちらでも大丈夫ですねwまとめ
解説は以上です。
このコードでは簡易的ですが、メソッドチェーン、メタプロ、リフレクション、Enumerable、Procオブジェクトが登場します。
脱初心者を目指すにはいい足掛かりにはなるんじゃないでしょうか?(主観100%)
- 投稿日:2019-12-14T17:11:40+09:00
懇親会での障害対応はもうこりごり!�JMeterを使ったRailsアプリの負荷テストの流れ
これは Money Forward Advent Calendar 2019 ? 14日目の記事です。
こんにちは! @machisukeです。
マネーフォワードでは、半年に1回全社員集まっての半期総会を開催しています。
そして昨日、2019年12月13日がちょうど半期総会でした。半期総会後の全社懇親会
で、僕たちマネーフォワード新卒はあるリベンジを果たそうとしていました
![]()
もうサーバーは落とさない。
今年6月に開催された半期総会で、僕たちは懇親会のコンテンツとして「MFクイズダービー」を担当しました。
スマホを使ってリアルタイムに順位が発表されるという内容に大盛り上がりでコンテンツはスタート。
会場の盛り上がりを見た僕たちは、作った甲斐があったなあと安堵していました。しかしその直後に事件は起きます。300名を超える参加者にサーバーが耐えきれず、途中でシステムが止まっていたのです。詳細は弊社のエンジニアブログ「新卒が社内懇親会アプリを開発したら、障害対応まで経験できた話」をぜひ読んでみてください。このままでは終われないと、半年後の全社懇親会でのリベンジを心に決めました。
リベンジの過程で、僕は負荷テストを実施し、その時使った「JMeter」がとても便利で面白かったので、皆さんに手順を共有したいと思います。
(※ちなみに、今回の懇親会が成功したのかどうかは誰かがブログを書くと思うので楽しみに待ちましょう。)JMeter上でのテスト計画と結果のイメージ
このような感じで、JMeterを使ってクイズの参加登録(sign_up)、クイズ取得、クイズ回答などが正しく動作しているか検証できます。本番は50チームで行いますが、テストは300チームで行いました。
Railsアプリの負荷テストに挑戦してみよう
![]()
今回は負荷テストを検証するアプリケーションとしてRailsチュートリアルで作成するSampleAppを拝借したいと思います。
SampleAppはTwitterのように「Micropost」を投稿するサービスです。30ユーザーを同時アクセスさせ、1秒あたり1投稿させても、アプリは落ちることなく動き続けるでしょうか!?
環境
- Mac OS Mojave
- JMeter 5.2.1
- ruby 2.6.5
- rails 5.1.2 (sample_appの最新版に合わせました)
手順
1. jmeterインストール
$ brew install jmeter2. Railsアプリケーションの起動
$ git clone https://github.com/yasslab/sample_apps.git $ cd sample_apps/5_1_2/ch14 $ bundle install $ bundle exec rails db:create $ bundle exec rails db:migrate今回はメール認証を強制的にスキップするため、app/controllers/users_controller.rbに変更を加えます。
①、②の変更を行ってください。app/controllers/users_controller.rb# POST /users def create @user = User.new(user_params) if @user.save # => Validation # Sucess # ①↓コメントアウト #@user.send_activation_email # ②↓追加 @user.activate flash[:info] = "Please check your email to activate your account." redirect_to root_url else # Failure render 'new' end end起動
$ bundle exec rails shttp://localhost:3000 にアクセスすると画面が開かれるはずです。
3. JMeter起動
$ jmeterHTTP Request Defaults作成
Test Plan 右クリック > Add > Config Element > HTTP Request Defaults
起動しているサーバーのアクセス情報を入れます。Thread Gropu(ユーザーグループ)の作成
Test Plan 右クリック > Add > Threads (Users) -> Thread Group
同時にアクセスするユーザー数を適当に決めます。
今回は、30人のユーザーが30秒の間に操作を開始するという設定にします。ユーザ毎に登録内容を変える準備
i番目のユーザーは
name: name_i email: name_i@example.com password: password_iとしましょう。
Test Plan 右リクック > Add > Config Elemennt > Counter
coutnerという変数名で取得できるようにします。4. JMeterでユーザー登録、ログインさせる
ユーザー登録・ログインのリクエストをグルーピングする
Thread Group 右クリック > Add > Logic Controller > Simple Controller
ユーザー登録(sign_up)フォームの取得
sign_up/sign_in 右クリック > Add > Sampler > HTTP Request
名前は
sign_upフォーム取得
にします。
sign_upフォームが表示されるURLは
http://localhost:3000/signup
なので、Pathにsignup
を入力します正しくリクエストできているか検証
Test Plan 右クリック > Add > Listener > View Results Tree
を追加JMeterの上側の緑色の三角ボタンを押してテストをスタートするとリクエスト結果が出ます。
AuthenticityTokenの取得
今回使うRailsアプリはCSRF対策が施されているので、AuthenticityTokenをリクエストパラメーターに含める必要があります。
AuthenticityTokenは、「登録フォーム取得」のレスポンスに含まれています。これは、Regular Expression Extractorで抜き出します。
sign_upフォーム取得 右クリック > Add > Post Processors > Regular Expression Extractor
tokenを正規表現でキャプチャして、authenticity_tokenという変数に代入します。ユーザー登録
sign_up/sign_in 右クリック > Add > Sampler > HTTP Request
名前はsign_up
にします。
urlencodeにチェック入れるのを忘れないようにしましょう。ログイン状態を保持できるようにする(Cookie)
Test Plan 右クリック > Add > Config Element > HTTP Cookie Manager
追加するだけでOKです。
ログイン
登録同様、下記の手順を行います。
- フォーム取得
- authenticity_token抜き出し
- ログイン
sign_up/sign_in 右クリック > Add > Sampler > HTTP Request
名前はsign_inフォーム取得
にします。
sign_inフォーム取得 右クリック > Add > Post Processors > Regular Expression Extractor
tokenを正規表現でキャプチャして、authenticity_tokenという変数に代入します。
sign_up/sign_in 右クリック > Add > Sampler > HTTP Request
名前はsign_in
にします。
試しにログインしてみる
ブラウザで
http://localhost:3000/login
を開き、適当なユーザーでログインして、http://localhost:3000/users
にアクセスしてみる。※テストを実行すると、ユーザーが登録されてDBに保存されます。
テストの度にDBをリセットするとユーザー登録から正しくテストを行うことができます。$ bundle exec rails db:migrate:reset5. 各ユーザーに、Micropost(Tweet)を50件登録させる
sign_in/sign_up同様、下記の手順でMicropostを投稿します。
- フォーム取得
- authenticity_token抜き出し
- 登録
投稿リクエストをグルーピングする
Thread Group 右クリック > Add > Logic Controller > Simple Controller
さらに
Thread Group 右クリック > Add > Logic Controller > Loop Controller
名前は
50回投稿
にします投稿ごとにメッセージを分けるための変数を用意
50回投稿 右リクック > Add > Config Element > Counter
micropost_coutnerという変数名で取得できるようにします。投稿フォーム取得
50回投稿 右クリック > Add > Sampler > HTTP Request
名前は投稿フォーム取得
にします。
URLはhttp://localhost:3000
なので、pathは何も入力しません。AuthenticityToken取得
投稿フォーム取得 右クリック > Add > Post Processors > Regular Expression Extractor
tokenを正規表現でキャプチャして、authenticity_tokenという変数に代入します。投稿
50回投稿 右クリック > Add > Sampler > HTTP Request
名前は投稿
にします。投稿間隔の調整
50回投稿 右クリック > Add > Timer > Constant Timer
投稿間隔を一人につき、1秒1回に調整します。6. Listener(レポート機能)の設定
テストが終わるまでのレスポンスタイムの遷移を見る
Test Plan 右クリック > Add > Listener > jp@gc - Response Times Over Time
7. テスト実行
JMeterの上部にある、緑色の三角ボタンを押したら始まります。
8. テスト結果
サーバは落ちませんでした
ただ、ところどころピークが生まれていて、ログを見るとDBのRollbackが行われている様子。同時書き込みに弱いSQLiteだから発生したRollbackでしょうか・・?まとめ
JMeterは気軽に負荷テストを行えるツールでした。
どのようにインフラ/実装を変えれば、レスポンスタイムが短くなるかを考えてみるのは、課題として面白そうですね。
- 投稿日:2019-12-14T16:53:56+09:00
Railsでクエリが複雑になってきたのでQueryObjectパターンでリファクタをして良かった話
この記事は Opt Technologies Advent Calendar 2019 13 日目の記事です。若干遅刻しました。
12 日目の記事は @gcchaan さんの
なにか書きます
です
14 日目の記事は @naru0504 さんのstyled-componentでモダンなCSS設計入門
です
「RailsでActiveRecordでクエリが複雑でつらいしパフォーマンスも悪くなってきた」
よくある話だと思います。例に漏れず、自分のチームも同じ問題と向き合いましたクエリが複雑になりアプリケーションの修正時にコードリーディングが難しい・修正後の結果予測が困難になるという、まあ言ってしまえば典型的な事例です
これまでに同様の議論は多くなされてきたと思いますが、自分のチームがこの問題と向き合い、「何が問題で・どのように解決し・結果どうなったか・反省点」という観点で事例を書き残しておこうと思います何が問題だったか
プロダクトにとっての問題点
ユーザーに利用してもらうにはあまりにも レスポンスが遅い という状態になってしまっていたことが第一です
プロダクトにとってとても優先度の高い機能を優先して実装していたのですが、それがリリースされたときにはもうだいぶ遅い(レスポンスに30sec以上平気でかかる・・・)状態になっていましたコードベースの問題点
クエリについて話しているのでお察しのことと思いますが、SQLがボトルネックでした
しかし、クエリを改善しようとするも、以下の状況が立ちはだかります
- プロダクトの性質上、集計関数を用いたクエリが必要
- 基本方針として、よく使うscopeを実装し、その組み合わせによってクエリを実現していた
- レポーティングのクエリで、同じテーブルに対して複数の結果セットが欲しいので、とあるscopeが複数のクエリ呼び出しから参照されていた
- scopeは別のscopeも参照していた
- scopeの組み合わせるクエリ組み立て処理はControllerで書いていた
- 一部でクエリの結果に対しての複雑な加工処理もあった(クエリ組み立てがControllerで実施されているので、この処理もControllerで呼び出しされていた)
- Controllerで呼び出されている処理について直接のテストはなかった
- あったのはrswagによるスキーマのテストぐらい
- 各scopeは結構丁寧にテストは書かれていた
イメージとしては以下のような実装をもっとFatにしたものになっていました
foo_controller.rbclass FooController < ApplicationController def index # Controllerでこのようにscopeチェインしてクエリを組み立てていた @foo = Foo.by_foo(params[:some_param]) .of_baz(params[:some_condition], params[:some_condition2]) render json: @foo end def other # 別のメソッドやControllerなどからも呼ばれることもある @foo_other = Foo.of_baz(params[:some_condition], params[:some_condition2]) .by_other render json: @foo_other end endfoo.rbclass Foo < ApplicationRecord scope: by_foo, ->(some_param) { # このように別のscopeを参照している ---↓ select(:foo).group(:foo).order(:foo).sum_bar } scope: sum_bar, ->() { select('SUM(bar) AS bar') } scope: of_baz, -> (cond1,cond2) { baz_condition = make_condition(cond1,cond2) where(baz: baz_condition) } endまとめると、でかいクエリが詳細なテストなしに複数あって、scopeの参照関係も複雑、という形です
どのように解決したか
タイトルにも記載しましたが、QueryObjectパターンを利用しました
QueryObjectについて参考にした資料
- 7 Patterns to Refactor Fat ActiveRecord Models
- Rails - ActiveRecord の scope を Query object で実装するただ適当にQueryObjectを利用しても上手く行かないと思い、以下のような指針でQueryObjectへの切り出しを実施しています
実装の方針
- 「最終的に欲しいクエリ」は必ずQueryObjectに定義する
- 実装自体は引き続きscopeのチェイン
- ただし、scope内から別のscopeを(なるべく)参照しない
- 結果の予測が困難になる理由の一つだったため
- scopeの利用自体は許容(scopeを利用しないとQueryObject側の実装が複雑になりそうだったため)
- 「最終的に欲しいクエリ」同士で重複しているクエリは許容
- scopeという「クエリの断片を組み合わせる処理」を呼び出す部分を共通化するのは「早すぎる抽象化」になりそう
以上のような方針にすることで、以下のような恩恵を受けられます
- 最終的なクエリに対してのテストがQueryObjectの呼び出しだけで実施できるようになる
- scopeの影響範囲が明快になり、変更の結果を予測しやすくなる・影響範囲を限定できる
リファクタの方針
方針を決定したので、リファクタをします
といってもこれ自体は「まずはテスト出来るような形にだけ変更」、「テストを書く」、「リファクタを実施」という鉄則に従って実施しただけです
幸いだったのが、「まずはテスト出来るような形にだけ変更」が非常に容易だった点です先程の例を元に説明します
- 最初に
FooController#index
についてのみをQueryObjectに切り出すfoo_query.rbclass SumOfFooQuery class << self delegate :call, to: :new end def initialize(foo = Foo.all) @foo = foo end def call(params) # ここにFooController#indexに書かれていた処理をまるっとコピペ @foo.by_foo(params[:some_param]) .of_baz(params[:some_condition], params[:some_condition2]) end end
- モデルにQueryObjectを参照したscopeを定義
foo.rbscope :sum_of_foo, SumOfFooQuery
- FooControllerの実装を置き換え
foo_controller.rbclass FooController < ApplicationController def index + @foo = foo.sum_of_foo(params) - @foo = Foo.by_foo(params[:some_param]) - .of_baz(params[:some_condition], params[:some_condition2]) render json: @foo end def other @foo_other = Foo.of_baz(params[:some_condition], params[:some_condition2]) .by_other render json: @foo_other end end
- テストを書く
- リファクタする
- サンプルは割愛
以上のような流れでリファクタリングを順次実施していきました
結果どうなったか
クエリ単位でのテストがあり、影響範囲も狭められたので、特定のエンドポイントから順番に・独立してクエリチューニングを実施できるようになりました
(チューニングはそれはそれで大変だったのですがそれはまた別の話)
scope内から別のscopeを(なるべく)参照しない
、「最終的に欲しいクエリ」同士で重複しているクエリは許容
といった方針も見込み通りにコードの読みやすさや変更しやすさに繋がったという感触ですその後運用していても、変更時に大きく困るような事態にはなっていないので、設計は上手くいったと思っています
反省点
- Controllerに処理書いちゃだめだった
- ここを徹底すべきだった
- scopeの先のscopeの先のscope・・・という道のりを辿った先でのselectを把握してコーディングするのは人間には無理だった
- 作るときはいいけど変更できない
- 「1つのメソッドを短くする」、「DRY」を徹底すればいいってもんじゃなかった
- メソッドの定義をバラけさせればバラけさせるほど「結局何をやりたいのか」が分かりにくくなることもある
- (かと言ってまとめりゃいいってもんでもないので難しい)
- レスポンスに30sec以上という状態は流石にもっと早く手を打てたんじゃ・・・
- turai
- 開発が進むうちにデータが溜まって表出したものが多かったので、開発初期時点でデータを作れるなら作っておくという選択肢は今後頭に入れておきたい
- 機能追加の優先度が高かったので、パフォーマンスがヤバくなるかもしれないとわかってても優先度を変更するかは判断が難しかったと思う
- ので、普段から変更に強い設計にしておく・設計を身に着けておくという再現性のない反省になってしまう・・・
ただ、以下のような良かった点もあって、この前提がなかったらもっと困難な課題になっていたと思います
- scopeで非常に汎用的なクエリを表現していた
- 例えば見込み値の算出クエリなど
- このような複雑な処理がControllerに氾濫していたらQueryObjectへの移行が困難だったと思う
- scope単位のユニットテストはあった
- テスト大事・・・
- Controllerはクエリ組み立てとrender用の多少の加工だけでFatにはなっていなかった
- Fatになる前にちゃんとリファクタに手を付けられたとも言えるかも
おわりに
ということで、Railsでクエリが複雑になってきたのでQueryObjectパターンでリファクタをして良かった話でした
今回の事例がどこかのRailsプロダクトの参考になれば幸いです
- 投稿日:2019-12-14T15:09:55+09:00
自分用 Ruby on Rails 初歩メモ
Rubyの要注意基本文法
- 文字列(String) とシンボル(Symbol) の違いに注意
https://qiita.com/Kta-M/items/53a13ef60e14fcb41193- 文字列内の変数展開は ‘#{hoge}’ 表記
名前付きルートの使い方
- 対象のルーティングの末尾に ‘as HOGE’ を追記
- View で対象のルーティングに遷移するリンクのURLにて’HOGE_path’ と指定
- もし、対象のルーティングにパラメータを渡す際は、 ‘HOGE_path(x)’ の表記になる。
インスタンス変数とローカル変数
@hoge
: インスタンス変数、ビューに渡せない
hoge
: ローカル変数、ビューに渡せるコントローラの基本7アクション
- new データを新規作成する
- create データを追加(保存)する
- index データの一覧を表示する
- show データの内容(詳細)を表示する
- edit データを更新するためのフォームを作成する
- update データを更新する
- destroy データを削除する
View で利用するタグについて
<% %>
では、タグ内の結果が画面に表示されずに処理される。
<%= %>
では、タグ内の結果が画面に表示される。form_for における POST, PATCHの判定
form_forにおけるsubmit時のメソッドについて、特にPOST, PATCHから適切なものが指定される。
これは、コントローラから渡されたオブジェクトがEmptyかNotEmptyかによってActiveRecordのnew_record?メソッドは自動で判別してくれているため。
- 投稿日:2019-12-14T15:01:48+09:00
Runteqに入ってから意識的に変わったこと
はじめに
私は10月から渋谷のRunteqというスクールに通っています。
それまでに開発の実務経験はなく、rails tutorialを3ヶ月ほど独学で勉強していました。
Runteqに入ってから2ヶ月ちょっと経ち、技術的な事はもちろんですが、その他に意識的に変わったなと思うことがあるのでご紹介します。
開発経験者の方からすると当たり前と思うこともあるかもしれませんが、温かい目で見て頂けると嬉しいです。対象者
初学者の方、特に独学でrailsを勉強されてる方
公式ドキュメントを読むようになった
Qiitaの記事に書いてある通りに実行してるのに上手くいかないという事は皆さん経験があると思います。記事の通りにならないのは、記事を書いている人と環境、特にバージョンが異なっている場合が多く、そのような時は公式ドキュメントが非常に参考になります。
(その他にも、勉強し始めの頃から公式ドキュメントを読む利点として、今後必要な情報が全てqiitaにわかりやすくまとめられているわけではないために公式ドキュメントを読む練習をするというのがあると思います。)railsの場合、githubが公式ドキュメントとなっていることが多く、例えばsorceryというgemだと
<>Code
タブやWiki
タブを見て使い方を理解します。
説明の代わりにソースコードが載っている事もあり、察する力も大事なのかなと思います。rails tutorial等をやっていたら、理解もずっと早くなりますよ。大体英語で書かれているため、分かりづらい時はgoogle翻訳や、補助的にQiitaを使ったりするのがオススメです。
新しいツールに手を出すようになった
Runteqの教室内やslackにて、意見交換をしたり、オススメのツールが紹介される事がよくあります。
スクールに入ったのをきっかけに、色々経験してみようと思い、紹介されたツールを全部試していました。
以前の自分は、ツールをインストールするより目の前の作業に時間を使いたいという思考をしていました。実際本格的なツールになるほど複雑な使い方を覚えなくてはいけないというのはあると思うのですが、だからといって躊躇っていると今後使うツールの幅がぐっと狭くなり、結果効率化から遠のいてしまいます。
使ってる数が多いほど良いというものではありませんが、自分と合うツールは積極的に取り入れていきましょう。日々の業務が効率的になります。自分のオススメのツールを書いてみます。色々試してみてください。
- google keep:タスク等をメモするのに使っています。タグ付けもできるので便利です。
- HyperSwitch:アプリ切り替えの際、ウィンドウ単位で切り替えられるようになります。
- Magnet:Macでの画面分割が楽にできます。
- cvim:vimの操作でchromeを閲覧できます。vimを使っている方は是非。
- rubymine:rubyの便利機能が詰まっているエディタです。
ショートカットコマンドを覚えるようになった
Runteqにて質問をする時にいつも思うのですが、講師の方はPCの操作が早いのです。
ショートカットコマンドを多用しているのがその要因の一つではないかと思い、ショートカットコマンドを覚えようと意識してみました。覚えるのはなかなか大変ですが、2つ〜3つずつくらいを目安にちょっとずつ覚えていくと段々使えるようになってきます。
また、自分だけかもしれませんが、ショートカットを使えるようになると、操作が早くなるだけでなく、使ってて楽しく感じます。自分が特に使えると思ったショートカットコマンドをいくつかご紹介します。
使い慣れていない方は、まずはこれらから試してみて、使いこなせるようになってきたら新しいショートカットコマンドを模索してみてください。chrome
command + tab
:アプリケーションの切り替え(shiftを押しながらだと逆向きに切り替え)
control + tab
:タブを1つ右に切り替え(shiftを押しながらだと1つ左に切り替え)
command + [
:ページを1つ戻る( ]だと1つ進む)vimを使っている方であれば、cvimをインストールして、上記ショートカットと組み合わせるとより快適になります。
ターミナル
control+a
:カーソルを一番左に移動
control+e
:カーソルを一番右に移動
control+l
:画面をクリア
control+u
:カーソルより左を削除
control+k
:カーソルより右を削除まとめ
「Runteqに入ってから意識的に変わった事」として紹介しましたが、今考えるとどれも独学の頃から意識できた事だと思います。
独学の頃は目の前の作業に夢中になっていましたが、少しゆとりを持って手を広げてみるともっと日々のエンジニア生活が楽しくなると思うので、是非色々試してみてください。
- 投稿日:2019-12-14T12:28:32+09:00
Windows Subsystem for Linuxでmysqlを使えるようにする。
環境
Windows 10 pro
Ubuntu 18.04 LTS
ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-linux]
Rails 5.2.4
gem 2.7.6
Bundler version 2.0.2mysqlの前に
github上で
$ rails _5.2.4_ new example -d mysqlをして新しい環境を作った後、
Gemfileをgem 'mysql2', '0.5.2'と編集されたものを、
git pullした後に、
WSL上でbundle installしようとしたが、$ bundle install * * * Fetching mysql2 0.5.2 Installing mysql2 0.5.2 with native extensions Gem::Ext::BuildError: ERROR: Failed to build gem native extension. * * * ----- mysql client is missing. You may need to 'apt-get install libmysqlclient-dev' or 'yum install mysql-devel', and try again. ----- *** extconf.rb failed *** Could not create Makefile due to some reason, probably lack of necessary libraries and/or headers. Check the mkmf.log file for more details. You may need configuration options. * * * An error occurred while installing mysql2 (0.5.2), and Bundler cannot continue. Make sure that `gem install mysql2 -v '0.5.2' --source 'https://rubygems.org/'` succeeds before bundling. In Gemfile: mysql2とエラーが出てしまった(汗)
----- mysql client is missing. You may need to 'apt-get install libmysqlclient-dev' or 'yum install mysql-devel', and try again. -----この箇所を参考に
$ sudo apt-get install libmysqlclient-devした後、
Make sure that `gem install mysql2 -v '0.5.2' --source 'https://rubygems.org/'` succeeds before bundling.この箇所を参考に
$ gem install mysql2 -v '0.5.2' --source 'https://rubygems.org/' Successfully installed mysql2-0.5.2 Parsing documentation for mysql2-0.5.2 Installing ri documentation for mysql2-0.5.2 Done installing documentation for mysql2 after 0 seconds 1 gem installedこうすると、
$ bundle install $ bundle updateがそれぞれ実行できて、gemの環境が整った(^^)/
mysqlを使えるようにする
試しにmysqlの状態を確認すると、
$ mysql ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)mysqlをstartさせようと試みた後、
$ sudo service mysql start * Starting MySQL database server mysqld No directory, logging in with HOME=/もう一度、mysqlの状態を確認すると、
$ mysql ERROR 1045 (28000): Access denied for user 'example'@'localhost' (using password: NO)こちらのサイトによると、
パスワードを設定しなおさなければならないらしい。$ sudo mysqld_safe --skip-grant-tables &セーフモードで実行したのち、
$ sudo mysql -u root Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 7 Server version: 5.7.28-0ubuntu0.18.04.4 (Ubuntu) Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> use mysql; Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A Database changed mysql> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | mysql | | performance_schema | | sys | +--------------------+ 4 rows in set (0.00 sec) // データベースの状態を確認したよ mysql> update user set authentication_string=password("パスワード") where user='root'; ERROR 1819 (HY000): Your password does not satisfy the current policy requirements // パスワードは英数字と文字を組み合わせないとだめですよ mysql> update user set authentication_string=password("英数字と文字をいれたパスワード") where user='root'; Query OK, 1 row affected, 1 warning (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 1$ sudo service mysql start * Starting MySQL database server mysqld$ mysql --version mysql Ver 14.14 Distrib 5.7.28, for Linux (x86_64) using EditLine wrapperこういう状態になりました(^▽^)/
素人がやっているので説明不足等あるとは思いますが、
ご指摘の程、よろしくお願いします!!
- 投稿日:2019-12-14T10:02:00+09:00
【トリビア】Rubyにおける3連引用符(ダブルクオート3つ)は何を意味しているのか?
はじめに:Rubyクイズ!!
Rubyプログラマのみなさん、こんにちは。
突然ですが、問題です。
以下のコードを実行すると何が起きるでしょうか?sample.rbmsg = """ Hello, world. I am Ruby. """ puts msgはい、実行結果はこうです。
$ ruby sample.rb Hello, world. I am Ruby.どうですか?想像通りの結果になりましたか?
それではまた。ごきげんよう。・・・ではなくて、上のコードに出てきた3連引用符(
"""
)ってみなさん知ってましたか?
Rubyを長年やってる人もあまり見たことはないんじゃないでしょうか。
というか僕も最近知りました。
"""
の種明かし「Rubyに
"""
なんていう構文はあったっけな〜??」と思ってネットや書籍を調べても、たぶんそんな構文はどこにも載っていないと思います。これ、実は
""" = "" + "
の意味になっているだけなんです。
つまり、先ほどのコードはこう書いているのとほぼ同じです。msg = "" + " Hello, world. I am Ruby. " + ""結果として、こう書いていることと同じになります。
msg = " Hello, world. I am Ruby. "ウソだ!と思っている人も、
puts
の代わりにp
を使って確認すると、納得がいくかもしれません。msg = """ Hello, world. I am Ruby. """ p msg #=> "\nHello, world.\nI am Ruby.\n"公式リファレンスの説明を確認する
公式リファレンスにも以下のような説明が載っています。
空白を間に挟んだ文字列リテラルは、コンパイル時に1つの文字列リテラルと見倣されます。
p "foo" "bar" => "foobar"リファレンスには「空白を間に挟む」とありますが、空白なしでも1つの文字列になるようです。
p "foo""bar" #=> "foobar"よって、
"""abc"""
は、
"" "abc" ""と書いていることになり、結果として、
"abc"
と書いたことになるわけです。
ちなみに、シングルクオートで書く場合も考え方は同じです。
p 'foo' 'bar' #=> "foobar" p '''abc''' #=> "abc"余談:C言語も同じ文法を持っています
「隣り合った文字列リテラルは1つの文字列として扱われる」という文法は、C言語でも同じだそうです。
というか、C言語の影響を受けているとこういう文法が用意される、と言った方が正しいかもしれません。#include <stdio.h> int main(void) { // 実行すると"Hello, World!"になる printf("Hello, " "World!\n"); return 0; }参考 https://stackoverflow.com/a/23811427/1058763
でもなんで
"""
なの?しかし、なんでRubyでわざわざ
"""
なんて書く人が出てくるんでしょうか?
僕は"""
を自分で書いたことがないので、これは僕の推測になりますが、PythonやCoffeeScriptなど、他の言語の文字列リテラル(改行が入れられる文字列リテラル)をうっかりRubyでも書いてしまった、という理由があるのかもしれません。Pythonの場合
print("""\ Usage: thingy [OPTIONS] -h Display this usage message -H hostname Hostname to connect to """)出典 https://docs.python.org/ja/3/tutorial/introduction.html#strings
CofeeScriptの場合
html = """ <strong> cup of coffeescript </strong> """出典 https://coffeescript.org/#strings
しかし、Rubyであればこういうケースは
"""
ではなく、ヒアドキュメント構文を使うのが良いと思います。msg = <<TEXT Hello, world. I am Ruby. TEXT puts msg #=> Hello, world. # I am Ruby.まとめ
というわけで、この記事では摩訶不思議なRubyの3連引用符(ダブルクオート3つ、またはシングルクオート3つ)の謎について説明しました。
日常的に使うことはまったくオススメしませんが、ちょっとしたトリビアとして「へえ〜」と思ってもらえれば幸いです?
- 投稿日:2019-12-14T09:44:07+09:00
VBScriptをつくってみる(制御構文編)
前回まででVBScriptの変数宣言機能・変数代入機能・演算機能を作りました。
今回は制御構文(If, For)を作ってみます。まずは、制御構文に対応するように構文解析器を改良します。
VBScriptの文法を知っていればそんなに難しくない構文解析ルールですね。
一部TODOがありますが、そこは見なかったことにして下さい。block_statement : var_declartion | if_statement | loop_statement | for_statement | select_statement | inline_statement nl # If Statement if_statement : 'If' expr 'Then' nl block_statement_list else_statement_list 'End' 'If' nl { result = AST::IfStatement.new(val[1], val[4], val[5]) } | 'If' expr 'Then' inline_statement else_opt end_if_opt nl { result = AST::IfStatement.new(val[1], val[3], val[4]) } else_statement_list : 'Elseif' expr 'Then' nl block_statement_list else_statement_list { result = AST::IfStatement.new(val[1], val[4], val[5]) } | 'Elseif' expr 'Then' inline_statement nl else_statement_list { result = AST::IfStatement.new(val[1], val[3], val[5]) } | 'Else' nl block_statement_list { result = AST::ElseStatementList.new(val[2]) } | 'Else' inline_statement nl { result = AST::ElseStatementList.new(val[1]) } | { result = AST::Nop.new } else_opt : 'Else' inline_statement { result = val[1] } | { result = AST::Nop.new } end_if_opt : 'End' 'If' | loop_statement : 'Do' 'While' expr nl block_statement_list 'Loop' nl { result = AST::WhileStatement.new(val[2], val[4]) } | 'Do' 'Until' expr nl block_statement_list 'Loop' nl { result = AST::UntilStatement.new(val[2], val[4]) } | 'Do' nl block_statement_list 'Loop' 'While' expr nl { result = AST::DoWhileStatement.new(val[5], val[2]) } | 'Do' nl block_statement_list 'Loop' 'Until' expr nl { result = AST::DoUntilStatement.new(val[5], val[2]) } | 'Do' nl block_statement_list 'Loop' nl { result = AST::Nop.new } # TODO: Exit文が実装されたらLoop文も実装する | 'While' expr nl block_statement_list 'Wend' nl { result = AST::WhileStatement.new(val[1], val[3]) } for_statement : 'For' extended_id '=' expr 'To' expr step_opt nl block_statement_list 'Next' nl { result = AST::ForStatement.new(val[1], val[3], val[5], val[6], val[> | 'For' 'Each' extended_id 'In' expr nl block_statement_list 'Next' nl { result = AST::Nop.new } # TODO: 配列が実装されたらForEach文を実装する step_opt : 'Step' expr { result = val[1] } | { result = AST::NumberLiteral.new(1) } # Select Statement select_statement : 'Select' 'Case' expr nl case_statement_list 'End' 'Select' nl { result = AST::SelectStatement.new(val[2], val[4]) } case_statement_list : 'Case' expr nl_opt block_statement_list case_statement_list { result = val[4].unshift(AST::CaseStatement.new(val[1], val[3])) } | 'Case' 'Else' nl_opt block_statement_list { result = [AST::CaseElseStatement.new(val[3])] } | { result = [] }次に各制御構文の実装を紹介します。
まずは、If文です。
条件式をevalし、その結果がTrueかFalseかに応じてThen節もしくはElse節の実行(eval)を行います。class IfStatement < List def initialize(expr, then_statement_list, else_statement_list) super([expr, then_statement_list, else_statement_list]) end def eval(environment) if child(0).eval(environment) child(1).eval(environment) else child(2).eval(environment) end end endWhile文は特に説明をしなくても自明かと思います。
class WhileStatement < List def initialize(expr, block_statement_list) @expr = expr super([block_statement_list]) end def eval(environment) while @expr.eval(environment) do child(0).eval(environment) end end end最後にFor文の紹介です。
For文は引数が多いために実装がやや面倒くさいです。
To句やStep句には即値だけでなく式も書けることの考慮が必要です。class ForStatement < List def initialize(id, from, to, step, block_statement_list) @id = id @from = from @to = to @step = step super([block_statement_list]) end def eval(environment) environment[@id.identifier] = @from.eval(environment) to = @to.eval(environment) step = @step.eval(environment) if step >= 0 while environment[@id.identifier] <= to child(0).eval(environment) environment[@id.identifier] += step end else while environment[@id.identifier] >= to child(0).eval(environment) environment[@id.identifier] += step end end end endここまでの機能を使うことで、フィボナッチ数のN項目を計算できるようになりました。
' フィボナッチ数のn項目を計算する Dim n, tmp1, tmp2, answer n = 10 tmp1 = 1 tmp2 = 1 If n = 1 Or n = 2 Then answer = 1 Else For i = 1 To n - 2 Step 1 answer = tmp1 + tmp2 tmp1 = tmp2 tmp2 = answer Next End If ' この時点でanswerに答えがセットされている怒涛のVBScript記事連投は、VBScriptっぽい文法を持ったチューリング完全な言語が完成したあたりで一旦中断します。
気が向いたら、また来年この続きから言語実装を始めるかもしれません。明日のZOZOテクノロジーズアドベントカレンダー#3 @zt_takumi_ito さんです。
是非そちらもご覧ください!
- 投稿日:2019-12-14T09:25:17+09:00
Ruby on Railsの現場でよく見る書き方【実例あり】
はじめに
こんにちは、@ryokky59と申します!
未経験からエンジニアになって1年経ちました!
今回はRailsを実務で1年間触ってきて、この書き方は最初に知っておけばスムーズにソースコードが読めたなぁと感じたことをまとめてみました!この記事で実務と独学の壁がすこしでも埋められるお手伝いができたらいいなと思います!
対象読者
- 未経験からエンジニアを目指してRailsを勉強している方
- 普段は他言語を使っているけど今度はRailsを使う現場に行かれる方
- 自社以外のRailsの書き方を知ってみたい方
Railsのいろんな書き方
埋め込みRuby
文字列の中にRubyの式や変数を埋め込める
user_name = "taro" "こんにちは#{user_name}さん。今日は#{Date.today}です" # => "こんにちはtaroさん。今日は2019-12-14です"
""
(ダブルクォーテーション)じゃないとRubyが展開されないので注意です。
||=
変数に値を入れるときに、変数がnilかfalseのときのみ値を入れることができます
user1 = "taro" user1 ||= "jiro" user1 # => "taro" ("jiro"が代入されていない) user2 = nil user2 ||= "jiro" user2 # => "jiro" ("jiro"が代入されている)
後置if
例えば以下のようなfalse側がいらないif文がある時
if true "trueでhoge" end # => "trueでhoge"こういう時は以下のように一行で書くことができます
"trueでhoge" if true # => "trueでhoge"例を挙げるとメソッドから抜ける時によく使われます(ガード節といいます。詳しくはコチラ)
def puts_user_name(user_id) user = User.find_by(id: user_id) return if user.nil? # もしuserがnilだったらreturnでメソッドから抜ける puts user.name end
三項演算子
例えば以下のような普通のif文があるとき、
if true puts("trueでhoge") else puts("falseでhuga") end # trueでhogeこのような
if~else~end
で終わるようなif文であれば一行で以下のように書くことができますtrue ? puts("trueでhoge") : puts("falseでhuga") # trueでhoge
メソッドの引数
その1メソッドの引数の書き方は
()
でも(半角スペース)でも大丈夫です。
User.find(1) # => {id: 1, name: "idが1のユーザー"} User.find 1 # => {id: 1, name: "idが1のユーザー"}使い分けとしては例えば、上の三項演算子の例では
puts "trueでhoge"
みたいに()
ではなく、(半角スペース)で書くと
SyntaxError unexpected ':'
がでてしまいます。
三項演算子は(半角スペース)で次にどの文字がきて欲しいかを予測します。
なのでputs "trueでhoge"
と書いてしまうとputs
の次が(半角スペース)で
:
が来ることを期待しているのに"trueでhoge"
が来ているのでエラーになります。このように、
()
で囲って一つの文として評価されたいか、(半角スペース)で繋げて読みやすくするかを使い分けることができます。
メソッドの引数
その2メソッドに渡す引数の種類もいくつか種類があります。
デフォルト引数
引数にデフォルト値を入れておいて、その引数がなければデフォルト値を使います
def puts_string(string="文字列") puts string end puts_string # "文字列"キーワード引数
引数をハッシュで指定する。
実行するときにキーワードも指定する必要がある。
引数が複数だったり、明示的にしたいときに使われる。def puts_string(string:) puts string end puts_string(string: "文字列") # "文字列"可変長引数
引数を配列として受け取ることができる
def name_array(*name) p name end puts_name_array("taro", "jiro", "saburo") # => ["taro", "jiro", "saburo"]
!◯◯(変数)
変数の前に
!
を書くと中身を反転したbooleanの形(trueまたはfalse)に変換することができます下記のように
string
という変数の前に一つ!
をつけると反転してfalse、さらにもう一つ!
をつけるとさらに反転してtrueを返します。string = "文字列" string # => "文字列" !string # => false !!string # => true変数の中身をnilにすると理解しやすいかもしれません
nil_val = nil nil_val # => nil !nil_val # => true !!nil_val # => false
%記法
配列はよく%記法を用いて書かれます。
%w(hoge huga foo bar) # => ["hoge", "huga", "foo", "bar"]注意するところは配列の中身は必ずstringになるというところです
%w(1 2 3) # => ["1", "2", "3"]シンボルにすることもできます
%i(new create delete) # => [:new, :create, :delete]
{}
ブロックは
do~end
で書くことが多いかもしれませんが{}
を使って一行で書く時に読みやすくすることができます。%w(hoge huga foo).each do |string| puts string end%w(hoge huga foo).each{ |string| puts string }この二つはどちらも同じ結果を返します。
do
が{
に、end
が}
に置き換わっただけですね
ブロックの中身が短く単純な時に便利です。[1, 2, 3, 4].select { |item| item % 2 == 0 } # => [2, 4]上のように
select
やany?
など配列、ハッシュを扱うメソッドを使う時によく見られます
map(&:〇〇)
こちらは先ほどの
{}
で囲むブロック分よりさらに短縮したような書き方です。users.map { |user| user.name } # => ["taro", "jiro", "saburo"] users.map(&:name) # => ["taro", "jiro", "saburo"]上の例ではオブジェクトのキーを指定して値を取り出しています
これはブロックをprocに変換しているからできます。
実際はmapメソッドだけでなく他の配列、ハッシュを扱うメソッドで使うことができます。procとかは聞き慣れないかなと思うので参考記事を置いておきます
Rubyのmap &:to_iとはなんなのか
Ruby Procについて学ぶitems.sort_by(&:created_at) # itemのcreated_atが古い順に並び替える
includes
引数に書いたモデルを先読みしてキャッシュしておく。
簡単にいうと不要なSQLを発行しないようにしてパフォーマンス低下を防ぐ(N+1問題を防ぐ)@user = User.all.includes(:items)↑のようにしておくと@user.itemsをeachで回したときにitem.nameとかでitemの数だけSQLが発行されるのを防ぐ
詳しくは下記記事を参考にどうぞ
Rails で includes して N+1 問題対策
&.
(セーフナビゲーション)別名
ぼっち演算子
エラーの代わりにnilを返してくれます。# userのnameがnilのとき user.name.email # => NoMethodError: undefined method `length' for nil:NilClass user.name&.email # => nilnilの値であるレシーバ(name)に対してlengthメソッドを実行するとエラーが出ますが、
&.
でチェーンすることでnilに変わります。
nilが入る可能性のあるカラムを操作するときによく使われます。
each_with_index
eachで回すときにindexが欲しいときに使います。
users = %w(taro jiro saburo) users.each_with_index do |user, index| puts "user: #{user}, index: #{index}" end # user: taro, index: 0 # user: jiro, index: 1 # user: saburo, index: 2indexを任意の数字で始めることもできます
users = %w(taro jiro saburo) # eachにチェーンしてwith_indexを繋げていることに注意 users.each.with_index(1) do |user, index| puts "user: #{user}, index: #{index}" end # user: taro, index: 1 # user: jiro, index: 2 # user: saburo, index: 3おわりに
現場では他にも色々書き方はあると思いますが、ここまでの内容を抑えておけばそこそこスムーズにソースコードを読めるのではないでしょうか?
他にも「うちではこんな書き方あるよ!」みたいな方はお気軽にコメントください!他記事への参考リンク
これからRailsを触っていく方に役に立つなと感じたリンクです。
(一部この記事を書く際にも参考にさせていただきました。m(_ _)m)
他言語経験者がRailsの案件にジョインしたときに、何を足掛かりにすべきか
差をつけるRuby
- 投稿日:2019-12-14T04:22:33+09:00
【Rails】オブジェクト指向について
はじめに
Rubyに最初に触れる方はオブジェクト指向という言葉はよく聞くけれどいまいち何のことを言っているかわからないという人も多いと思います。なので、このオブジェクト指向について自分の思考の整理も兼ねて記事にしました。
ちなみにオブジェクト指向はRuby以外にもjava,phpなどにも用いられる概念です。オブジェクト指向とは
簡単にいうとオブジェクト指向とはオブジェクトを中心に回り、複数のオブジェクトを組み合わせることでプログラムを構築していくという考え方のことです。
オブジェクトとは
オブジェクトとはデータや処理の集まりのことを指します。
ただし闇雲なデータや処理が集まったものではなく、一つのテーマの下で集まったものです。
イメージとしては、オブジェクトというのは概念のようなものではクラスとインスタンスによってできているという感じです。
またオブジェクトは振る舞いというものを持っています。振る舞いとは自分自身に対する操作のことで、
仮にAさんとBさんという別々のオブジェクトがいた場合に、名前を教えてと言うと、Aさんは「私はAです」、Bさんは「私はBです」というふうに別々の振る舞いを持っています。クラスとは
オブジェクト指向には大切な概念としてクラスというものが存在します。
簡単にいうとオブジェクトの設計図のことで、一つのテーマに沿った振る舞いや情報の保持などをひとまとまりにしたものです。
今回はCarというクラスを作ってみます。class Car def initialize(car_name, mileage, color) @car_name = car_name @mileage = mileage @color = color end def answer_car_name puts @car_name end end //実行結果 Prius = Car.new("prius", "50000", "blue") => #<Car:0x007fc55c1604f8 @car_name="prius", @mileage="50000", @color="blue"> irb(main):016:0> Car.answer_car_name priusCarというクラスにはanswer_car_nameというCar自身の振る舞いが含まれています。
このようにクラスを設定するとこの一つの操作(名前を出力する)についてはこのオブジェクトに任せることができます。開発者にとってのオブジェクト指向
オブジェクト指向はユーザーにとって何かしらの利点があるわけではありません。あくまでも開発者が円滑にコミュニケーションをとるために考えられた概念です。
守るべき原則
DRY
"Don't Repeat Yourself"の略で、繰り返しを避けるという意味です。
これはコードの量が増えることを避けて、できるだけバグの原因をなくそうという考え方です。
また、コードを書き直す際に、重複した表現を書いていた場合、どちらも直さないといけなくなり、無駄な労力を割いてしまいます。YAGNI
"You ain't gonna need it"の略で、必要になったときに必要な機能だけ実装すると言う原則です。
必要になるかもしれない、と言う理由で使わないコードを書いてしまった場合、後々それがバグの原因になることがあります。単一責任の原則
クラスが持つ役割は一つだけにすると言う原則です。もしもCalendarクラスがあるとします。Calenderクラスには予定を操作する機能と、予定をグラフで可視化する機能があるとします、しかしその二つの機能を一つのクラスが持つことは望ましくありません。なぜなら、予定をグラフで可視化する機能を編集したいときに、カレンダーを操作する機能を司る部分も編集する必要が出てくるかもしれないからです。なのでしっかりと二つにクラスをわけ、変更したい機能のみを編集するためにクラスを分ます。
インターフェイス分離の原則
複数のクライアントが使用する機能の場合、どのクライアントにも対応したような網羅的な機能の場合、一クライアントが使っているときに使わないメソッドなどが出てきてしまいます。そのような場合は望ましくなく、適切な処理ができない場合があります。なので、一クライアント単位で使う機能をグループ分けしあまり網羅的な機能を作らないように心がけることが大切です。
まとめ
いかがだったでしょうか。このオブジェクト指向はとてもわかりづらいと感じてしまったかもしれません。しかし、実際に自分の手でコードを書いて再びこの考え方を復習したときに理解度がより深まるのではないかと思います。とにかくコードを書き、オブジェクト指向の全体の流れのようなものを掴むことをお勧めします。
- 投稿日:2019-12-14T02:05:03+09:00
【ポートフォリオ】UROGを開発しました
どんなアプリを作ったか - 概要
カテゴリー毎にURLを分けて保存できるメモアプリです。
https://www.urog.me/
お試しで利用したい方は、以下のemail,passwordを使ってみてください。
(もちろん新しくユーザー登録も可能です)email: aaaaa@email.com password: aaaaaaaagithub:https://github.com/koao123/urog_app
アプリ・筆者についての解説
使用言語・フレームワーク:Ruby・Rails・HTML・CSS・BOOTSTRAP
使用ツール: github, heroku, AWS(cloud9)開発期間:2ヶ月
勉強期間:半年このアプリでできること
高い一覧性で投稿を見ることが可能です。
なぜこのアプリを作ったか
私は将来役に立つだろう記事(Web)や、気づきを与えられる記事に遭遇すると、将来のために保存するようにしています。しかし実際問題、必要な時にその記事を保管場所から取り出せることがほとんどありません。
この問題が改善され、緊急性が上がり必要になった情報(Webページ)を必要な時に入手できるようになれば、
将来の無駄な時間が削減され自分の成長効率が上がると考えました。具体的に何を解決しようとした結果、このアプリを作ったのか
保存したWeb記事を閲覧する際のハードルとして、「一覧性の低さ」が挙げられる。これを解決するためにこのアプリを作りました。
ーーー
現状では、記事をどのサイトのどの場所に置いたか分からず、辿り着くまでにかなりの手間がかかるようになっています。
(記事保存先検索候補が多く、TOPページで全体把握できないため、更にそこから探す必要があります)そして、理想の状態は
「探すべき場所が一箇所で、その場所にたどり着いた後、保存した記事をすぐに見つけれる」ことです。この状態だと、素早く記事を探せるので、実用性があると考えました。
理想から考える現状のギャップとしては
「保存場所に行った後、保存した記事の"一覧性が低い"こと(メモアプリなので、二階層。全て初めのページで見れない)」と「"カテゴリー分け等がされていない"ので、保存ページに行った後、どの範囲を探せば良いか分からない」ということです。そのため「一ページで全体が把握できるほど一覧性が高く」「カテゴリー分けができる」メモアプリを作ろうと考え、このアプリを作りました。
どうやって作ったのか
以下の手順で行いました。
サービス企画
理想と現状のギャップを考え、最低限どのような機能を持ったアプリを作ればその課題は解決できそうか?を考えた
↓
必要な機能の洗い出し
DB設計(postテーブル・userテーブル等)
UI設計
↓
バックエンドをRailsで実装
↓
フロントエンドを実装(HTML、CSS)その過程で工夫した点
サービス設計
ユーザーにとってどんな機能があれば使うか?何があれば課題を解決出来そうか?を自分の行動を分解し突き詰めて考えました。(対象ユーザーは自分なので)
カテゴリー機能
ただURLが投稿できるだけではなく、カテゴリー毎に投稿できるようにした。これにより、URLがカテゴリー毎に探しやすくなる。
LPに書く文章の工夫
このアプリの価値とターゲットユーザーを考え、ターゲットユーザーが「このサービスいいな」と思うような文章を考えた。
苦しんだ点(その解決過程)と学び
正体不明のエラーの対応
特に苦しんだ点は、エラーを読んでも分からず、エラーメッセージを検索してもあまり分からなかったエラーです。こういう時はエラーメッセージが示していることや、検索語のページで特定の方が言っていることが理解できていないことが多かったです(自分は相手が見えている構造を理解できていないため)。なので今後は、行き詰まった時は見える情報から構造を確認し、エラーの対処に当たろうと思います。
サービス設計と実際の開発との解離
サービス設計ののち、DB設計とUI設計をきっちり行ったが、実際に開発すると、認識できていなかった技術的に難しいなどの問題が多数出てきて設計をやり直すことになりました。当たり前ですが、自分で実装できることの重要さを感じました。
このアプリの課題
新たなる問題
このアプリの目的は、「緊急性が低いURLを保存しておき、緊急性が高くなった時に取り出せるようにすること」です。この問題は、このアプリを作ったことによりある程度解決されたと思っているが、私は「保存時にWebアプリを開くのが面倒」と感じてしまっています。
そのため、この問題を解決する必要があると考えています(LINE APIを利用すれば可能・・・?)保守性の問題
今回はテストを書いておらず、全く後の改善時のことを考えていないので、取り組もうと思います。
フロントの実装
フロントは見よう見まねで実装したため、正直なところちゃんとしたcssも書けておらず、レスポンシブ対応さえできていません。Ajax対応もさせる必要があるので、早く勉強して取り組もうと思っています。
さいごに
実際に動くアプリを完成させれたのは自信になったが、自分の技術レベルの低さを目の当たりにすることになりました。これからも勉強して行きたいと思います・・・。
※これは、就職活動をする上で見ていただく採用担当の方向けに書いた記事です。
- 投稿日:2019-12-14T01:29:56+09:00
【Ruby】文字列の文字数を求めるアルゴリズム
この記事ではRuby 2.6を使っています。
count
やlength
、size
を使用せずに文字列の文字数を取得するというやつです。
for
やsplit
の使い方の練習も兼ねてやってみました。要件
count("文字列")
のように、関数に対して文字列の引数を渡すと、文字列の文字数が返却されるsize
,length
,count
メソッドを使用してはならない例えば
"hello!"
という文字列を与えたときに6が返ってきたら成功です。手順のフロー化
手順をフロー化します。
- 文字列を取得する。
- 文字列を配列として1文字ずつ分割
- 配列を繰り返し処理、その都度変数に+1を実行
実装
.split("")
のように引数に空文字列""を指定すると、1文字ごとに分割した配列を返します。
ここでは文字列"hello!"
が配列["h", "e", "l", "l", "o", "!"]
となります。count.rbdef array(str) array = str.split("") end配列に対して
for
を使って繰り返し処理をしていきます。
繰り返し処理のたびに変数count
に+1をしていきます。count.rbdef count(str) count = 0 for chara in array(str) do count += 1 end endcount.rbp count("Hello!") #6が返ってきた6が返って来るので成功です!
今回はfor
を使用していますが、while
やeach
でも同様にできます。ありがとうございました。
- 投稿日:2019-12-14T00:31:02+09:00
dry-monadsを使ってRubyでモナドの夢を見る
はじめに
皆さんはRuby、好きですか?僕は好きです。
皆さんはモナド、好きですか?僕は好きです。好きなものと好きなもの、どっちも使いたくなるのが人間の性。
どうにかしてRubyでモナドを使いたい!ということで、Rubyでモナドを使って、Rubyでよく書くありがちなコードをいい感じにしていきます。
モナドってなに?
モナドはHaskellなどの関数型言語で使われる概念です。
細かい定義は他の記事に任せますが、簡単にいってしまうと「書いたコード(文字通り)よりも外の世界から受ける影響に安全にアクセスする方法」です。こういうのをプログラミングでは「副作用」って言ったりします。
モナドについてはこことここの記事が個人的に勉強になりました。モナドが力を発揮する場面としてよくあげられるのが「IO」です。IOはまさに「自分が書いたコードの外から受ける影響」ですよね。
入力ではコードを実行するまでどんな値が入ってくるかわかりません。冷静に考えるとこれは結構怖いことだったりします。書いたコードは自分でいくらでもバグらないように修正できますが、外部からの影響には「備える」ことしかできません。
そんな時にモナドを使うと、「外界の影響」を箱に詰めてブラックボックス化しながら、コードとしてはやりたいことを素直に記述するだけでうまいコードを書くことができるようになります。ここまでの説明を読むだけでも、モナドはなんだかいいもののように感じませんか?(良いものです)
残念ながらRubyには組み込みの文法としてのモナドは存在しません。(今後も多分入らない)
しかし、型安全やバリデーションをシステムに提供してくれるライブラリ群、dry-rbにdry-monadsというgemがあります!
このgemを使ってモナドの力をRubyに加えてみます。実践
dry-monadsは関数型言語のモナドをRubyでも扱えるようにしたgemですが、正確にいうと関数型言語におけるモナドとは異なります。
詳しいことは省きますが、結論から言うとモナド則を満たさない記述ができてしまうからです(動的型付け言語だから仕方がないのかもしれない)
しかし、それでもモナドが提供してくれる恩恵を得ることはできます。言葉で書いてよくもわからないと思うので、早速手続き型プログラミングで書いた場合とモナドを使用して書いた場合のコードを載せます。
手続き型言語ではIO処理ではよくガード条件を使って外部からの影響に備えますね。
例えばRailsのActiveRecordとかを使っているとよくこんな感じのコードを書くんじゃないでしょうか?(ちょっと大げさに書いてます)user = User.find_by(id: user_id) address = Address.find_by(id: address_id) if user.present? && address.present? if user.update(address: address) ... else ... end else ... endこのように、手続き型で素朴に記述すると
User.find_by
が存在しているかどうか、取得したUser
と関連をもつArticle
が存在するかどうかを想定したコードになります。これに
dry-rb
を適用して書くとこうなります。require 'dry/monads' extend Dry::Monads[:maybe] result = Maybe(User.find_by(id: user_id)).bind do |user| Maybe(Address.find_by(id: address_id)).fmap do |address| user.update(address: address) end end.to_result if result.success? ... else ... endこのようにモナドを使うことで、
user
がなんの値になろうがarticle
がなんの値になろうが途中の処理は全てモナドが隠してくれます。これによってコードを書く上では最終結果に対してsuccess?
がtrue
になるかどうかだけを考えれば良くなります。(to_result
メソッドを用いてResult
モナドに変換しています)思考がスッキリするのを感じますね。
また、ruby2.7で使うことができるようになるパターンマッチを用いると、↓のようにResultへのキャストをする必要もなく記述できるようになりますrequire 'dry/monads' extend Dry::Monads[:maybe] result = Maybe(User.find_by(id: user_id)).bind do |user| Maybe(Address.find_by(id: address_id)).fmap do |address| user.update(address: address) end end case result in Some(_) ... in None() ... endこの例ではオプショナル型に近いような
Maybe
モナドを使いましたが、そのほかにもモナドはたくさんあるのでそれぞれの使い方を紹介しますMaybe
例でも用いたMaybeは、
nil
を受け取った際にはNone()
を返し、それ以外の時はSome(value)
を返します。
これを用いることで最終結果がSome
orNone
になるため、最後にその検証だけをすればよくなります。
https://dry-rb.org/gems/dry-monads/1.0/maybe/
コード再掲require 'dry/monads' extend Dry::Monads[:maybe] result = Maybe(User.find_by(id: user_id)).bind do |user| Maybe(Address.find_by(id: address_id)).fmap do |address| user.update(address: address) end end case result in Some(_) ... in None() ... endResult
ResultはMaybeに似たようなモナドです。Maybeが
nil
をNone
に変換してくれたのに対し、Resultは自分でSuccess
とFailure
の定義をする必要があります。
https://dry-rb.org/gems/dry-monads/1.0/result/require 'dry/monads' extend Dry::Monads[:result] result = find_user(user_id).bind do |user| find_address(address).fmap do |address| user.update(address: address) end end case result in Success(_) ... in Failure() ... end def find_user(user_id) user = User.find_by(id: user_id) user.present? ? Success(user) : Failure() end def find_address(address_id) address = Address.find_by(id: address_id) address.present? ? Success(address) : Failure() endTry
Tryはエラーハンドリングに使うことができるモナドです。
これを用いることで、エラー処理をcall
内に閉じ込めることができます。
https://dry-rb.org/gems/dry-monads/1.0/try/require 'dry/monads' class UpdateUser include Dry::Monads[:try] attr_reader :user_id, :address_id def initialize(user_id, address_id) @user_id = user_id @address_id = address_id end def call Try { User.find(user_id) }.bind do |user| Try { Address.find(address_id) }.bind do |address| Try { user.update!(address: address) } end end end end result = UpdateUser.new(user_id, address_id).call case result in Try::Value(_) ... in Try::Error(_) ... endList
Listは配列に対するイテレーションに使うことができるモナドです。
マップ処理などをモナドの世界に閉じ込めることができます。
https://dry-rb.org/gems/dry-monads/1.0/list/require 'dry/monads/list' M = Dry::Monads M::List[1, 2].bind { |x| [x - 1] } # => List[0, 1] M::List[1, 2].bind(-> (x) { [x, x * 2] }) # => List[1, 2, 2, 4] M::List[1, nil].bind { |x| [x - 1] } # => raise ErrorTask
Taskは非同期処理に対して使うことができるモナドです。
これを用いることで、JSのasync
、await
のような処理を行うことができるようになります。
https://dry-rb.org/gems/dry-monads/1.0/task/require 'dry/monads' class AsyncTask include Dry::Monads[:task] def call task1 = Task { fetch_task1 } task2 = Task { fetch_task2 } task1.bind { |t1| task2.fmap { |t2| [t1, t2] } } end def fetch_task1 sleep 3 'task1' end def fetch_task2 sleep 2 'task2' end end async_task = AsyncTask.new task = async_task.call task.fmap do |t1, t2| puts "Task: #{t1}" puts "Task: #{t2}" end sleep 10 puts 'done'まとめ
状況に応じてこれらのモナドを使うことで、やりたいことを素直に記述したコードがかけるようになります。
少しでもご興味を持っていただけたら幸いです。参考文献
- 投稿日:2019-12-14T00:02:35+09:00
[Ruby on Rails5 アプリケーション プログラミング]学習ログ#3[2019/12/13]
前回 -> [Ruby on Rails5 アプリケーション プログラミング]学習ログ#2[2019/12/12]
次回 -> [[Ruby on Rails5 アプリケーション プログラミング]学習ログ#4[2019/12/14]]
目的
Progateで学んだことをローカル環境で手を動かして復習することで、基礎を固める
使用する教材
山田祥寛(2017). RubyonRails5 アプリケーションプログラミング 株式会社技術評論社
Q.「RubyonRails5 アプリケーションプログラミング」とは?
A.ProgateのRoRコースを終えた後にするRoRの勉強としておすすめされている本
出典
(Samurai Blog / Ruby on Rails学習本おすすめ6選【入門者〜上級者までレベル別に解説】)[https://www.sejuku.net/blog/110292]
今日したこと
ビュー開発の基礎(p104~p136)
(復習)コントローラ/ビュー/ルート定義 を作成
console#viewという名前のコントローラクラスを作成 rails g controller view #view以外の名前のコントローラクラスhogeを間違って作成した場合の切り戻し rails destroy controller hoge #ビューの作成 [view_controller] , [books_controller] を編集 #ルート定義 [routes.rb] get 'view/keyword' post 'keyword/search'フォーム関連のビューヘルパーの作成
console#viewディレクトリに移動 cd app/views/view #テンプレートファイルの作成 vi hogehoge.html.erb