20220108のRubyに関する記事は11件です。

【Ruby】標準入力の値取得

 はじめに こちらの記事は、Rubyをある程度学び、下記を考えている方向けの記事となります。 転職を見据えてPaizaのスキルチェックでランクを上げたいと考えている方 競技プログラミングに興味のある方 標準入力 【 standard input(stdin)】 とは IT用語辞典e-Wordsによりますと、 標準入力とは、コンピュータ上で実行されているプログラムが、特に何も指定されていない場合に標準的に利用するデータ入力元。コンピュータの入力装置やOSが提供するデータ入力機能・経路などを指し、多くのシステムではキーボード装置による利用者の文字入力が標準入力に設定されている。 初心者の方には下記のPaizaFAQの説明が簡単でわかりやすいです。 標準入力はプログラムに値を渡す入力元のこと、標準出力はプログラムから出力される値の出力先のことをいいます。標準入力の仕組みを使うと、キーボードやファイルなど外部から値(データ)をプログラムに与えることができます。 もっと詳しく知りたい方はこの記事の説明が理解しやすいと思います。 cpコマンドの動きを例に入力/出力、標準という点がわかりやすく説明されています。 標準入力・標準出力ってなに? こちらはマンガで視覚的に学べます。 「標準入力」 ~マンガでプログラミング用語解説 1行に1つの要素だけの場合 文字列 標準入力 apple 値取得 str = gets.chomp 出力結果 p str => "apple" 整数 標準入力 123 値取得 # to_iメソッドが改行も含め整数に変換するので、chompメソッドは不要 int = gets.to_i 出力結果 p int => 123 1行に複数の要素がある場合 文字列 標準入力 apple banana cherry 値取得 # 引数を省略した場合空白文字(半角スペース、改行、タブ)で区切るのでchompメソッド不要 # split(' ')でも同じ結果になる str = gets.split 出力結果 p str # 配列として格納される => ["apple", "banana", "cherry"] 値をそれぞれ変数に格納したい場合(多重代入) 値取得/出力結果 str_1, str_2, str_3 = gets.split p str_1 => "apple" p str_2 => "banana" p str_3 => "cherry" 整数 標準入力 1 2 3 値取得 int = gets.split.map(&:to_i) 出力結果 p int => [1, 2, 3] 値をそれぞれ変数に格納したい場合(多重代入) ※ Paiza等で良く使う 値取得/出力結果 int_1, int_2, int_3 = gets.split.map(&:to_i) p int_1 => 1 p int_2 => 2 p int_3 => 3 複数行に1つずつの要素だけの場合 文字列 標準入力 apple banana cherry 値取得 str = readlines.map(&:chomp) 出力結果 p str # 配列として格納される => ["apple", "banana", "cherry"] 整数 標準入力 1 2 3 値取得 int = readlines.map(&:to_i) 出力結果 p int # 配列として格納される => [1, 2, 3] 複数行に複数の要素がある場合 文字列 標準入力 apple banana cherry durian grape peach 値取得 str = readlines.map { |s| s.split } 出力結果 p str # 多次元配列として格納される => [["apple", "banana", "cherry"], ["durian", "grape", "peach"]] 多次元配列を一次元にしたい場合 arr = str.flatten p arr => ["apple", "banana", "cherry", "durian", "grape", "peach"] 整数 標準入力 1 2 3 4 5 6 値取得 int = readlines.map { |i| i.split.map(&:to_i) } 出力結果 # 多次元配列として格納される p int => [[1, 2, 3], [4, 5, 6]] 多次元配列を一次元にしたい場合 arr = int.flatten p arr => [1, 2, 3, 4, 5, 6]  まとめ RubyやRailsを勉強した後、Paizaのスキルチェックに挑戦した際に、標準入力の値取得で大コケしたのをきっかけにこの記事を書きました。 この記事を書くにあたって、splitメソッドや、to_iメソッドがどこまでを対象に処理するのかということが理解でき大変勉強になりました。 今まで値取得時にはchompメソッドが必須だと思っていましたが、他のメソッドが同じ処理をしてくれている場合は省略し、すっきりしたコードを書くことができますね。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

処理に名前をつけて使おう① introduceメソッド

※初心者向け ※アウトプット練習の為 開発環境 rails 6.1.4.1 ruby 2.6.5 問題内容1 introduceメソッドを作成してください。 メソッドの処理 私の名前はRubyです。とターミナルに出力する。 解答1 def introduce puts "私の名前はRubyです。" end 解説1 メソッドを作成する場合には、以下のようにプログラムを書きます。 def メソッド名 # メソッドの処理 end 今回はメソッド名をintroduceにするため、メソッドの定義は以下のようになります。 def introduce # メソッドの処理 end メソッドの処理はdef メソッド名とendの中に記述します。今回はターミナルに文字を出力するのでputsメソッドを使います。 def introduce puts "私の名前はRubyです。" end なお、問題の条件はメソッドを作成するのみなので、メソッドを呼び出す記述は不要です。もし自身のターミナルで挙動を確認したい場合は以下のように記述し、実行します。 def introduce puts "私の名前はRubyです。" end # メソッドの呼び出し introduce 問題内容2 doubleメソッドを作成してください。 メソッドの処理 処理1:ターミナルから数値を入力させる 処理2:入力した数値を2倍にした値を戻り値にする 解答2 def double num = gets.to_i num * 2 end もしくはreturnを使って def double num = gets.to_i return num * 2 end 解説2 まず、メソッドの定義をします。今回はメソッド名がdoubleなので定義は以下のようになります。 def double # メソッドの処理 end 次にメソッドの処理を考えます。今回のメソッドの処理は大きく分けて2つあります。 処理1:ターミナルから数値を入力させる 処理2:入力した数値を2倍にした値を戻り値にする 処理1:ターミナルから数値を入力させる まずターミナルから数値を入力させます。ターミナルに文字を入力するときはgetsメソッドを使います。 def double num = gets end getsメソッドで入力した文字は全て文字列となります。そこで文字列を数値に変換するto_iメソッドを使って、getsメソッドで入力した文字を数値に変換します。 def double num = gets.to_i end 処理2:入力した数値を2倍にした値を戻り値にする 戻り値というのは、メソッドを呼び出したあとに取得される値です。メソッドでは、以下のどちらかの値が戻り値となります。 ①メソッドの処理の最後に書かれた値 ②return文を使って指定された値 まず①のようにメソッドの最後の値が基本的に戻り値になります。よって以下のようにnum * 2をメソッドの最後に記載すれば、numを2倍した値が戻り値になります。 def double num = gets.to_i num * 2 end もしくはreturn文を使っても良いです。return文はreturn 値とすれば、たとえメソッドの最後でなくても、その値を戻り値にできます。 def double num = gets.to_i return num * 2 end Rubyではreturn文を使わずに、単に戻り値をメソッドの最後に記載することが一般的です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】ファイル投稿ボタンをカスタマイズ(備忘録)

