- 投稿日:2022-03-23T22:44:14+09:00
Dockerで、Rails 6系とMySQL8の開発環境の構築(OS : mac)
Rails 6系とMySQL8 で開発環境を作る docker-compose upでrails serverを起動できるようにすること ホストのファイルシステムとコンテナのファイルシステムを同期させること 勉強中の初学者です。誤りなどのご指摘いただければ嬉しいです! 流れ 各種ファイルの作成 イメージのBuild rails new でアプリ作成 -> Gemfileが上書きされる 新しいGemfileでイメージの再ビルド database.yml の設定,DB生成 コンテナ起動&サーバ起動 確認 各種ファイル作成 下記を作成します。 dockerfile docker-compose.yml Gemfile Gemfile.lock $ mkdir docker_rail_mysql $ cd docker_rail_mysql dockerfile作成 FROM ruby:2.6 RUN apt-get update && apt-get install -y \ build-essential \ libpq-dev \ nodejs RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - && apt-get install -y nodejs RUN npm install --global yarn WORKDIR /docker_rail_mysql COPY Gemfile Gemfile.lock /docker_rail_mysql/ RUN bundle install RUN rm -f tmp/pids/server.pid CMD ["rails","s","-b","0.0.0.0"] docker-compose.yml作成 docker-compose.yml version: '3' volumes: db-data: services: web: build: . ports: - '3000:3000' container_name: 'web' volumes: - '.:/docker_rail_mysql' tty: true stdin_open: true depends_on: - db db: image: mysql:8 container_name: 'db_mysql' volumes: - 'db-data:/var/lib/mysql' environment: - 'MYSQL_ROOT_PASSWORD=password' - 'TZ=Asia/Tokyo' ports: - 3306:3306 Gemfile作成 source 'https://rubygems.org' gem 'rails', '~>6' Gemfile.lockを作成 Gemfile.lockの内容はありません。 コマンド まずは、docker-composeで、imageとcontainerを作ります。 $ docker-compose run web bash コンテナー上でのコマンド rails newコマンドで、Gemfileは上書きされます。 $ rails new . -force --database=mysql --webpacker $ exit config/database.yml 変更 default: &default adapter: mysql2 encoding: utf8mb4 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: <%= ENV.fetch("MYSQL_USERNAME", "root") %> password: <%= ENV.fetch("MYSQL_PASSWORD", "password") %> host: <%= ENV.fetch("MYSQL_HOST", "db") %> development: <<: *default database: docker_development test: <<: *default database: docker_test production: <<: *default database: docker_homework_production username: root password: <%= ENV['DOCKER_DATABASE_PASSWORD'] %> コマンド 貧弱なGemfileで作られた最初のimageは削除して、 新しいGemfileでもう1回buildしましょう。 $ docker-compose down $ docker-compose up --build -d $ docker-compose run web rails db:create $ docker-compose up host側のWEBブラウザで確認 http://localhost:3000/ host側のdocker-homeworkフォルダの内容をgithubにpushしておくと、 下記のコマンドで、どこでも同じ環境構築ができるようになります。 $ git clone -b [ブランチ名] https://github.com/〜 $ cd docker $ docker-compose run web rails db:create $ docker-compose up 最後に これで構築完了です。 かなり時間がかかりましたが、無数のエラーを出しながら、dockerにもっと慣れてきたと思います。 参考になったら嬉しいです。 参考にしたサイト https://qiita.com/nsy_13/items/9fbc929f173984c30b5d https://qiita.com/A-Kira/items/f401aea261693c395966 https://qiita.com/ape/items/885e97ed1978f972d266 https://zenn.dev/yuma_ito_bd/scraps/1adb89dfe0661c
- 投稿日:2022-03-23T22:23:41+09:00
rails_tutorial chapter7 part2 <忘備録>
form_withは重要なパートですね。 まず下処理として、以下の記述 users_controller.rb ・ ・ ・ def new @user = User.new end view側で以下を記述 app/views/users/new.html.erb <h1>Sign up</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= form_with(model: @user, local: true) do |f| %> <%= f.label :name %> <%= f.text_field :name %> <%= f.label :email %> <%= f.email_field :email %> <%= f.label :password %> <%= f.password_field :password %> <%= f.label :password_confirmation, "Confirmation" %> <%= f.password_field :password_confirmation %> <%= f.submit "Create my account", class: "btn btn-primary" %> <% end %> </div> </div> これらを分解して理解していくと、 まず以下の部分ですが、 form_withはデフォルトで“remote” XHR requestを送るため、local: trueと設定することで同期通信とする。 以前、非同期通信の実装でデフォルトがremoteとなっていることを知らず、つまづいたので忘れないようメモ。 <%= form_with(model: @user, local: true) do |f| %> . . . <% end %> そして、f.formなどありますが、これらをhtmlでみてみると以下の通りです。 <form accept-charset="UTF-8" action="/users" class="new_user" id="new_user" method="post"> <input name="authenticity_token" type="hidden" value="NNb6+J/j46LcrgYUC60wQ2titMuJQ5lLqyAbnbAUkdo=" /> <label for="user_name">Name</label> <input id="user_name" name="user[name]" type="text" /> <label for="user_email">Email</label> <input id="user_email" name="user[email]" type="email" /> <label for="user_password">Password</label> <input id="user_password" name="user[password]" type="password" /> <label for="user_password_confirmation">Confirmation</label> <input id="user_password_confirmation" name="user[password_confirmation]" type="password" /> <input class="btn btn-primary" name="commit" type="submit" value="Create my account" /> </form> 例えば、以下のコードは <%= f.label :name %> <%= f.text_field :name %> <%= f.label :email %> <%= f.email_field :email %> <%= f.label :password %> <%= f.password_field :password %> <label for="user_name">Name</label> <input id="user_name" name="user[name]" type="text" /> <label for="user_email">Email</label> <input id="user_email" name="user[email]" type="email" /> <label for="user_password">Password</label> <input id="user_password" name="user[password]" type="password" /> 例えば、この中の"user[email]"という値は、userハッシュの:emailキーの値(params[:email])と一致します。 @user = User.new(params[:user]) 実際にはこのようなコードとほぼ同じである、ということです。 @user = User.new(name: "Foo Bar", email: "foo@invalid", password: "foo", password_confirmation: "bar") また、このような記述がありました。 次に重要な要素は、formタグ自身です。Railsはformタグを作成するときに@userオブジェクトを使います。すべてのRubyオブジェクトは自分のクラスを知っている(4.4.1)ので、Railsは@userのクラスがUserであることを認識します。また、@userは新しいユーザーなので、 Railsはpostメソッドを使ってフォームを構築すべきだと判断します。 要は、railsが自分のクラスや@userの中身を踏まえて、メソッドを決定してくれるとのことです。賢いですね。 つまり、@userがnewアクションでは空なので、updateアクションを実行してくれます。賢いですね。(2回目) したがって、htmlの記述は以下の通りです。 action="/users"とポストメソッドでnewアクションが実行されるというわけですね。 <form action="/users" class="new_user" id="new_user" method="post"> そして、POSTリクエストはcreateアクションに送られます。ここで、createアクションでフォーム送信を受け取り、User.newを使って新しいユーザーオブジェクトを作成し、ユーザーを保存(または保存に失敗)します。 なので、newアクションとupdateアクションはセットで必要となります。 これで問題なさそうですが、実は問題があります。 それは以下の書き方にはセキュリティ上の問題があります。 @user = User.new(params[:id]) 回避するためには、ストロングパラメーターが必要です。 private def user_params params.require(:user).permit(:name, :email, :password, :password_confirmation) end flhashの表示 以下の説明が分かりやすい。 def create @user = User.new(user_params) if @user.save flash[:success] = "Welcome to the Sample App!" redirect_to @user else render 'new' end end application.html.erb <% flash.each do |message_type, message| %> <div class="alert alert-<%= message_type %>"><%= message %></div> <% end %> 適用するCSSクラスをメッセージの種類によって変更するようにしています。これにより、例えば:successキーのメッセージが表示される場合、適用されるCSSクラスは次のようになります。 alert-<%= message_type %> alert-success このとき、:successキーはシンボルでしたが、テンプレート内に反映させる際に埋め込みRubyが自動的に"success"という文字列に変換している点に注意してください。この性質を利用することで、キーの内容によって異なったCSSクラスを適用させることができ、メッセージの種類によってスタイルを動的に変更させることができます。例えば、8.1.4ではflash[:danger]を使ってログインに失敗したことを表すメッセージを表示します11 (実際、既にalert-dangerというCSSクラスを使って、リスト 7.21のエラーメッセージのスタイルをdivタグで指定しています)。Bootstrap CSSは、このようなflashのクラス用に4つのスタイルを持っています(success、info、warning、danger)。また、本書のサンプルアプリケーションでは、これらの全てのスタイルを場合に応じて使っていきます(例えば11.2ではinfoを、8.1.4ではdangerを使います)。 テンプレート内にflashのメッセージが差し込まれるので、次のようなコードは、 flash[:success] = "Welcome to the Sample App!" 最終的には次のようなHTMLになります。 <div class="alert alert-success">Welcome to the Sample App!</div> 第7章は、以下の2点を理解できたら大丈夫かなと思います。 ・form_withを使って、newアクション、createアクションを実装する ・flashの使い方 gravatorも出てきましたが、ある程度知っていればよさそう。
- 投稿日: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 %> app/assets/stylesheets/application.css *= require simple_calendar #追記 *= require_tree . *= require_self */ 実装確認 rails sでローカルサーバーを起動。 ユーザー登録を経て、表示されたのが、以下のページ。 デザインは味気ないが、とりあえずは成功。 あとはこれに新規スケジュールの作成機能などを追加していくつもりだが、それはまた次回に。
- 投稿日:2022-03-23T17:15:08+09:00
WebブラウザはDELETEリクエストを発行できない
ハマったこと 下記のようにdeleteリクエストを指定して送信したがGETメソッドになってしまう。 _header.html.erb <%= link_to "Log out", logout_path, method: :delete %> 出たエラーメッセージ Routing Error No route matches [GET] "/logout" 原因 どうやらブラウザからDELETEリクエストを送ることができないみたいです。 参考にした記事 ← 理由も乗ってます。 対策 link_toメソッドではなく、button_toメソッドで解消できるみたいです。 もっといいやり方ないんだろうか、、、 ruby _header.html.erb <%= link_to "Log out", logout_path, method: :delete %> # 削除した <%= button_to "Log out", logout_path, method: :delete %> # 追加した button_toタグを使用すると、生成されるHTMLによりデザインが変わってしまいます。 jsを正しく、読み込めばlink_toメソッドのままdeleteリクエストを送れるみたいです。
- 投稿日: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-23T14:14:59+09:00
rails_tutorial chapter7 part1 <忘備録>
開発環境でデバッグを表示する /users/1 のURLを有効にするために、routesファイル(config/routes.rb)に次の1行を追加します。 routes.rb resources :users 以下のroutesが用意される。 1 2 3 4 5 HTTPリクエスト URL アクション 名前付きルート 用途 GET /users index users_path すべてのユーザーを一覧するページ GET /users/1 show user_path(user) 特定のユーザーを表示するページ GET /users/new new new_user_path ユーザーを新規作成するページ(ユーザー登録) POST /users create users_path ユーザーを作成するアクション GET /users/1/edit edit edit_user_path(user) id=1のユーザーを編集するページ PATCH /users/1 update user_path(user) ユーザーを更新するアクション DELETE /users/1 destroy user_path(user) ユーザーを削除するアクション viewでインスタンスを取り出せるように記述。 users_controller.rb class UsersController < ApplicationController def show @user = User.find(params[:id]) debugger end def new end end 'debugger'を記述すると、アプリケーションのdebuggerが呼び出された瞬間の状態を確認することができる。 Processing by UsersController#show as HTML Parameters: {"id"=>"1"} (0.1ms) SELECT sqlite_version(*) ↳ app/controllers/users_controller.rb:3:in `show' User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] ↳ app/controllers/users_controller.rb:3:in `show' Return value is: nil [1, 10] in /Users/fuko/original_app/sns_app/app/controllers/users_controller.rb 1: class UsersController < ApplicationController 2: def show 3: @user = User.find(params[:id]) 4: debugger => 5: end 6: 7: def new 8: end 9: 10: end (byebug)
- 投稿日:2022-03-23T12:10:02+09:00
[Rails] Address already in use が発生してrails sができなかった
前提 ・Dockerで環境構築をしている ・Rails 6.1.5 エラーの内容 `initialize': Address already in use - bind(2) for "0.0.0.0" port 3000 (Errno::EADDRINUSE) 原因 既に3000Portが使われているため同じポート番号は使えないよというエラー 解決策 3000Portを使用しているプロセスを終了する ①3000Portを使用しているプロセスの番号を調べる # 現在、実行しているプロセスを表示するコマンド $ ps -x PID TTY STAT TIME COMMAND 1 pts/0 Ss+ 0:00 irb 8 pts/1 Ss+ 0:00 /bin/bash 15 pts/2 Ss+ 0:00 /bin/bash 32 pts/1 Z+ 0:00 [ruby] <defunct> 64 pts/3 Ss+ 0:00 /bin/bash 919 pts/4 Ss 0:00 /bin/bash 1559 pts/1 Sl+ 0:00 spring server | rails-app | started 72 hours ago 2288 pts/4 Sl+ 0:20 puma 5.6.2 (tcp://0.0.0.0:3000) [rails-app] 2689 ? Ssl 13:18 spring app | rails-app | started 45 hours ago | test mode 2690 ? Ssl 13:19 spring app | rails-app | started 45 hours ago | development mode 2728 pts/5 Ss 0:00 /bin/bash 3115 pts/5 R+ 0:00 ps -x 後から気づいたがプロセス多いと探すの大変だからgrepで検索する方が良さそう $ ps -x | grep 0.0.0.0:3000 2288 pts/4 Sl+ 0:01 puma 3.12.6 (tcp://0.0.0.0:3000) [rails_app] 3168 pts/5 S+ 0:00 grep 0.0.0.0:3000 ②killコマンドでプロセスを終了する # killコマンドにはPIDの番号を渡す $ kill 2288 もう一度ps -xで確認するとプロセスが消えているはずだが、消えていなければ下記のコマンドで強制終了する #-9オプションで強制終了 $ kill -9 2288 プロセスが終了していることを確認したらrails sすれば起動できるはず。
- 投稿日:2022-03-23T11:56:03+09:00
【rails】DBアクセス時のログにLoadと表示されるメソッドと、表示されないメソッドの違いとは
DBアクセス時に、モデル名 Loadと表示されるメソッドの例 User.all => User Load (4.6ms) SELECT "users".* FROM "users" User.first User Load (5.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1 User.find(1) User Load (1.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT 1 DBアクセス時に、モデル名 Loadとログに表示されないメソッドの例 User.count (2.0ms) SELECT COUNT(*) FROM "users" User.pluck("email") (1.7ms) SELECT "users"."email" FROM "users" 上記の違いは単純で、モデルオブジェクトの構築の有無です。 モデル名 Loadのようにログにでるものは、モデルオブジェクトを作成した上で、メモリにロードしているんですね。 pluckなどもメモリにロードしてるんですが、モデルオブジェクトの構築を行っていないのでその分高速です。 pluck必要なフィールドだけを読み込むことで、メモリの使用量も削減できるとの事で これが圧倒的に早い理由らしい
- 投稿日:2022-03-23T11:52:14+09:00
イマドキのオススメ gem on Rails
はじめに ※ この文章は永遠のベータ版です。 ググったり、Qiita / Zenn の記事を見たり、していると、以前は利用者が多く、 開発もアクティブだったものの、今は最早メンテナンスすらされて いない、RailsのメジャーVer UP への追従の怪しい gem が 使われたりしている、ので、イマドキの オススメgem を まとめてみます。 Railsに限らず、アプリを開発する際には、利用するライブラリの 開発・メンテナンスがアクティブかどうか、継続的な開発・メンテナンスが 期待できるか、への注意が必要です。 非アクティブor開発・メンテナンスの継続性に不安がある なら、そのライブラリの 採用を見合わせる or 同等の機能を提供する他のライブラリに移行する、のが 無難です。 ファイル・アップローダ 名前 オススメ度 アクティブ 備考 Shrine ◎ ✔︎ CarrierWave ○ ✔︎ ActiveStorage △ ✔︎ Rails公式 Refile ✕ ✖️ PRほったらかし Paperclip ✕ ✖️ 公式に開発終了 Shrine CarrierWave・Refileのいいとこ取りな後発アップローダ。オススメ。 CarrierWave・Refileからの移行パスあり。 CarrierWave 老舗アップローダ。設計に(やや?)古い感あり。老舗ゆえの安心感?で、広く 利用されている。 ActiveStorage Railsビルトインのアップローダ。 (一時)ファイルのキャッシュ機能がないので、アップロードしたファイルの DBでの永続化前の事前プレビューを行いたいケースでは採用できない 弱点がある。 Refile CarrierWave の原作者が、一時期、後継としてスクラッチから開発していたアップローダ。 ActiveStorageのリリース(CarrierWaveからの移行パスが用意されておらず移行が 進まなかったことも?)により?原作者が意欲を失い、開発が停滞。 現在、メンテナンスを引き継いだ?メンテナも関心を失い、放置中。 Rails7以降への対応は期待できない。 Paperclip CarrierWave と双璧をなしていたアップローダ。 ActiveStorage のリリースに伴い、開発終了。 https://thoughtbot.com/blog/closing-the-trombone JSONシリアライザ 名前 オススメ度 アクティブ 備考 JSON:API Serialization Library ○ ✔︎ JSON:API対応 Alba ○ ✔︎ Jb △ ✔︎ Jbuilder △ ✔︎ BluePrinter ✕ ✖️ ActiveModelSerializers ✕ ✖️ JSON:API Serialization Library JSON:APIに対応し、高速なシリアライズが特徴の、JSONシリアライザ。 Netflixの開発していた Fast JSON API が開発・メンテナンスを 終了したので、有志により fork され、開発・メンテナンスが継続中。 https://github.com/Netflix/fast_jsonapi Alba 後発の和製JSONシリアライザ、高速なシリアライズが特徴。 https://okuramasafumi.hatenablog.jp/entry/2021/12/03/144055 https://zenn.dev/okuramasafumi/articles/53fcee0f946ac3 Jb 和製JSONシリアライザ、高速なシリアライズが特徴。 jbuilderのオルタナティブで、複雑なJSONの組み立てには不向き。 jbuilderよりシンプルに記述できる。 Jbuilder RailsビルトインのJSONシリアライザ。低速。 記述に用いるDSLにクセがある。 複雑なJSONの組み立てには不向き。 BluePrinter JSONシリアライザ、クセがある、DSLにJSONの構造とは直接 関係しない view がある、等。 開発・メンテナンス、アナウンスはないものの、終了している。 (PRは放置ぎみ。メンテナによりコメントされている場合あり。) ActiveModelSerializers (AMS) 老舗のJSONシリアライザ。コードベースが大きくて複雑なため、低速。 (Jbuilderよりは高速に動作。) 開発・メンテナンス、アナウンスはないものの、終了している。
- 投稿日: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を整備していければ良さそうなので、そのためにもまずは開発者・利用者を増やしつつ、便利なものにしていければいいんではないかと思います。興味のある方はぜひ試してみてはいかがでしょうか。 頑張っていきましょう。