- 投稿日:2022-03-23T21:57:48+09:00
カレンダーミニアプリを作ってみた
RailsのGemであるSimple Calendarを利用して、ミニアプリを作ってみた。 アプリの新規作成 まずはアプリの新規作成から。 ターミナルで以下を実行する。 ターミナル rails _6.0.0_ new minicalendar -d mysql 次にencodingの設定を変更。 config/database/yaml default: &default adapter: mysql2 encoding: utf8 #ここを変更する pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root .gitignoreファイルの一番下の行に、.Ds_Storeと追記。 gitignore /public/packs /public/packs-test /node_modules /yarn-error.log yarn-debug.log* .yarn-integrity .Ds_Store #ここに追記 続いてGemfileの編集。 Gemfile # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '~> 6.0.0' # Use mysql as the database for Active Record gem 'mysql2', '>= 0.5.3' #ここを変更 ターミナルでbundle installを実行する。 ユーザー機能の実装 次にユーザー機能を実装していく。 まずはGemの導入。 Gemfile gem 'devise' #一番下に追記する 追記後、bundle install。 ターミナルで以下を実行してdeviseをインストール → モデルの作成。 ターミナル rails g devise:install rails g devise user 今回初期設定のメールアドレスとパスワード以外にユーザー名も登録したいので、以下のようにカラムを追加する。 db/migrate/xxxxxx_devise_create_users.rb class DeviseCreateUsers < ActiveRecord::Migration[6.0] def change create_table :users do |t| ## Database authenticatable t.string :email, null: false, default: "" t.string :encrypted_password, null: false, default: "" t.string :name #追記 追記したらbundle install nameカラムをパラメーターとして渡してあげる記述とauthenticate_user!メソッドを記述。 authenticate_user!メソッドを使用すると、処理が呼ばれた段階で、ユーザーがログインしていなければ、そのユーザーをログイン画面に遷移させる事ができる。 app/controllers/application_controller.rb class ApplicationController < ActionController::Base before_action :authenticate_user! before_action :configure_permitted_parameters, if: :devise_controller? private def configure_permitted_parameters devise_parameter_sanitizer.permit(:sign_up, keys: [:name]) end end 続いてビューファイルの作成。 ターミナル rails g devise:views ビューファイルにnameカラムの入力フォームを追加する。 app/views/devise/registrations/new.html.erb <div class="field"> <%= f.label :name %><br /> <%= f.text_field :name, autofocus: true, autocomplete: "name" %> </div> シンプルカレンダーの導入 Gemfileに以下を記述。 Gemfile gem "simple_calendar", "~> 2.4" # 一番下に追記 bundle installを実行。 モデル作成。 ターミナル rails g model calendar 次にルーティングを設定。 今回ルートパスにindexページを設定している。 config/routes.rb Rails.application.routes.draw do devise_for :users # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html root to: 'calendars#index' resources :calendar, only: [:index] end 次にコントローラー。 ターミナル rails g controller calendars app/cotrollers/calendars_controller.rb class CalendarsController < ApplicationController def index end end 最後にビューファイルの作成。 とりあえずは以下のように記述すると、カレンダーのフォーマットのようなものが表示されるようになる。 app/views/calendars/index.html.erb <%= month_calendar do |date| %> <%= date %> <% end %> 実装確認 rails sでローカルサーバーを起動。 ユーザー登録を経て、表示されたのが、以下のページ。 デザインは味気ないが、とりあえずは成功。 あとはこれに新規スケジュールの作成機能などを追加していくつもりだが、それはまた次回に。
- 投稿日:2022-03-23T19:17:57+09:00
【LeetCode】26. Remove Duplicates from Sorted Arrayを解いてみた
はじめに コーディングテスト対策としてLeetCodeの7. Reverse Integerを解いていく。 問題文を和訳 これは、O(1) の余分なメモリを使用して入力配列をin-placeで変更することによって行う必要があります。 明確化: 戻り値が整数であるのに、答えが配列である理由が混乱していますか? 入力配列は参照によって渡されることに注意してください。 つまり、入力配列への変更は呼び出し側にも認識されます。 内部的には、次のように考えることができます。 numsは参照によって渡されます。(つまり、コピーを作成せずに) int len = removeDuplicates(nums); 関数内のnumsへの変更は、呼び出し元に認識されます。 関数から返された長さを使用して、最初のlen要素を出力します。 for(int i = 0; i <len; i ++){ print(nums [i]); } Input: nums = [1,1,2] Output: 2, nums = [1,2] Explanation: Your function should return length = 2 with the first two elements of nums being 1 and 2 respectively. It doesn't matter what you leave beyond the returned length. 回答 26_RemoveDuplicatesfromSortedArray.rb def remove_duplicates(nums) index = 0 if nums.length == 0 return 0 end for i in 0...nums.length do if nums[i] != nums[index] index += 1 nums[index] = nums[i] end end return index + 1 end 最後に 難易度はEasyでした。
- 投稿日:2022-03-23T18:53:46+09:00
【LeetCode】21. Merge Two Sorted Listsを解いてみた
はじめに コーディングテスト対策としてLeetCodeの21. Merge Two Sorted Listsを解いていく。 問題文を和訳 並べ替えられた 2 つの連結リストをマージし、 並べ替えられた連結リストとして返します。 リストは、最初の 2 つのリストのノードをつなぎ合わせて作成する必要があります。 Input: l1 = [1,2,4], l2 = [1,3,4] Output: [1,1,2,3,4,4] 回答 21_MergeTwoSortedLists.rb def merge_two_lists(list1, list2) list3 = ListNode.new(0) result = list3 while list1 != nil && list2 != nil if list1.val <= list2.val list3.next = list1 list1 = list1.next else list3.next = list2 list2 = list2.next end list3 = list3.next end list3.next = list1 || list2 return result.next end 最後に 難易度はEasyでした。
- 投稿日:2022-03-23T15:51:53+09:00
Rubyではシフト演算 `1 << k` より配列参照 `lookup[k]` のほうが速い
AtCoderの問題をRubyで復習していて、どうしても時間制限超過(TLE)してしまうものがあった。 他の方の解答を見たりして試しているうちに、どうもシフト演算の実行回数が多いとTLEしやすいということがわかった1。計算したいのは 1 << k という単純なもの(を最大3000万回ほど)なので、これを事前に計算しておき配列参照 lookup[k] に置き換えたところ無事に通過した。 そんなに速度差があるのか気になったので、ベンチマークしてみた。 ベンチマーク AtCoderのコードテストを利用。その結果、配列参照のほうが約1.3倍速く、3000万回計算すると0.3秒ほど短くなった。 shift-vs-lookup.rb puts RUBY_DESCRIPTION puts require 'benchmark' n = 30 lookup = Array.new(n) { |i| 1 << i } m = 30_000_000 ary = Array.new(m) { rand(n) } Benchmark.bmbm do |x| x.report("shift") { ary.each { |k| 1 << k } } x.report("lookup") { ary.each { |k| lookup[k] } } end 標準出力 ruby 2.7.1p83 (2020-03-31 revision a0c7c23c9c) [x86_64-linux] Rehearsal ------------------------------------------ shift 1.528244 0.000037 1.528281 ( 1.528378) lookup 1.152500 0.000020 1.152520 ( 1.152588) --------------------------------- total: 2.680801sec user system total real shift 1.260826 0.000017 1.260843 ( 1.260900) lookup 0.958101 0.000000 0.958101 ( 0.958157) 他の演算なども遅い可能性はあるが、削減しやすいのはシフト演算だけだった。 ↩
- 投稿日:2022-03-23T15:43:57+09:00
【Ruby】みんな理解出来てる?テンプレートメソッドパターンのこと。
テンプレートメソッドパターンとは? テンプレートメソッドパターンはGoFのデザインパターンの一種です。 このパターンは、基底クラスに不変の部分を記述し、変わる部分はサブクラスに定義するメソッドにカプセル化するというモノです。 変わるものと変わらないものを分離するという設計の考えに基づいています。 つまり次の2つのオブジェクトによって構成されます。 - 骨格となる「抽象的な基底クラス」 - 実際の処理を行う「サブクラス」 メリット 基底クラス側に、「不変の基本的なアルゴリズム」を配置可能になる。 「高レベルの処理を制御すること」に集中できる。 サブクラス側に、「変化するロジック」を置ける 「詳細を埋めること」に集中できる 似通った部分を共通化して「サブクラスに対してスーパクラスで実装されている抽象メソッドを実装する」という責任を与えることが可能になる。 実装例 基底クラス class Test def open() raise NotImplementedError.new("#{self.class}##{__method__}") end def close() raise NotImplementedError.new("#{self.class}##{__method__}") end def output() raise NotImplementedError.new("#{self.class}##{__method__}") end def display open() 5.times do output() end close() end end サブクラス class SubTest < Test def initialize(string) @string = string end def open print 'open!' end def close print 'close!' end def output print @string end end 実行例 sub_test = SubTest.new('テスト') sub_test.display #=> open! #=> "テスト" #=> "テスト" #=> "テスト" #=> "テスト" #=> "テスト" #=> close!
- 投稿日:2022-03-23T15:12:41+09:00
【Ruby】構造体クラスを使いこなしたい。
概要 みなさん。「構造体クラス」使ってますか? たくさん利用してる人もいれば、聞いた事もない人がいるかと思います。 便利なRubyのクラスなのでぜひ頭の隅にでも置いておいてください! Sturctとは Rubyにおける構造体クラス(Struct)は簡易的なクラスのようなものです。 明示的にアクセスメソッドを定義しなくても、構造体クラス外でメンバの参照・更新は可能です。 以下のような場面で使えます。 まとまったデータを扱いたいが、クラス定義式を使ってクラスを作るまでもない場合。 クラス内で特定のデータのまとまりを表現する場合。 定義方法 以下のように定義することで構造体クラスを使えるようになります。 # Struct.new('構造体クラス名', メンバ) Struct.new("User", :name, :age) # 第一引数を省略する事も可能 User = Struct.new(:name, :age) # クラスのように定義する事も可能(非推奨) class User < Struct.new(:name, :age) end puts Struct::User.new('山田太郎', 25) #=> #<struct Struct::User name="山田太郎", age=25> 参照や更新は以下のように簡単に行えます。 Struct.new('User', :name, :age) user = User.new('山田太郎', 25) # 参照 puts user.name #=> "山田太郎" # 更新 user.name = "山田二郎" puts user.name #=> "山田二郎" クラス定義式のようにメソッドを設定する事も可能です。 User = Struct.new('User', :name, :age) do def introduction "私の名前は#{name}です" end def greet(word) word end end user = User.new('山田太郎', 25) p user.introduction #=> 私の名前は山田太郎です p user.greet('こんにちは') #=> こんにちは 注意点 指定されたメンバ数以上の引数を与えるとエラーとなる。 指定されたメンバ数以下のの引数の場合はnilが入る。 インスタンスのメンバの値が同じであれば比較した際にtrueを返す。 こういったものがある OpenStructというRubyの標準ライブラリがあります。 これは要素を動的に追加・削除できる手軽な構造体を提供するクラスです。 使い方 require 'ostruct' # 構造体を作って動的にpropertyを追加 ostruct = OpenStruct.new ostruct.name = '山田太郎' ostruct.age = 25 puts ostruct.name #=> "山田太郎" puts ostruct.age #=> 25 # ハッシュを使用することも可能 ostruct = OpenStruct.new({ name: '山田太郎', age: 25 }) puts ostruct.name #=> "山田太郎" puts ostruct.age #=> 25
- 投稿日:2022-03-23T14:36:21+09:00
【Rails】なぜ"private" "protected" "public"に分けるのか
概要 あるメソッドを実装するときに「アクセス権をどうするか?」という問題に必ず直面すると思います。 それぞれの特徴を知っておくことで、効率良く設計・開発が可能です。 ちなみにこれはカプセル化と言い、、オブジェクト同士の紐付き(関係性)を薄くし独立性を高め、再利用や交換といった保守性を高める効率の考え方です。 特徴 publicメソッド - クラスの主な責任や目的を明らかにする - 外部から実行されることが想定される - 思いつくままに変更されたりはしない - 他者が依存したとしても安全 - テストで完全に文書化されている privateメソッド - 実装の詳細に関わる部分 - 外部から実行されることは想定されていない - 変更されやすい - 他者が依存するのは危険 - 基本的にはテストはしない(個人的な意見です。諸説あり。) protectedメソッド - 外部からは隠蔽されている - 仲間(自クラスかサブクラスのレシーバー、親子関係にあるクラスのみ)からは実行可能 判断のポイント なるべく狭い範囲で設定するようにする。 余計なアクセス権は想定外の場所の不具合を生む原因にもなりかねないため。 privateメソッドを多用しないようにする。 本来そのクラスの責務でないものを抱えてしまうことが可能性があるため。 「別のクラスのpublicメソッドにする」可能性を視野に入れる。 最も範囲が狭いprivateをベースとして設計する。 継承先、あるいは継承元で呼び出したり上書きしたい場合はprotectedにする。 外部からのアクセスを可能にしたい場合はpublicにする。
- 投稿日:2022-03-23T11:52:11+09:00
RailsアプリにChoices.jsを使ったセレクトボックスを作る
概要 Choices.jsを使ってセレクトボックスを作成しました はじめに 現在、フィヨルドブートキャンプでチーム開発をしています。 Choices.jsというライブラリを使って、セレクトボックスを作成しましたが、情報が少なかったのでQiitaに記事として残しておきます。 初学者向けの内容となっています。 Choices.jsについて 軽量な選択ボックス/テキスト入力プラグインです。リッチなセレクトボックスが作れます→DEMO Select2およびSelectizeに似ていますが、jQueryの依存関係はありません。 2年ほどリリースが止まっていたようですが、2021年の12月より再びメンテナンスされているようです。 インストール npm npm install choices.js yarn yarn add choices.js stylesheet 導入したらしっかりstylesheetで読み込みましょう。 自分はこれを忘れて無駄な時間を過ごしてしまいました?(忘れるのは初学者の自分だけだと思います) CDNは以下、その他ディレクトリで読み込むものなど多数あります(README参照) <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/choices.js@9.0.1/public/assets/styles/choices.min.css"/> 用語 専門用語というほどではないんですが、ここを読み解かないとうまく使いこなせませんでした。 ほとんどREADMEを翻訳したものだけど載せておきます。 (今回はセレクトボックスを作るので、テキストボックスとしての使い方は割愛) 用語 説明 Choice ユーザーが実際に選択できる値で、<option>要素と同等 Group 選択肢の集まりでその名の通りグループ。<optgroup>要素と同等 Item 選択された要素の<option value = "item">要素と同等 HTMLでのセレクトボックスはというと <select name="spice"> <option value="">スパイスを選択</option> <option value="garammasala">ガラムマサラ</option> <option value="coriander">コリアンダー</option> <option value="cumin">クミン</option> </select> となっているのでChoiceは<option>のこと、Itemは<option>のvalueだとわかります。 ということでChoices.jsを使って、もっとも簡単だと思われるコードを書いてみました index.html <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>Spice select</title> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/choices.js@9.0.1/public/assets/styles/choices.min.css" /> <script src="https://cdn.jsdelivr.net/npm/choices.js@9.0.1/public/assets/scripts/choices.min.js"></script> </head> <body> <select name="spice" id="hoge"> <option value="">スパイスを選択</option> <option value="garammasala">ガラムマサラ</option> <option value="coriander">コリアンダー</option> <option value="cumin">クミン</option> </select> <script> document.addEventListener("DOMContentLoaded", () => { new Choices("#hoge") }) </script> </body> </html> セレクトボックスにhogeというidを振って、Choicesオブジェクトを作る際に紐づけるだけです。 これでインクリメンタルサーチが簡単に使えるし、上の通りvalue属性でもヒットします。 選択肢が多くなったときに便利ですね。 開発者ツールを見てみるとclass="choices"やdata-type="select-one"などのタグが使われています。 <select>はselect-one、<select multiple>とselect-multipleとなっていて、 オプションが反映できるか否かは、この入力タイプを確認するとわかりやすいと思います。 Railsでの使い方 再び簡単な復習になるんですが、既にSpiceモデルがあるとして、selectを使うと select(:spice, :name, Spice.pluck(:name, :id), { include_blank: "スパイスを選択" }) collection_selectを使うと collection_select(:spice, :name, Spice.all, :id, :name, prompt: "スパイスを選択") これをHTMLで出力すると、いずれも <select name="spice[name]" id="spice_name"> <option value="">スパイスを選択</option> <option value="1">ガラムマサラ</option> <option value="2">コリアンダー</option> <option value="3">クミン</option> </select> のようになります。 こちらもこのセレクトボックスにidさえ付与すれば、Choices.jsが使用可能です。 ここからは実際に自分がチーム開発で実装したコードに、一部修正を加えたものを記載していきます。 環境とgem ruby 3.1.0 Rails 6.1.4.4 webpacker 5.4.3 Choices.js slim-rails UserモデルとSpiceモデルが存在し、UserはSpice(好みのスパイス)を1つ持つような関係です。 CSSに関してはnode_modulesから読み込むように設定しています。 view = f.label :spice, class: 'a-form-label' = f.collection_select :spice_id, all_spices_with_empty, :id, :name, {}, { id: 'js-spice-select' } フォーム内でのセレクトボックスです。 セレクトボックスにjs-spice-selectというidを付与しています。 all_spices_with_emptyは選択肢なしを含めた配列です。 def all_spices_with_empty Spice.all.to_a.unshift(Spice.new(name: '好みのスパイスなし')) end javascript app/javascript/packs/application.js import '../spice-select.js' JavaScriptファイルをWebpacker経由で読み込んでいます。 app/javascript/spice-select.js import Choices from 'choices.js' document.addEventListener('DOMContentLoaded', () => { return new Choices('#js-spice-select', { removeItemButton: true, searchResultLimit: 10, searchPlaceholderValue: '検索ワード', noResultsText: '一致する情報は見つかりません', itemSelectText: '選択' }) }) 以上のように、少しだけオプションをつけてみますと Choices.jsを使ったセレクトボックスが完成しました? これがRailsでChoices.jsを使う初歩的な方法だと思います。 他にも選択肢を他からfetchして作成したり、複数の選択肢を選んだり、タグをつけたり、、、 などなどカスタマイズが可能なので、少しリッチなセレクトボックスを作成するのにいかがでしょうか? 最後に 最後までご覧いただきありがとうございました。本記事がどなたかのご参考になれば幸いです。 何かご意見やご感想などありましたら、お気軽にコメントや編集リクエストをお待ちしております? 参考文献 執筆にあたり、以下の記事を参考とさせていただきました。ありがとうございました?♂️
- 投稿日:2022-03-23T11:35:35+09:00
Rails + RBS & Steepを試してみて困ったところとか感想とか
まえがき Railsアプリに型チェックを導入しようとして試しているのですが、なかなか一筋縄ではいかないようで、試行錯誤しています。 おそらく他にも似たようなことでハマったりしている人もいるかと思うので、これまで困ったこと、そして(回避できた場合は)回避策を書いてみます。 なお、RBSやSteepとはなんぞやといった辺りは特に説明せずにいきなり本題に入るため、詳しくない方はmameさんの記事「Ruby 3の静的解析機能のRBS、TypeProf、Steep、Sorbetの関係についてのノート」などを参考にしてください。 Steep & RBSで困ったところ BigDecimalを導入してもto_dが使えない (追記: ksssさんにrbsのstdlibのテストの書き方を教えてもらったので書けるようになりました! 結果はpull requestしてみたので、最終的に取り込まれれば使えるようになるはず。) Steepfileにlibrary "bigdecimal"を書いてもダメでした。rbsの定義が必要そうですが、stdlibのテストの書き方がよく分からない…(assert_send_typeを使ってもmethod_typesでエラーになって挫折しました…)。 とりあえずローカルのrbsファイルに以下のように書くと回避できます。 class Integer def to_d: () -> BigDecimal end class String def to_d: () -> BigDecimal end class Float def to_d: () -> BigDecimal end ちょっとベタですが、どうせアプリ内ではどこでも使えるメソッドですし、理解しやすくはありそうです。 この手の既存クラスにメソッドを生やすやつは、こんな感じに適宜メソッドを追加していくことになりそうです。 include ActiveModel::Validationsをしたクラスでもvalidationメソッドが使えない ActiveRecordでもないモデルクラスとして、ActiveModelを使ってvalidationを書きたくなることがあります。 そうした場合、include ActiveModel::Validationsをした上でvalidates :foo, presence: trueのように書くかと思いますが、そうすると以下のようなエラーになります。 [error] Type `singleton(::Foo)` does not have method `validates` │ Diagnostic ID: Ruby::NoMethod` 仕方がないのでrbsファイルに以下のように書いて、明示的にvalidationのクラスメソッドを有効にすると回避できます。 class Foo include ActiveModel::Validations extend ActiveModel::Validations::ClassMethods # ... end include ActiveModel::Modelをしたクラスでもvalidationが使えない 上に同じです。 module ClassMethodsが効いてなさそう 上記の話のそもそもの原因ですが、Railsの場合、module ClassMethodsを使ってクラスメソッドを定義しているコードを普通に使うかと思います。 ところが、これはRubyとして解釈されるから機能するわけで、この辺りをそのままrbsに書いてもクラスメソッドの定義としては解釈されないのは当然ですよね……。 案外RBS用のmodule用構文として、includeするとsingletonメソッドがクラスメソッドになるようなものが記述できるとしあわせになれるかも、と思いました。 includeの代わりにinclude_singletonと書く、みたいなやつです(あまり深く考えず思いつきで書いています)。 ActiveRecordのvalidationでvalidate :foobar, if: -> { new_record? } のように書いたときに Diagnostic ID: Ruby::NoMethodになる 「Type singleton(::FooModel) does not have method new_record?」みたいなエラーが出てしまうのでした。 これはif:のブロック内について、クラスメソッドで探索しているのですが、実際にはインスタンスメソッド(が使えるようになっている)だからですよね。 こういったif:ブロック等の中はインスタンスレベルのメソッドとして解釈してほしいのですが、どうにかできないですかね…? Foo = Struct.new(.....)というコードを普通に記述できない RubyでStructクラスを使う場合、普通は定数にStruct.newの結果を代入して、その定数をクラスのように使うかと思います。 が、素朴にこう書くと、SteepでRuby::IncompatibleAssignmentのエラーが出てしまいます。 rbsのリポジトリを見る限りでは、このような場合にはFoo = _ = Struct.new(.....)という書き方をするようです。 が、これは不自然ですし、見た目的にも分かりやすくはないと思います。もうちょっと自然なRubyのコードを書いて、うまいこと解釈していただきたいところです。 どちらかというと後述の型チェックを抑制するようなコメントを使えるようにした方がマシなような気がします。が、慣れの問題もあるかも。 RuboCopのdisable/enableみたいなことが書けない Steepを本番投入するのにいちばん重要なのは、部分的にのみ型チェックを行うようにして、その「部分」を柔軟に指定できることではないかと思いました。 具体的には、この行からこの行まで無視してほしい、みたいなときに、RuboCopのようにコメントで指定できると良さそうです。 先ほどのif:ブロックの例だと、以下のように書いて回避する感じです。 class FooModel include ActiveModel::Validations # steep:disalbe Ruby::NoMethod validate :foobar, if: -> { new_record? } # steep:enalbe Ruby::NoMethod # ... end 特定のファイルだけチェックしたいときにsteep checkの引数で指定できない 上と同じような話で、1ファイルだけテストしたいときにSteepfileを都度変更するのはちょっと面倒な感じでした。 この辺りはコマンドラインオプションで指定できると、作業的に楽になりそうな気がします。 I18n.tが通らない → pull request送りました。 https://github.com/ruby/gem_rbs_collection/pull/130 Time.zone.now.yesterdayが通らない → pull request送りました。 https://github.com/ruby/gem_rbs_collection/pull/134 その他もろもろpull requestしてみています。 とりあえずの感想 そんなわけでSteepがちゃんと通るようになるのはまだまだ先は長そうなのですが、とりあえず現時点での感想を書いておきます。 gem_rbs_collectionは「published」なAPIのみでいいかも? publishedなAPIというのは、Martin Fowlerの言う公布済みインターフェイスのことです。 gem_rbs_collectionについて説明しておくと、これは様々なgemのRBSを集積するためのリポジトリです。 そもそもgemのRBSを記載し共有するには2つの方法があります。 各gemの中に書いて、一緒に公開する(標準ではそのgem内のsig/ディレクトリに書く) gem_rbs_collectionにgemのrbsを追加する 前者はそのgemの開発者(チーム)自身が書くもので、後者は主にgem開発者ではない人がそのgemをRBSを使うために書くものになるかと思います。 そう考えると、privateメソッドも含めたgemの網羅的なRBSはgem開発者が書いたり使ったりするもので、単にそのgemを使いたい人にとってはRBSで記述してほしいAPIはpublicの中でも公布済みインターフェイスと思われるものだけになりそうです。 そもそもprivateなメソッドの仕様を外部の人が考えたり決めたりするというのも変な話ですし。 (ただし、moduleについてはprivateなメソッドであっても、そのmoduleをincludeしたクラスから利用する前提で作られている場合もありますよね…。そこはちょっと扱いが難しいかもしれません。) 実際、gem_rbs_collectionのContribution Guideにも「Focus on the most important part of the API」「Focus on examples available through the README or docs of the gem. Focus on the APIs your app is using.」と書かれているわけで、この辺はそういうことかもと思いました。 もちろん、開発しているアプリ自体のsig/ディレクトリに置くRBSは、また別の開発指針が考えるのが良さそうです。 (Yet another) developer testingとしてのRBS RBSの「書き味」は、なんとなくRSpecでテストを書くのに似ているような気がします。 そう考えると、RBSとSteepに求めるのは厳密な「型」やその代替物ではなく、「開発を駆動するためのツールとしくみ」であると考えた方がよいかもしれません。 というのも、Rubyで型安全な世界、あるいはそれに近い世界が早期に実現することは現実的ではなさそうなことに加えて、そこに注力するのもなにか違うのではという感触があります。まあ実際のところそうですよね。 そして現状のRBSの手応えは、開発者テスト(developer testing)の世界で、完全なテストを書くことが現実的ではないことに似ているような気がするのでした。 しかし、開発者テストが完全ではなくても、開発者テストが便利なものであることは、日々の開発の中で実感されている方も多いはずです。 RBSも同様に、gem開発者とgem_rbs_collection開発者とアプリ開発者がそれぞれ協力して、みんなでみんなのアプリやライブラリが開発しやすくなるように育てていくためのしくみだと考えてみるのも、あながち間違ってはいないのではないでしょうか。 現状のRBS・Steepの使い勝手 Rails関連についてはまだ正しいメソッドがエラーになることがありそうだった(気づいたところはPull Requestしました)ので、追加・修正はまだまだ必要かもしれません。 もっとも、これはみんなで触っていればすぐ気づくところではあるので、それほど時間がかからず改善されるとは思います。 そして現状でもnull(nil)チェックが甘いところは普通に見つかるようです。これはありがたいかもしれません。 あとTypeScriptでいうところの型ガード的なものはまだ使えないんでしたっけ? そうすると無駄な検知が増えるので使い勝手がよろしくないかもしれません。 とりあえず上で書いたコメントでの抑制ができると多少は有効そうですが、やや限界もあるかも。 とはいえ型ガードが使えるようになったとして、Rubyのコードの中に型ガード的な記述が増えすぎるのもそれはそれでうれしくないかもしれないですね…。 そこまで到達するのはもう少し先になりそうですが、むしろそうなってからがRubyと型との関係性をどうするべきかの突っ込んだ議論が始まりそうです。 頑張っていきましょう また、例えばRailsで普通に使われているgemについても、まだまだgem_rbs_collectionにないものもありそうです。 先ほどの点と合わせてこれは時間が解決する問題かとは思いますが、その時間を圧縮するにはより多くの人の参加と協力が必要になりそうです。 まずは粛々とgem_rbs_collectionを整備していければ良さそうなので、そのためにもまずは開発者・利用者を増やしつつ、便利なものにしていければいいんではないかと思います。興味のある方はぜひ試してみてはいかがでしょうか。 頑張っていきましょう。
- 投稿日:2022-03-23T11:19:18+09:00
本日の基礎練習問題②(22/3/23)
問題 西暦の年数および月を入力し、その月の日数を求めるプログラムを書きます。 その場合、閏年について考慮する必要があります。 閏年は以下の判断基準で決まります。 ①その西暦が4で割り切れたら閏年である ②ただし、例外として100で割り切れる西暦の場合は閏年ではない ③ただし、その例外として400で割り切れる場合は閏年である つまり、西暦2000年は閏年であり、西暦2100年は閏年ではありません。 これらに対応できるように、出力例と雛形をもとに実装しましょう。 出力例 1990年2月 =>"1990年2月は28日間あります" 2000年2月 =>"2000年2月は29日間あります" 2100年2月 =>"2100年2月は28日間あります" 2000年3月=>"2000年3月は31日間あります" 雛形. def get_days(year, month) month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] # ここに処理を書き加えてください end puts "年を入力してください:" year = gets.to_i puts "月を入力してください:" month = gets.to_i days = get_days(year, month) puts "#{year}年#{month}月は#{days}日間あります" 私の回答 時間制限のため回答できず‥ 模範回答 def get_days(year, month) month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] if month == 2 if year % 4 == 0 if year % 100 == 0 && year % 400 != 0 days = 28 else days = 29 end else days = 28 end else days = month_days[month - 1] end return days end puts "年を入力してください:" year = gets.to_i puts "月を入力してください:" month = gets.to_i days = get_days(year, month) puts "#{year}年#{month}月は#{days}日間あります" 模範解説 2月以外は各月の日数が決まっています。したがって、まずは以下のようにコードを記述しましょう。 def get_days(year, month) month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] # 各月の日数は配列で管理 return month_days[month - 1] end puts "年を入力してください:" year = gets.to_i puts "月を入力してください:" month = gets.to_i days = get_days(year, month) puts "#{year}年#{month}月は#{days}日間あります" そして、2月の時だけ条件式を用いて出力をします。 def get_days(year, month) month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] if month == 2 # 2月のとき # 閏年のときはdaysに29を代入 # それ以外はdaysに28を代入 else days = month_days[month - 1] end return days end puts "年を入力してください:" year = gets.to_i puts "月を入力してください:" month = gets.to_i days = get_days(year, month) puts "#{year}年#{month}月は#{days}日間あります" 閏年か判定します。閏年の条件は3つありましたが、以下のようにまとめることができます。 ① その年が4で割り切れること ② ただし、年が100で割り切れて400で割り切れない場合は閏年ではない したがって、以下のような条件分岐を設けることができます。 year = 指定の年 if year % 4 == 0 # 年が4で割り切れること if year % 100 == 0 && year % 400 != 0 # 年が100で割り切れて400で割り切れない場合 # 閏年でない else # 閏年 end end 上記のロジックを反映しましょう。 def get_days(year, month) month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] if month == 2 if year % 4 == 0 # 年が4で割り切れること if year % 100 == 0 && year % 400 != 0 # 年が100で割り切れて400で割り切れない場合 days = 28 # 閏年でない else days = 29 # 閏年 end else days = 28 # 閏年でない end else days = month_days[month - 1] end return days end puts "年を入力してください:" year = gets.to_i puts "月を入力してください:" month = gets.to_i days = get_days(year, month) puts "#{year}年#{month}月は#{days}日間あります" 感じたこと 閏年の場合の処理が少し複雑で回答することができなかった。 if文の入れ子?にまだ慣れていないと感じたのでif文の復習を行い理解を深めていきたい。 また西暦や年度系の問題はよく出てくると思うので処理方法を覚えていきたいと思った。 アドバイスやご指摘等ございましたらぜひお願いします!!! 以上
- 投稿日:2022-03-23T10:10:43+09:00
本日の基礎練習問題①(22/3/23)
問題 以下の要件を満たすxyz_thereメソッドを実装しましょう。 任意の文字列から連続する文字列"xyz"を探し、その直前にピリオド(.)がない場合はTrueを出力する 任意の文字列から連続する文字列"xyz"を探し、その直前にピリオド(.)がある場合はFalseを出力する 上記2つの条件に当てはまらない場合はFalseを出力する 雛形. def xyz_there(str) # 処理を記述 end # 呼び出し例 xyz_there('abcxyz') 出力例 xyz_there('abcxyz') → True xyz_there('abc.xyz') → False xyz_there('xyz.abc') → True xyz_there('azbycx') → False xyz_there('a.zbycx') → False 私の回答 ruby. def xyz_there(str) if str.include?(".xyz") puts "False" elsif str.include?("xyz") puts "True" else puts "False" end end # 呼び出し例 xyz_there('abcxyz') 解説 まずif文でinclude?メソッドを使い".xyz"がstrに含まれているかどうか判断します。 次に"xyz"を同じように判断します。 判断する順番に気をつけて処理を記述しました。 模範回答 ruby. def xyz_there(str) if str.include?(".xyz") puts "False" elsif str.include?("xyz") puts "True" else puts "False" end end # 呼び出し例 xyz_there('abcxyz') 模範解説 include?を用いて、対象となる文字列が含まれているかどうかを、各条件式で判別しています。 ポイントとしては、str.include?(".xyz")の条件式をstr.include?("xyz")より前に設置することです。 例えば"abc.xyz"という文字列の場合は、str.include?("xyz")においてもtrueとなってしまうため、先に".xyz"を含むかどうかを判別する必要があります。 感じたこと 今回使用したinclude?メソッドは一度学習したことがあったので覚えていた。 文字列判断の順番で処理結果がおかしくなるので、if文設定の場合はしっかりと順番を考えて記述していきたい。 アドバイスやご指摘等ございましたらぜひお願いします!! 以上