ファイルを投稿するボタンをカスタマイズする方法を残しておきます。 元の記述 new.html.erb #省略 <div class="field"> <%= f.label :画像 %> <%= f.file_field :image %> </div> #省略 変更後 new.html.erb #省略 <div class="label-box__inner"> <label class="mealpicture">料理画像 <%= f.file_field :image ,class:"input-default" %> <div class="boxFileSelect"></div> </label> </div> #省略 SCSS .label-box__inner{ padding: 10px 20px 10px 20px; .input-default{ display: none; width: 100%; height: 40px; margin: auto; padding: 10px 16px 8px; border-radius:4px; background: #fff; line-height: 1.5; cursor: pointer; outline-width: 2px; } } 以上です。 お好みに合わせてCSSの値をいじったり、ラベルの文字をいじってください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rails 6のアプリをgit clone後に遭遇したエラー

rails6 git clone後の手順 やりたいこと・背景 実装途中のrails6のアプリをgit cloneしたい 間違えて、ローカルのリポジトリを消してしまった際にそんなこと滅多にないgithubにpushしていればなんとかなる 昔作っていたrails6のアプリ実装を再開したい などなど ざっくり手順 1. Githubでgit cloneしたいリポジトリをコピー terminal. $ git clone クローンしたいリポジトリのurl 2. rm -rf Gemfile.lockでgemのバージョン依存関係をなくす git cloneしてきたrailsのアプリは、gemのバージョンの依存関係が固定されてしまっているため。 そのまま、bundle installするとエラーが出る。 cd アプリ名 で、railsアプリのディレクトリに移動した後 terminal. $ rm -rf Gemfile.lock 私は、bundlerでアプリごとに環境指定しているので、bundle install --path vendor/bundleとしていますが、bundle installでも問題ないです。 terminal. $ bundle install --path vendor/bundle terminal. $ rails db:migrate エラーです少数の方は出ます terminal. /Users/○○/○○/○○/○○/○○/vendor/bundle/ruby/2.6.0/gems/ffi-1.15.4/lib/ffi/library.rb:145:in `block in ffi_lib': Could not open library '/Users/○○/○○/○○/○○/○○/vendor/bundle/ruby/2.6.0/gems/sassc-2.4.0/ext/libsass.bundle': dlopen(/Users/○○/○○/○○/○○/○○/vendor/bundle/ruby/2.6.0/gems/sassc-2.4.0/ext/libsass.bundle, 0x0005): tried: '/Users/○○/○○/○○/○○/○○/bundle/ruby/2.6.0/gems/sassc-2.4.0/ext/libsass.bundle' (no such file), '/usr/local/lib/libsass.bundle' (no such file), '/usr/lib/libsass.bundle' (no such file) (LoadError) from /Users/○○/○○/○○/○○/○○/vendor/bundle/ruby/2.6.0/gems/ffi-1.15.4/lib/ffi/library.rb:99:in `map' from /Users/○○/○○/○○/○○/○○/vendor/bundle/ruby/2.6.0/gems/ffi-1.15.4/lib/ffi/library.rb:99:in `ffi_lib' from /Users/○○/○○/○○/○○/○○/vendor/bundle/ruby/2.6.0/gems/sassc-2.4.0/lib/sassc/native.rb:13:in `rescue in <module:Native>' from /Users/○○/○○/○○/○○/○○/vendor/bundle/ruby/2.6.0/gems/sassc-2.4.0/lib/sassc/native.rb:10:in `<module:Native>' from /Users/○○/○○/○○/○○/○○/vendor/bundle/ruby/2.6.0/gems/sassc-2.4.0/lib/sassc/native.rb:6:in `<module:SassC>' from /Users/○○/○○/○○/○○/○○/vendor/bundle/ruby/2.6.0/gems/sassc-2.4.0/lib/sassc/native.rb:5:in `<main>' from /Users/○○/○○/○○/○○/○○/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.9.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `require' ... 省略 ... rails db:migrate後に上記のようなエラーが出た方は、以下のように対応してください。 Gemfile. gem "sassc", "< 2.2.0" 追記 優秀な先輩エンジニアが教えてくれていました Also fails for me on CentOS 7 + Ruby 2.5. However unlike what was reported by OP, Jekyll 4.0 doesn't seem to require sassc 2.2.0. Just adding gem "sassc", "< 2.2.0" to my Gemfile worked around the bug successfully. When installing the extension from 2.1.0, libsass.so is at ~/.gem/ruby/2.5.0/gems/sassc-2.1.0-x86_64-linux/lib/sassc/libsass.so. When building it from 2.2.0, it is at ~/.gem/ruby/2.5.0/gems/sassc-2.2.0/ext/libsass.so. 再度 terminal. $ bundle install エラー terminal. You have requested: sassc < 2.2.0 The bundle currently has sassc locked at 2.4.0. Try running `bundle update sassc` If you are updating multiple gems in your Gemfile at once, try passing them all to `bundle update` 素直にbundle update sassc terminal. $ bundle update sassc 以下のような出来たメッセージ出たらok terminal. ... Installing sassc 2.1.0 (was 2.4.0) with native extensions Using sassc-rails 2.1.2 Using sass-rails 6.0.0 Note: sassc version regressed from 2.4.0 to 2.1.0 Bundle updated! 3. rails db:migrate terminal. $ rails db:migrate 4. rails sで立ち上げよう terminal. $ rails s 5. Webpacker::Manifest::MissingEntryError in Homes#topエラー terminal. ActionView::Template::Error (Webpacker can't find application.js in /Users/yade/pg/dmm/review/meshiterro/public/packs/manifest.json. Possible causes: 1. You want to set webpacker.yml value of compile to true for your environment unless you are using the `webpack -w` or the webpack-dev-server. 2. webpack has not yet re-run to reflect updates. 3. You have misconfigured Webpacker's config/webpacker.yml file. 4. Your webpack configuration is not creating a manifest. Your manifest contains: { } ): 7: <%= csp_meta_tag %> 8: 9: <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> 10: <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %> 11: </head> 12: 13: <body> app/views/layouts/application.html.erb:10 これはrails6で必要なwebpackerなどがないので以下実行 詳しくは、先輩エンジニアの方を参考にしてください。 terminal. $ rails webpacker:install $ rails webpacker:compile terminal. $ rails s 終わりです。 最後まで読んでいただきありがとうございました。独学でやっているので、情報不足や分かりづらい点がございましたら、コメントしていただければ幸いです。 少しでもお役に立てたら、幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

bcryptとbcrypt-rubyについて調べてみた

先日『暗号技術入門』を読みました。この過程で、アプリケーション開発で使用機会の多いbcryptについて気になり、調べてみました。 本記事では、bcryptを仕事で使う際に知っておいたほうが良さそうなことをまとめます。 環境 bcryptの挙動を説明するためにbcrypt-rubyを利用しています。 記事執筆時点で最新のv3.1.16を対象としています。 bcryptとは bcryptは一方向ハッシュ関数のアルゴリズムの1つであり、(パスワードなどの)データからハッシュ値を算出します。 bcryptの特徴として、ハッシュ値から元のデータを逆算されづらくするために、ソルトとコストがあります。 一方向ハッシュ関数とソルト、コストについては後述します。 ちなみに、「パスワードをハッシュ化する」という表現をよく見かけますが、これは「ハッシュ関数を用いてパスワードからハッシュ値を得る」と同義です。また、ハッシュ値は「ダイジェスト」と表現することもあります。Rails Tutorialでは、この表現でしたね。 余談が長くなりましたが、bcrypt-rubyでは、このようにハッシュ値を計算できます。 BCrypt::Password.create('password') # => "$2a$12$0jExrhEvd3zt6Me6OOj7xu7lysiIvPgixN1zExHgN6It.W4d4y/7C" 一方向ハッシュ関数とは 一方向ハッシュ関数とは、データからハッシュ値を計算することは出来るものの、ハッシュ値からデータに逆算ことはできない関数です。この逆算できない性質を、一方向性と呼びます。 ハッシュ値からデータに逆算ことができないため、パスワードが正しいかを判定するには、ハッシュ値同士で比較します。 hash = BCrypt::Password.create('password') # => "$2a$12$smCjRIHIJt4yL1ZGPx2Xg.O0zVnDVXuR05Fz54T0bYlaEIlgor15K" hash == 'password' # => true 上記では、文字列同士を比較しているように見えますが、これはStringクラスを継承したBCrypt::Passwordクラスが、==をオーバーライドしているためです。実装は、引数のsecretからハッシュ値を計算し、親クラスのString#==を呼び出しています。 # https://github.com/bcrypt-ruby/bcrypt-ruby/blob/master/lib/bcrypt/password.rb module BCrypt # something... class Password < String def ==(secret) super(BCrypt::Engine.hash_secret(secret, @salt)) end end end レインボーテーブル攻撃とは ソルトについて説明する前に、ソルトの理解に欠かせないレインボーテーブル攻撃について説明します。 レインボーテーブル攻撃は、事前に、膨大な数のパスワード候補の平文とそのハッシュ値をデータベースに保存しておき、ハッシュ値からパスワードの平分を取得する攻撃のことです。 ソルトとは ソルトは、乱数であり、レインボーテーブル攻撃の対策のために必要です。 bcryptはソルトと平文を入力に取り、ハッシュ値を算出します。 ソルトを加えたデータのハッシュ値を計算することにより、このハッシュ値からデータを逆算するためには、ソルトのビット長だけ必要なデータ量は増えることとなり、レインボーテーブル攻撃に有効となるわけです。 bcryptでは、ハッシュ値の中にソルトが含まれていることを確認できます。 hash = BCrypt::Password.create("password") => "$2a$12$9Plx9x2SgweSioLq9UInwe6mrIuGzi0QzoZyVP46WDo45ata5ITLi" pry(main)> hash.salt => "$2a$12$9Plx9x2SgweSioLq9UInwe" bcrypt-rubyでは、Bcrypt::Passwordクラスにsaltメソッドが定義されています。 saltはソルトに加えて、バージョンとコストを含めて返します。 (上記の場合は、バージョンが、2a、コストが12、ソルトがUPUKgRo8aH/NfJ87TwJMJe) ストレッチングとコストとは 次に、ストレッチングについて説明します。 ストレッチングは、ハッシュ値への計算を繰り返し行うことです。ハッシュ関数によって出力されたハッシュ値を再度ハッシュ関数の入力にしてハッシュ値を得る、というサイクルを繰り返すわけです。 ストレッチングによって、ハッシュ値から平文を逆算するのに必要な計算時間が増え、ハッシュ値からデータを逆算されずらくなります。 このストレッチングの回数をコストと呼びます。コストを増やすほどハッシュ化が遅くなりパフォーマンスは悪くなりますが、ハッシュ値から逆算される時間も遅くなります。 bcrypt-rubyのデフォルトのコストは12です。 BCrypt::Engine.cost # => 12 参考 https://github.com/bcrypt-ruby/bcrypt-ruby Hashing in Action: Understanding bcrypt 『暗号技術入門 第3版』
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】ポートフォリオ制作「バイク専用SNS」KoKo-iiYo

【Rails】ポートフォリオ制作「バイク専用SNS」KoKo-iiYo はじめに DMM WEB CAMP通学中、3ヶ月目に制作したポートフォリオについて、サイト概要・実装機能などまとめます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsのモデル同士の関連付けについて

概要 Ruby on Railsにおけるモデル同士の関連付けについてアウトプットしたいと思います。 環境 ruby: '3.0.1' rails: '6.1.4' 前提 関連付けの説明にあたって下記モデルを前提とします。  モデル   User  id 1 name 'example_name' モデル Post id 1 content 'example_content' user_id 1 モデル Account id 1 account_number 1234 user_id 1 関連付けとは 関連付けとは異なるモデル同士をつなげてコードを簡素化するために行うものです。 関連付けを行っていない場合と行っている場合のコードの違いを見てみましょう。 userの投稿一覧を取得するというコードを比較します。 関連付けを行っていない場合 user = User.find(1) posts = Post.find_by(user_id: user.id) 関連付けを行っている場合 user = User.find(1) posts = user.posts 関連付けの目的 関連付けの目的は主に2点あります。 1. コードの簡素化 上記の例を見ると分かる通り投稿を作成する際にuser_idを気にしなくて良いので記載するコード量が少なくなりますし、 コードも直感的でわかりやすくなります。 2. コード記載ミスを減らすこと 関連付けを行っていない場合では、上記の例だと投稿を作成する際にuser_idを気にかけないといけなくなります。 つまり投稿を作成するときは必ずuser_idの記載が必要になるので、必然的にコード量が増え、記載ミス等が起きやすくなります。 関連付けの種類 関連付けの種類は3つあります。 1. belongs_to 使い方 models/post.rb class Post < ApplicationRecord belongs_to :user # 関連名は必ず単数形 end この関連付けはUserモデルと従属の関係を作ります。 つまり、投稿には必ずユーザーが必要になるということです。 ユーザーのいない投稿は作成できません。 よくhas_manyを定義したモデルの相方で定義されます。 また下記のように投稿したユーザーを取得できるようにもなります。 post = Post.find(1) post.user #=> 投稿したユーザー情報が返ってきます。 2. has_many 使い方 models/user.rb class User < ApplicationRecord has_many :posts # 関連名は必ず複数形 end この関連付けはPostモデルと1対多の関係を作ります。 つまり一人のユーザーに対して投稿が複数存在するということです。 よくbelongs_toを定義したモデルの相方で定義されます。 これによりユーザーの投稿一覧を取得できるようになります。 user = User.find(1) user.posts #=> ユーザーの投稿一覧が返ってくる またuser_idを気になくても下記のように投稿を作成することもできます。 user = User.find(1) post = user.posts.create(content: 'new_content') # 作成の際にuserから自動的にuser_idを取得してくれる 3. has_one 使い方 models/user.rb class User < ApplicationRecord has_one :account # 関連名は必ず単数形 end 関連する2つのモデルでそれぞれ定義する。 この関連付けはUserモデルとAccountモデルで1対1の関係を作ります。 よくbelongs_toを定義したモデルの相方で定義されます。 つまりユーザーは必ず1つのアカウントIDを持っているということになります。 下記のようにユーザーのアカウントを取得することもできるようになります。 user = User.find(1) user.account #=> userのアカウント情報を取得できる まとめ その他にも関連付けの際に使えるオプションもあるので別記事にて記載したので下記をご確認ください。  参考文献 Railsガイド 【Rails】 アソシエーションを図解形式で徹底的に理解しよう!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Carrierwaveで公開されているファイルURLからデータを保存する

やりたいこと 既に公開されている画像のURLからCarrierwaveを介してファイルを保存する。 ※ 試したのは画像だけですが他のファイルでもできそう 結論 # CarrierWave の Uploader class AvatarUploader < CarrierWave::Uploader::Base storage :file end # ActiveRecord class User < ApplicationRecord mount_uploader :avatar, AvatarUploader end User.create!(remote_avatar_url: "https://example.com/***.png") remote_**_url に値を指定することによって解決しました。 よくよくみるとgithubのreadmeにもformの例として remote_**_url に値をセットする方法が紹介されてました。 ちゃんとおってないですが多分ここら辺のActiveRecordへのパッチでこのメソッドが早されているのかなという感じでした。 ダメなパターン User.create!(avatar: URI.open("https://example.com/***.png")) 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rails new 時に "ActiveSupport::EncryptedFile::InvalidKeyLengthError" エラーが出た時の対応

タイミングは rails new に限りませんが、rails でコマンドを叩いた時に以下のようにActiveSupport::EncryptedFile::InvalidKeyLengthError が出ることがあったので対処方法を書きます。 $ rails new xxxxx /Users/yyyyyy/.rbenv/versions/3.0.3/lib/ruby/gems/3.0.0/gems/activesupport-7.0.1/lib/active_support/encrypted_file.rb:114:in `check_key_length': Encryption key must be exactly 32 characters. (ActiveSupport::EncryptedFile::InvalidKeyLengthError) # 環境情報 $ ruby -v ruby 3.0.3p157 (2021-11-24 revision 3fb7d2cadc) [arm64-darwin21] $ rails -v Rails 7.0.1 原因 環境変数に設定していた RAILS_MASTER_KEY が適切な文字数 (=32) ではなかったことが原因でした。 $ export -p | grep RAILS_MASTER_KEY declare -x RAILS_MASTER_KEY="hogehoge" ? ここ 背景 検証中、ローカル端末の ~/.bash_profile 内で設定している環境変数のうち RAILS_MASTER_KEY に一時的に適当な値を設定していました。 (Rails 5.2 以上で実装された credentials.yml と master.key の仕組みで使う環境変数) しかし RAILS_MASTER_KEY は 32 文字であるという制限事項が設けられているため、適当な値をセットしていると弾かれる仕組みのようです。 対策 検証で一時的に適当な値をセットする際は、RAILS_MASTER_KEY には 32 文字の文字列をセットしましょう。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby 2.5.0コンテナでRails6の開発環境を作るときのトラブルシューティング

こちらのハンズオンをやっていたところ、いくつかの問題に遭遇したので備忘録。 Rails アプリケーションをコンテナで開発しよう ! 第 1 回 - まずは Rails アプリケーション作りから Rails7のインストールに失敗する 記事では Ruby2.5 & Rails6 を使用しているが、現時点(2022/1)での最新版は 7.0.1。 エラーメッセージにもあるように7.0.1 ではRuby2.7以上が必要であるため gem install railsに失敗する。 root@810c33297af8:/work# gem install rails Fetching: concurrent-ruby-1.1.9.gem (100%) Successfully installed concurrent-ruby-1.1.9 Fetching: i18n-1.8.11.gem (100%) Successfully installed i18n-1.8.11 Fetching: tzinfo-2.0.4.gem (100%) Successfully installed tzinfo-2.0.4 Fetching: activesupport-7.0.1.gem (100%) ERROR: Error installing rails: There are no versions of activesupport (= 7.0.1) compatible with your Ruby & RubyGems. Maybe try installing an older version of the gem you're looking for? activesupport requires Ruby version >= 2.7.0. The current ruby version is 2.5.0. gem install のオプションでRailsのバージョンを指定してインストールすることとした。 root@810c33297af8:/work# gem install -v 6.0.3.2 rails Nokogiriのインストールに失敗する さて、Railのバージョン指定をして改めてgem installを実施したところ、 root@810c33297af8:/work# gem install -v 6.0.3.2 rails ... Fetching: activesupport-6.0.3.2.gem (100%) Successfully installed activesupport-6.0.3.2 ERROR: While executing gem ... (Gem::RemoteFetcher::FetchError) bad response Forbidden 403 (https://api.rubygems.org/quick/Marshal.4.8/nokogiri-1.13.0-x64-unknown.gemspec.rz) Nokogiri 1.13.0のインストールに失敗している。 403なのでライブラリが不足している的なやつではないみたい。 rubygems.org でNokogiriの過去バージョンを調べて 1.12.5 を予めインストールすることで解決。 root@810c33297af8:/work# gem install nokogiri -v 1.12.5 Fetching: nokogiri-1.12.5-x86_64-linux.gem (100%) Successfully installed nokogiri-1.12.5-x86_64-linux 1 gem installed root@810c33297af8:/work# gem install -v 6.0.3.2 rails Fetching: loofah-2.13.0.gem (100%) Successfully installed loofah-2.13.0 Fetching: rails-html-sanitizer-1.4.2.gem (100%) Successfully installed rails-html-sanitizer-1.4.2 Fetching: rails-dom-testing-2.0.3.gem (100%) Successfully installed rails-dom-testing-2.0.3 Fetching: builder-3.2.4.gem (100%) Successfully installed builder-3.2.4 Fetching: erubi-1.10.0.gem (100%) Successfully installed erubi-1.10.0 Fetching: actionview-6.0.3.2.gem (100%) Successfully installed actionview-6.0.3.2 Fetching: actionpack-6.0.3.2.gem (100%) Successfully installed actionpack-6.0.3.2 Fetching: activemodel-6.0.3.2.gem (100%) Successfully installed activemodel-6.0.3.2 Fetching: activerecord-6.0.3.2.gem (100%) Successfully installed activerecord-6.0.3.2 Fetching: globalid-1.0.0.gem (100%) Successfully installed globalid-1.0.0 Fetching: activejob-6.0.3.2.gem (100%) Successfully installed activejob-6.0.3.2 Fetching: mini_mime-1.1.2.gem (100%) Successfully installed mini_mime-1.1.2 Fetching: mail-2.7.1.gem (100%) Successfully installed mail-2.7.1 Fetching: actionmailer-6.0.3.2.gem (100%) Successfully installed actionmailer-6.0.3.2 Fetching: nio4r-2.5.8.gem (100%) Building native extensions. This could take a while... Successfully installed nio4r-2.5.8 Fetching: websocket-extensions-0.1.5.gem (100%) Successfully installed websocket-extensions-0.1.5 Fetching: websocket-driver-0.7.5.gem (100%) Building native extensions. This could take a while... Successfully installed websocket-driver-0.7.5 Fetching: actioncable-6.0.3.2.gem (100%) Successfully installed actioncable-6.0.3.2 Fetching: mimemagic-0.3.10.gem (100%) Building native extensions. This could take a while... Successfully installed mimemagic-0.3.10 Fetching: marcel-0.3.3.gem (100%) Successfully installed marcel-0.3.3 Fetching: activestorage-6.0.3.2.gem (100%) Successfully installed activestorage-6.0.3.2 Fetching: actionmailbox-6.0.3.2.gem (100%) Successfully installed actionmailbox-6.0.3.2 Fetching: actiontext-6.0.3.2.gem (100%) Successfully installed actiontext-6.0.3.2 Fetching: thor-1.2.1.gem (100%) Successfully installed thor-1.2.1 Fetching: method_source-1.0.0.gem (100%) Successfully installed method_source-1.0.0 Fetching: railties-6.0.3.2.gem (100%) Successfully installed railties-6.0.3.2 Fetching: sprockets-4.0.2.gem (100%) Successfully installed sprockets-4.0.2 Fetching: sprockets-rails-3.4.2.gem (100%) Successfully installed sprockets-rails-3.4.2 Fetching: rails-6.0.3.2.gem (100%) Successfully installed rails-6.0.3.2 29 gems installed
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails 6】Formオブジェクトでシンプルな検索機能を作ってみる

概要 記事タイトル通り、Form オブジェクトを使用したシンプルな検索機能を Rails6 で作ってみます。 Form オブジェクトの概念については、以下の記事の表現が非常にわかりやすかったので参考にされてください。 Formオブジェクトはフォームの責務をカプセル化し、コントローラやビューを疎結合に保つために必要なデザインパターンです。 ユーザの入力の整形や永続化をコントローラだけで行うと、コントローラが肥大化してしまいます。 この原因はコントローラがモデル層の知識をもちすぎるためにあります。 このときビューもフォームを表示するための知識をもつことになるため、コントローラと同じような問題が起こってしまいます。 このことは単一責任の原則に反し、モデル層の変更がコントローラやビューに影響を及ぼすことになります。 逆にActiveRecordモデルにこういった責務をもたせると、今度はActiveRecordモデルがフォームの知識を持ちすぎてしまいます。 フォームという独立した責務があるのであれば、これをひとつのクラスにカプセル化する、というのがFormオブジェクトの役割です。 参照: Railsのデザインパターン: Formオブジェクト 完成イメージ 名前、性別、年齢、都道府県といった複数の条件でユーザーを絞り込んでいく事が可能です。 仕様 Ruby 3 Rails 6 MySQL 5.7 Bootstrap 5 Docker ※ Bootstrap はバージョン5から JQuery が不要になり導入方法が少し変わったのでご注意ください。 下準備 まず最初に下準備から始めていきます。 各種ディレクトリ & ファイルを作成 $ mkdir rails-search-form && cd rails-search-form $ touch Dockerfile docker-compose.yml entrypoint.sh Gemfile Gemfile.lock ./Dockerfile FROM ruby:3.0 RUN curl https://deb.nodesource.com/setup_14.x | bash RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs yarn ENV APP_PATH /myapp RUN mkdir $APP_PATH WORKDIR $APP_PATH COPY Gemfile $APP_PATH/Gemfile COPY Gemfile.lock $APP_PATH/Gemfile.lock RUN bundle install COPY . $APP_PATH COPY entrypoint.sh /usr/bin/ RUN chmod +x /usr/bin/entrypoint.sh ENTRYPOINT ["entrypoint.sh"] EXPOSE 3000 CMD ["rails", "server", "-b", "0.0.0.0"] ./docker-compose.yml version: "3" services: db: image: mysql:5.7 environment: MYSQL_ROOT_PASSWORD: password volumes: - mysql-data:/var/lib/mysql - /tmp/dockerdir:/etc/mysql/conf.d/ ports: - 4306:3306 web: build: context: . dockerfile: Dockerfile command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" volumes: - .:/myapp - ./vendor/bundle:/myapp/vendor/bundle environment: TZ: Asia/Tokyo RAILS_ENV: development ports: - "3000:3000" depends_on: - db volumes: mysql-data: ./entrypoint.sh #!/bin/bash set -e # Remove a potentially pre-existing server.pid for Rails. rm -f /myapp/tmp/pids/server.pid # Then exec the container's main process (what's set as CMD in the Dockerfile). exec "$@" ./Gemfile # frozen_string_literal: true source "https://rubygems.org" git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } gem "rails", "~> 6" ./Gemfile.lock # 空欄でOK rails new おなじみのコマンドでアプリの雛型を作成。 $ docker-compose run web rails new . --force --no-deps -d mysql --skip-test database.ymlを編集 デフォルトの状態だとデータベースとの接続ができないので「database.yml」の一部を書き換えます。 ./config/database.yml default: &default adapter: mysql2 encoding: utf8mb4 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root password: password host: db development: <<: *default database: myapp_development test: <<: *default database: myapp_test production: <<: *default database: <%= ENV["DATABASE_NAME"] %> username: <%= ENV["DATABASE_USERNAME"] %> password: <%= ENV["DATABASE_PASSWORD"] %> host: <%= ENV["DATABASE_HOST"] %> コンテナを起動 & データベースを作成 $ docker-compose build $ docker-compose up -d $ docker-compose run web bundle exec rails db:create 動作確認(localhost:3000 にアクセス) localhost:3000 にアクセスしてウェルカムページが表示されればOKです。 slim を導入 個人的にビューのテンプレートエンジンは erb よりも slim の方が好みなので変更します。 ./Gemfile gem "slim-rails" gem "html2slim" $ docker-compose build 既存のビューファイルを slim に書き換え。 $ docker-compose run web bundle exec erb2slim app/views app/views $ docker-compose run web bundle exec erb2slim app/views app/views -d Bootstrap を導入 見た目を整えるために Bootstrap を使用します。 ※ 下記の手順はバージョン5を想定したものになるので、別のバージョンを使いたい場合は他の記事を参考に導入してください。 yarn install 必要なライブラリをインストール。 $ docker-compose run web yarn add bootstrap @popperjs/core app/views/layouts/application.html ./app/views/layouts/application.html - = stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' + = stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' 「app/views/layouts/application.html」内の9行目あたりに記述されている「stylesheet_link_tag」を「stylesheet_pack_tag」に書き換えます。 app/javascript/stylesheets/application.scss $ mkdir app/javascript/stylesheets $ touch app/javascript/stylesheets/application.scss 「app/javascript/stylesheets/」以下に「application.scss」を作成し、次の1行を追記してください。 ./app/javascript/stylesheets/application.scss @import "~bootstrap/scss/bootstrap"; app/javascript/packs/application.js 「app/javascript/packs/application.js」内に次の2行を追記。 ./app/javascript/packs/application.js import "bootstrap"; import "../stylesheets/application"; これで Bootstrap の設定は完了です。 本実装 準備ができたので、本格的な実装に入りましょう。 User モデルを作成 今回は簡単な User モデルを例に検索機能を実装していきたいと思います。 名前で検索(姓・名・フルネーム) 性別で検索 年齢で検索 都道府県で検索 $ docker-compose run web rails g model User first_name:string last_name:string gender:integer birthdate:date prefecture_id:integer $ docker-compose run web rails db:migrate ./app/models/user.rb class User < ApplicationRecord extend ActiveHash::Associations::ActiveRecordExtensions belongs_to_active_hash :prefecture validates :first_name, presence: true, length: { maximum: 10 } validates :last_name, presence: true, length: { maximum: 10 } validates :gender, presence: true validates :birthdate, presence: true validates :prefecture_id, presence: true enum gender: { male: 0, female: 1 } # 新しい順 scope :order_by_latest, -> { order(id: :desc) } # 古い順 scope :order_by_oldest, -> { order(id: :asc) } # フルネーム(姓 + 名) def full_name "#{last_name} #{first_name}" end # 年齢(生年月日から計算) def age return if birthdate.blank? date_format = "%Y%m%d" (Date.today.strftime(date_format).to_i - birthdate.strftime(date_format).to_i) / 10_000 end end サンプルデータを作成 ./Gemfile gem "active_hash" gem "faker" $ docker-compose build active_hash ハッシュ形式のデータをActiveRecordと同じ感覚で使用できるようになるgem。基本的に変更される事の無い「都道府県」のような情報を取り扱う時などに便利。 faker ランダムな値のダミーデータを作成する際に定番のgem。一部で日本語対応も。 $ docker-compose run web rails g model Prefecture --skip-migration ./app/models/prefecture.rb class Prefecture < ActiveHash::Base self.data = [ {id: 1, name: "北海道"}, {id: 2, name: "青森県"}, {id: 3, name: "岩手県"}, {id: 4, name: "宮城県"}, {id: 5, name: "秋田県"}, {id: 6, name: "山形県"}, {id: 7, name: "福島県"}, {id: 8, name: "茨城県"}, {id: 9, name: "栃木県"}, {id: 10, name: "群馬県"}, {id: 11, name: "埼玉県"}, {id: 12, name: "千葉県"}, {id: 13, name: "東京都"}, {id: 14, name: "神奈川県"}, {id: 15, name: "新潟県"}, {id: 16, name: "富山県"}, {id: 17, name: "石川県"}, {id: 18, name: "福井県"}, {id: 19, name: "山梨県"}, {id: 20, name: "長野県"}, {id: 21, name: "岐阜県"}, {id: 22, name: "静岡県"}, {id: 23, name: "愛知県"}, {id: 24, name: "三重県"}, {id: 25, name: "滋賀県"}, {id: 26, name: "京都府"}, {id: 27, name: "大阪府"}, {id: 28, name: "兵庫県"}, {id: 29, name: "奈良県"}, {id: 30, name: "和歌山県"}, {id: 31, name: "鳥取県"}, {id: 32, name: "島根県"}, {id: 33, name: "岡山県"}, {id: 34, name: "広島県"}, {id: 35, name: "山口県"}, {id: 36, name: "徳島県"}, {id: 37, name: "香川県"}, {id: 38, name: "愛媛県"}, {id: 39, name: "高知県"}, {id: 40, name: "福岡県"}, {id: 41, name: "佐賀県"}, {id: 42, name: "長崎県"}, {id: 43, name: "熊本県"}, {id: 44, name: "大分県"}, {id: 45, name: "宮崎県"}, {id: 46, name: "鹿児島県"}, {id: 47, name: "沖縄県"} ] end 「db/seeds.rb」内を以下のように編集し、サンプルデータを挿入してください。 ./db/seeds.rb puts "Creating users..." Faker::Config.locale = "ja" 500.times do gender = User.genders.keys.sample User.create!( first_name: gender == "male" ? Faker::Name.male_first_name : Faker::Name.female_first_name, last_name: Faker::Name.last_name, gender: gender, birthdate: Faker::Date.birthday(min_age: 18, max_age: 65), prefecture_id: Prefecture.all.sample.id ) end puts "Finished" $ docker-compose run web rails db:seed 良い感じに入ってますね。 日本語化対応 「config/application.rb」内に以下を内容を記述し、デフォルトのタイムゾーンや言語を日本に変更してください。 ./config/application.rb module Myapp class Application < Rails::Application # ... # 追記 config.time_zone = "Tokyo" config.active_record.default_timezone = :local config.i18n.default_locale = :ja config.i18n.load_path += Dir[Rails.root.join("config", "locales", "**", "*.{rb,yml}").to_s] # ... end end また、「config/locals/」以下にja.ymlを作成します。 $ touch config/locales/ja.yml ./config/locales/ja.yml ja: activerecord: models: user: ユーザー attributes: id: ID created_at: 作成日時 updated_at: 更新日時 user: first_name: 名 last_name: 姓 full_name: 名前 gender: 性別 birthdate: 生年月日 age: 年齢 prefecture_id: 都道府県 enums: user: gender: male: 男性 female: 女性 Form オブジェクト(UserSerachForm) を作成 ここからが本記事の目玉。 検索を行うためのロジックを Form オブジェクトで実装していきます。 $ mkdir app/forms $ touch app/forms/user_search_form.rb ./app/forms/user_search_form.rb class UserSearchForm include ActiveModel::Model attr_accessor :first_name, :last_name, :gender, :min_age, :max_age, :prefecture_id def initialize(params = {}) @first_name = params[:first_name] @last_name = params[:last_name] @gender = params[:gender] @min_age = params[:min_age] @max_age = params[:max_age] @prefecture_id = params[:prefecture_id] end # クエリを実行 def query base_relation .then(&method(:search_by_name)) .then(&method(:search_by_gender)) .then(&method(:search_by_ages)) .then(&method(:search_by_prefecture_id)) end private # 基本条件 def base_relation User.all end # 名前で検索(必ずしも姓・名の両方が入力されるとは限らないため、入力された値によって検索条件を変える) def search_by_name(relation) if first_name.present? && last_name.present? # 姓・名の両方が入力された場合 search_by_full_name(relation) elsif first_name.present? && last_name.blank? # 名のみ入力された場合 search_by_first_name(relation) elsif first_name.blank? && last_name.present? # 姓のみ入力された場合 search_by_last_name(relation) else relation end end # フルネーム(姓 + 名)で検索 def search_by_full_name(relation) relation .where("last_name LIKE ?", "%#{ActiveRecord::Base.sanitize_sql_like(last_name)}%") .where("first_name LIKE ?", "%#{ActiveRecord::Base.sanitize_sql_like(first_name)}%") end # 名で検索 def search_by_first_name(relation) relation.where("first_name LIKE ?", "%#{ActiveRecord::Base.sanitize_sql_like(first_name)}%") end # 姓で検索 def search_by_last_name(relation) relation.where("last_name LIKE ?", "%#{ActiveRecord::Base.sanitize_sql_like(last_name)}%") end # 性別で検索 def search_by_gender(relation) return relation if gender.blank? relation.where(gender: gender) end # 年齢で検索(Userモデルに年齢の値そのものが保持されているわけではないので誕生日から計算) def search_by_ages(relation) return relation if min_age.blank? || max_age.blank? start_date = Date.today - max_age.to_i.years end_date = Date.today - min_age.to_i.years relation.where(birthdate: start_date..end_date) end # 都道府県で検索 def search_by_prefecture_id(relation) return relation if prefecture_id.blank? relation.where(prefecture_id: prefecture_id) end end 基本的にはコードを読んでいただければ何をしているのかわかると思いますが、あまり見慣れない部分としては def query base_relation .then(&method(:search_by_name)) .then(&method(:search_by_gender)) .then(&method(:search_by_ages)) .then(&method(:search_by_prefecture_id)) end この辺かなと思います。 この then は「レシーバを引数としてブロックを呼び出し、そこで評価された結果を返す」というメソッドで、 tap メソッドと似ている事からしばしば比較されていたりします。 参照: Ruby: Object#tap、Object#then を使ってみよう その性質からメソッドチェーンにおいて便利と言われており、今回もそんな感じの用途で使用しました。 「一つ前の処理で返ってきた結果に対し再度処理を施してその結果を返す...」 というプロセスを繰り返し、各条件に合わせた絞り込みを行なっていくイメージですね。 今回は &記法にしているので若干わかりにくいかもしれませんが、もう少し素直に書くなら def query base_relation .then { |relation| search_by_name(relation)} .then { |relation| search_by_gender(relation)} .then { |relation| search_by_ages(relation)} .then { |relation| search_by_prefecture_id(relation)} end こんな感じになると思います。どちらでも正常に動くのでお好きな方を採用してください。 ちなみに、Rubyには yiled_self という同じ挙動を持つ別のメソッドがあったりしますが、 「then」 はそのエイリアスになります。(「yiled_self」という名前がやや不評で後ほど追加されたとの事。) コントローラーを作成 コントローラーを作成し、先ほど定義した Form オブジェクトを呼び出します。 $ docker-compose run web rails g controller users ./app/controllers/users_controller.rb class UsersController < ApplicationController def index @users = User.order_by_latest end def search @users = UserSearchForm.new(user_search_params).query.order_by_latest render :index end private # 検索用のストロングパラメータ def user_search_params params.fetch(:q, {}).permit( :first_name, :last_name, :gender, :min_age, :max_age, :prefecture_id ) end end ビューを作成 見た目の部分を作り込んでいきます。 $ touch app/views/users/index.html.slim app/views/users/_search_form.html.slim ./app/views/users/index.html.slim .container.p-3 .row.mb-4 .col-12 .d-flex.align-items-center.justify-content-center = render "search_form" .row .col-12 .mt-1.mb-2 = "#{@users.count} 件" .card.card-outline .card-body table.table thead tr th scope="col" = t("activerecord.attributes.id") th scope="col" = t("activerecord.attributes.user.full_name") th scope="col" = t("activerecord.attributes.user.gender") th scope="col" = t("activerecord.attributes.user.age") th scope="col" = t("activerecord.attributes.user.prefecture_id") - if @users.present? - @users.each do |user| tr td = user.id td = user.full_name td = user.gender_i18n td = user.age td = user.prefecture.name - else tr td 該当するデータがありません td td td td ./app/views/users/_search_form.html.slim = form_with url: search_users_path, scope: :q, method: :get, local: true do |f| .row .col-4 .form-group = f.label t("activerecord.attributes.user.last_name"), class: "control-label" = f.text_field :last_name, value: params.dig(:q, :last_name), class: "form-control" .col-4 .form-group = f.label t("activerecord.attributes.user.first_name"), class: "control-label" = f.text_field :first_name, value: params.dig(:q, :first_name), class: "form-control" .col-4 .form-group = f.label t("activerecord.attributes.user.gender"), class: "control-label" = f.select :gender, User.genders_i18n.invert, { include_blank: true, value: nil, selected: params.dig(:q, :gender) }, class: "form-select" .col-4.mt-2 .form-group = f.label "#{t('activerecord.attributes.user.age')}(from)", class: "control-label" = f.select :min_age, [*(18..65)], { include_blank: true, value: nil, selected: params.dig(:q, :min_age) }, class: "form-select" .col-4.mt-2 .form-group = f.label "#{t('activerecord.attributes.user.age')}(to)", class: "control-label" = f.select :max_age, [*(18..65)].reverse, { include_blank: true, value: nil, selected: params.dig(:q, :max_age) }, class: "form-select" .col-4.mt-2 .form-group = f.label t("activerecord.attributes.user.prefecture_id") = f.collection_select :prefecture_id, Prefecture.all, :id, :name, { include_blank: true, value: nil, selected: params.dig(:q, :prefecture_id) }, class: "form-select" .d-flex.align-items-center.justify-content-center.mt-4 = f.submit "検索", class: "btn btn-primary me-1" = link_to "リセット", users_path, class: "btn btn-secondary ms-1" scope: :q という部分で検索用のパラメータにまとめている部分だけご注意ください。 参照: form_withの:scopeオプション ルーティングを設定 最後にルーティングを設定しましょう。 ./config/routes.rb Rails.application.routes.draw do resources :users, only: :index do collection do get :search end end end 動作確認 localhost:3000/users にアクセスしてこんな感じになっていれば完成です。 試しに色々な条件で検索してみてください。 あとがき 以上、Form オブジェクトを使ってシンプルな検索機能を作ってみました。 ロジック部分の大半を Form オブジェクトに切り出した事でモデル・コントローラー・ビューがすっきり保てているのが大きなメリットですね。 少しでも参考になれば幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む