- 投稿日:2021-12-03T23:35:26+09:00
【再現性皆無】docker開発環境上での、ブラウザテストの設定
bundle exec rails generate rspec:install bin/rails g factory_bot:model mandalart bin/rails g rspec:system mandalart bin/rails g rspec:model mandalart いままでこれらのコマンドを入力してきました。 spec/rails_helper.rb + require 'capybara/rspec' spec/rails_helper.rb require 'capybara/rspec' spec/support/capybara.rb equire 'selenium-webdriver' require 'capybara/rspec' Capybara.configure do |config| config.default_driver = :chrome config.javascript_driver = :chrome config.run_server = true config.default_selector = :css config.default_max_wait_time = 5 config.ignore_hidden_elements = true config.save_path = Dir.pwd config.automatic_label_click = false end Capybara.register_driver :chrome do |app| options = Selenium::WebDriver::Chrome::Options.new options.add_argument('disable-notifications') options.add_argument('disable-translate') options.add_argument('disable-extensions') options.add_argument('disable-infobars') options.add_argument('window-size=1280,960') # ブラウザーを起動する Capybara::Selenium::Driver.new( app, browser: :chrome, options: options ) end spec/rails_helper # This file is copied to spec/ when you run 'rails generate rspec:install' # Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f } RSpec.configure do |config| config.before(:each, type: :system) do driven_by :rack_test end config.before(:each, type: :system, js: true) do driven_by :selenium_chrome_headless end end require 'spec_helper' ENV['RAILS_ENV'] ||= 'test' require File.expand_path('../config/environment', __dir__) # Prevent database truncation if the environment is production abort('The Rails environment is running in production mode!') if Rails.env.production? require 'rspec/rails' # Add additional requires below this line. Rails is not loaded until this point! # Requires supporting ruby files with custom matchers and macros, etc, in # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are # run as spec files by default. This means that files in spec/support that end # in _spec.rb will both be required and run as specs, causing the specs to be # run twice. It is recommended that you do not name files matching this glob to # end with _spec.rb. You can configure this pattern with the --pattern # option on the command line or in ~/.rspec, .rspec or `.rspec-local`. # # The following line is provided for convenience purposes. It has the downside # of increasing the boot-up time by auto-requiring all files in the support # directory. Alternatively, in the individual `*_spec.rb` files, manually # require only the support files necessary. # # Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f } # Checks for pending migrations and applies them before tests are run. # If you are not using ActiveRecord, you can remove these lines. begin ActiveRecord::Migration.maintain_test_schema! rescue ActiveRecord::PendingMigrationError => e puts e.to_s.strip exit 1 end RSpec.configure do |config| # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures config.fixture_path = "#{::Rails.root}/spec/fixtures" # If you're not using ActiveRecord, or you'd prefer not to run each of your # examples within a transaction, remove the following line or assign false # instead of true. config.use_transactional_fixtures = true # You can uncomment this line to turn off ActiveRecord support entirely. # config.use_active_record = false # RSpec Rails can automatically mix in different behaviours to your tests # based on their file location, for example enabling you to call `get` and # `post` in specs under `spec/controllers`. # # You can disable this behaviour by removing the line below, and instead # explicitly tag your specs with their type, e.g.: # # RSpec.describe UsersController, type: :controller do # # ... # end # # The different available types are documented in the features, such as in # https://relishapp.com/rspec/rspec-rails/docs config.infer_spec_type_from_file_location! # Filter lines from Rails gems in backtraces. config.filter_rails_from_backtrace! # arbitrary gems may also be filtered via: # config.filter_gems_from_backtrace("gem name") end docker-compose.yml version: '3' services: db: image: mysql:8.0.21 cap_add: - SYS_NICE # コンテナにLinux機能を追加するオプションのようです。SYS_NICEは、プロセスの優先度(nice値)をあげます。 environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} MYSQL_HOST: db ports: - '3306:3306' volumes: - mysql-data:/var/lib/mysql command: --default-authentication-plugin=mysql_native_password # 認証方式を8系以前のものにする web: &web build: . command: ./bin/rails s -b 0 stdin_open: true tty: true # この2文を追加でコンテナ内の標準入出力をローカルマシンのターミナルにアタッチする準備が整います。 volumes: - .:/ideaFrameworks ports: - "3000:3000" depends_on: - db - selenium_chrome environment: WEBPACKER_DEV_SERVER_HOST: webpacker MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} RAILS_MASTER_KEY: ${RAILS_MASTER_KEY} MYSQL_HOST: db # selenium_chrome を使うために以下の行を追加 SELENIUM_DRIVER_URL: http://selenium_chrome:4444/wd/hub" selenium_chrome: image: selenium/standalone-chrome-debug logging: driver: none ports: - 4444:4444 webpacker: <<: *web command: ./bin/webpack-dev-server environment: WEBPACKER_DEV_SERVER_HOST: 0.0.0.0 ports: - "3035:3035" volumes: mysql-data: driver: local vendor_bundle: driver: local FROM ruby:2.6.5 ## nodejsとyarnはwebpackをインストールする際に必要 # yarnパッケージ管理ツールをインストール RUN curl http://deb.debian.org/debian/dists/buster/main/binary-amd64/by-hash/SHA256/935deda18d5bdc25fb1813d0ec99b6e0e32a084b203e518af0cf7dc79ee8ebda | head RUN apt-get update && apt-get install -y curl apt-transport-https wget && \ 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 && \ apt-get update && apt-get install -y yarn && apt-get install -y graphviz # chromeの追加 RUN apt-get update && apt-get install -y unzip && \ CHROME_DRIVER_VERSION=`curl -sS chromedriver.storage.googleapis.com/LATEST_RELEASE` && \ wget -N http://chromedriver.storage.googleapis.com/$CHROME_DRIVER_VERSION/chromedriver_linux64.zip -P ~/ && \ unzip ~/chromedriver_linux64.zip -d ~/ && \ rm ~/chromedriver_linux64.zip && \ chown root:root ~/chromedriver && \ chmod 755 ~/chromedriver && \ mv ~/chromedriver /usr/bin/chromedriver && \ sh -c 'wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -' && \ sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list' && \ apt-get update && apt-get install -y google-chrome-stable RUN /bin/sh -c /bin/sh -c bundle update --bundler RUN gem install bundler:2.1.4 RUN mkdir /ideaFrameworks WORKDIR /ideaFrameworks COPY Gemfile /ideaFrameworks/Gemfile COPY Gemfile.lock /ideaFrameworks/Gemfile.lock RUN bundle install RUN bundle update rails RUN bundle update RUN bundle update capybara selenium-webdriver #RUN bundle update nokogiri marcel mimemagic RUN bundle install COPY . /ideaFrameworks RUN yarn install --check-files RUN bundle install RUN bundle exec rails webpacker:compile # Add a script to be executed every time the container starts. COPY entrypoint.sh /usr/bin/ RUN chmod +x /usr/bin/entrypoint.sh ENTRYPOINT ["entrypoint.sh"] EXPOSE 3000 # Start the main process. CMD ["rails", "server", "-b", "0.0.0.0"] 僕の、Dockerfileとdocker-compose.ymlの設定では動きました。
- 投稿日:2021-12-03T22:57:51+09:00
【Rails】ルーティングまとめ
背景 学び始めでのルーティングをする際、getを使わなきゃだとか、resource、ヘルパーメソッドって何だったか…?となり毎回検索しては忘れてしまうのでここにまとめを。 ルーティングのやり方 ◆個別設定 1行ずつ設定する方法。1行1行手入力しないといけないので少し面倒。 一括設定する方法もある。 get 'URLに追加' to: 'コントローラのアクション' 例: get 'index' to: 'posts#index' これをURLにすると→ http://localhost:3000/index ◆一括設定 resourcesを使う。 これらのメソッドは、railsで定義されている7つのアクションを自動で生成してれる。 ▼7つのアクションとは アクション名 役割 index リソースの一覧を表示させる show リソースの詳細を表示させる new 投稿フォームを表示させる create リソースを追加させる edit 更新フォームを表示させる update リソースを更新させる destroy リソースを削除する resources :コントローラー名,:コントローラー名... コンソールにてrails routesコマンドで、ルーティングを確認できる。 また、アクションを指定したいときには下記のように記述。 resources :コントローラー名,only:[:index, :new] resouce(最後に"s"が無い)メソッドでも出来るが、コントローラの7つのアクションに対して、indexとid付きのパスが生成され無いため、show, editアクションの実行に、idが必要ない場合に有効。 HTTPメソッドについて ◆HTTPメソッドとは HTTPメソッドとは、「クライアントがサーバーにしてほしいことを依頼するための手段。 ▼比較的多く使われるメソッド メソッド 役割 get データの取得 post データの送信(主に新規作成) put データの送信(主に既存データの更新) delete データの削除 getとpostの違い get 主にサーバーからブラウザに情報を返す時に使われる。単にウェブページを参照する際にはこのメソッドが利用される。 post ブラウザからサーバーに情報を送信するときに使うメソッドで、入力フォームのデータをブラウザからサーバーに送信するときなどに使われている。 getメソッドとは異なり、リクエスト内容をHTTP通信のbody部に含めることで、URLにその内容が表れない通信方式。 代わりに、リクエストボディ内に内容が含まれているため、URLを直接入力するだけでは同じ通信を再現することはできない。 _pathと_urlの違い _path ・相対パス ・redirect_to以外で使用 _url ・絶対パス ・redirect_toの時にセットで使用する。(HTTPの仕様上、リダイレクトのときに完全なURLが求められるため 相対パスと絶対パスの違い 相対パス URLでページを指定して、目的地(情報)がどこにあるのかを確実に伝える。 たとえば実際に道案内をするときに、目的地の住所(URL)を教えるのが絶対パスの伝え方。 絶対パス 今いる場所(階層)を基準にして、目的地(情報)がどこにあるのかを伝える。 ある地点から道を曲がって道路沿いのつきあたりを右…というように、今いる場所から目的地までの経路を案内するのが相対パスの伝え方。 参考記事 【Rails入門】ルーティング (config/routes.rb)の書き方を説明! 【Rails】 resourcesメソッドを使ってルーティングを定義しよう! resourcesとresourceの違いについて! Web開発でよく使う4つのHTTPメソッド【REST API】 GETメソッドとPOSTメソッドの違いとSEO面からの使い分け _pathと_urlの違いについて調べてみた これだけは覚えておきたい!絶対パスと相対パスの違いとは【初心者向け】
- 投稿日:2021-12-03T22:48:09+09:00
【テーブル?】railsのモデルやデータベースが何をしているのか分からない人へ【migration?】
前説 DMM WebCampのAdvent Calendar2021の4日目を担当させていただきました、日置と申します。 Railsのモデルやデータベース関係で何をしているのか分からず困っている方向けに、railsにおけるデータの扱い方について説明していきたいと思います。よろしくお願いします。 この記事の最終目標 テーブルの意味を理解する migrationファイルル/schemaファイルと、rails db:~コマンドの役割を理解する Modelとは何かを理解する Modelの使い方を覚える 対象者 Rubyの以下の基礎知識を把握している方 文字列の結合や数値の計算 変数 メソッドと引数(参考) RailsのModelが何をしているのか分からない方 rails db:migrateが何をしているのか分からない方 目次 テーブルの意味を理解する RailsとDB DBのデータの形式 migrationファイル/schemaファイルとrails db:~コマンドの役割を覚える migrationファイル/schemaファイルとは何か テーブルの定義 マイグレーションとrails db:~コマンド Modelとは何かを理解する Railsのデータの形式 Modelの正体 Modelの使い方を覚える DBからデータを取り出す 取り出したデータを扱う データをDBに追加する テーブルの意味を理解する この項目の目標 DBとは何かを理解する テーブル、カラム、レコード、値の意味を理解する RailsとDB テーブルを理解する上でまず念頭において欲しいのは、Railsとデータベース(以下DB)は、全くの別のものという事です。 「Rails」とはサイトを表示させたり、データを処理するためのアプリケーション 「DB」とは、データを長期的に保存しておくためのアプリケーション つまり皆様は、「Rails」+「DB」でアプリケーションを作成しているわけですね。 アプリ 言語 データの形態 できること Rails Ruby インスタンス ブラウザにHTMLを表示したり、計算処理をする DB SQL テーブル データを保存する このように使っている言葉も仕組みも全く違うものなので、DBの話を読むときは、Railsと切り離して考えていただけると理解がしやすいです。 DBのデータの形式 データの形式 データベースのデータは、エクセルの表の様に保存されています。この表自体がテーブルです。 下は、学校の生徒の表の例です。 この表は、テーブル、カラム、レコード、値という4つの要素で出来ています。 次からこの4つを順に説明していきます。 テーブル 先ほども書きましたが、テーブルとはこの表自体の事です。1つの事柄(生徒、先生、教室等)につき、1テーブルを作成します。 通常、テーブルは英単語の複数系で定義します。 カラム カラムとは情報の属性の事です。学年、名前等、情報が何を指しているのかを示す物。 レコード、値 値とはカラムの中身(1年生、田中太郎等)の事です。 レコードとは、1データ分のカラムの値をまとめて一つの情報にしたものです。一人の生徒の情報(学年番号1の、学年が1年生の、名前が田中太郎の生徒)、一人の先生の情報、一つの教室の情報等。 まとめ 下記の事が分かっていれば、今回の項目の理解は完璧です。 RailsとDBは別のアプリケーション テーブルを構成する4つの要素 migrationファイルとrails db:~コマンドの役割を覚える migrationファイルとは何か migrationファイルとは、Railsから、DBのテーブルを定義(作成)したり、既存のテーブルの定義を変更したりするためのファイルです。 後に説明するマイグレーションを行う際、このmigrationファイルの通りにテーブルが定義/変更されます。 schemaファイルとは、DBの状態を見やすくまとめたファイルです。 そしてここで重要なのが、migrationファイルの書き方や、schemaファイルの見方はわざわざ覚える必要はありません! 読み書きするたびに、分からなくなったら調べれば良いからです。この記事でもmigrationファイルの作成については説明しません。 次は、その代わりに理解していただきたい、テーブルを定義するとは具体的に何をする事なのかについて説明していきます。 テーブルの定義 テーブルを定義には、2つの事を決めます テーブル名 カラム テーブル名はそのまんま、何についての情報なのかを英単語複数系で決めればokです。 カラムの定義では以下の事を決めます カラムの名前 カラムのデータ型 カラムの制約 カラムの名前 スネークケース(大文字を使わない記述方式)で記述しましょう。他にも暗黙の了解のような命名規則があります。 カラムのデータ型 文字や数値等、値の種類の事です。一つのカラムには同じデータ型の値しか入りません。 カラムの制約 NULL(空の状態)禁止、デフォルトでは1等のルールです。細かい説明はここでは省略します。初歩の段階では特に気にしなくても問題ありません。 migrationファイルでは、これらを設定しています。 マイグレーションとrails db:~ ターミナルで実行するこのrails db:~系のコマンドは、railsからDBのテーブルの作成や変更(マイグレーション)を行ったり、DB自体をリセットするために行うものです。 このコマンドを理解するには、先にマイグレーションについて知る必要があります(単語は難しそうですが、少しの説明で終わります!)。 マイグレーション migrationファイルは、作成しただけではDBに影響を与えません。 マイグレーション(rails db:migrate)という事を行う事で、migraionファイルの内容がDBに反映されます。 なおテーブル作成時は、migrationファイルに書かれていないidというカラムが自動で追加されます この時、マイグレーションを行ってDBに反映されている状態のmigrationファイルをup状態、まだ反映されていない状態のmigrationファイルをdown状態と言います。 up状態のmigrationファイルは、もう一度マイグレーションを行っても無視されるため、注意が必要です。(つまり、up状態のmigrationファイルを書き換えても意味がないです!) コマンド rails db:~というコマンドは、基本的に以下の3つを覚えておけば大丈夫です! rails db:migrate rails db:migrate:status rails db:migrate:reset rails db:migrate マイグレーションを行います。 rails db:migrate:status 今あるマイグレーションファイルのup, downを確認できます。 rails db:migrate:reset データベースを初期化(DB内のデータを削除、migrationファイルを全てdownに)して、新たにマイグレーションを行います。 migrationファイルがごちゃごちゃしてしまった時、必要なもの以外を消してこちらを実行することで、必要なmigrationファイルだけがある状態からやり直せます。 まとめ 下記の事が分かっていれば、今回の項目の理解は完璧です。 migrationファイルとは、テーブルを定義/変更するためのもの テーブルを定義するとは、テーブル名とカラムの内容を決める事 マイグレーションを行うと、migrationファイルの内容がDBに反映される Modelとは何かを理解する Railsのデータの形式 テーブルを理解するためにDBのデータの形式を覚えたように、Modelを理解するためにはRails(Ruby)のデータの形式を理解する必要があります。 なので、ここからはRubyの話になってきます。 Rubyでは、データをクラスというもので管理しています。 クラスの定義 クラスとは、Rubyのテーブルのようなもので、指定したデータ(生徒、先生など何についてのデータか)の型を定義したものです。 ※この記事では、簡単に説明してしまいますが、いずれ必要な知識なので、気になる方は以下の記事を参考にしてみてください。(その際はこのRailsのデータ形式の項目は読み飛ばしていただいて大丈夫です) オブジェクトとは クラスを作成する時に定義するものの内、今回は以下の3つについて触れていきます。 クラス名 インスタンス変数 メソッド コードの例を載せていますが、コードを覚えたり理解する必要はないので安心してください。 クラス名 1文字目を大文字にした、英単語単数で定義します。(例: Student) class Student end インスタンス変数 テーブルのカラムのようなものです。 テーブルのカラムと違うところは、データ型を決める必要がなく、自由に値を入力できます。 class Student # gradeとnameを定義 def initialize(grade: nil, name: nil) # この@~がカラムのようなもの @grade = grade @name = name end end Railsを勉強していると、controllerからviewにデータを渡すための変数として、インスタンス変数が出てくると思うのですが、あれとは用途が違うので、今回は別物だと考えてください。 メソッド これはテーブルにはないものです。 クラスには、そのクラスに保存する値の種類(インスタンス変数)の他に、そのクラスに関する機能(メソッド)を定義できます。 メソッドには2種類あります。 インスタンスメソッド(後述するインスタンスを対象とした機能) クラスメソッド(クラス自体の機能) class Student def initialize(grade: nil, name: nil) @grade = grade @name = name end # 名前の後に学年を付けた文字列を返すメソッドを定義(インスタンスメソッド) def out_put_data "#{@name} #{@grade}年" end end インスタンス インスタンスとは、定義したクラスに所属しているオブジェクト(データ)の事です。 DBのレコードと似ています。 クラス名.new(値)で生成でき、下記のように表示されます。 #<Student:~ @grade=1, @name="田中太郎"> class Student # gradeとnameを定義 def initialize(grade: nil, name: nil) @grade = grade @name = name end end student = Student.new(grade: 1, name: "田中太郎") # studentという変数に、Studentクラスのインスタンスが入る メソッドの使い方 それぞれ以下の方法で呼び出す インスタンスメソッド: インスタンス.メソッド クラスメソッド: クラス名.メソッド メソッドには2種類あって、それぞれ役割と呼び出し方違うという事が分かればok class Student def initialize(grade: nil, name: nil) @grade = grade @name = name end def out_put_data "#{@name} #{@grade}年" end end student = Student.new(grade: 1, name: "田中太郎") # インスタンスメソッドを使う puts student.out_put_data #=> 田中太郎 1年 Modelの正体 Railsでは、migrationファイルでDBにテーブルを作成できました。 Modelとは、この作成したテーブルに対応したRails(Ruby)のクラスの事です。 1つのテーブルに付き、担当のModelが1つあります。 Modelには、Railsで使っている言葉(Ruby)と、DBで使っている言葉を翻訳して、DBにデータを追加できるメソッドがたくさん用意されています。 まとめ 以下の事を理解していれば問題ありません。 Rubyでは、クラスと言うものを定義し、データをそのインスタンスとして扱う クラスには、インスタンス変数とメソッドを定義できる クラスのメソッドには2種類ある Modelとは、DBのテーブルに対応したRubyのクラス Modelの使い方を覚える いよいよ最後の項目、Modelの扱い方についての説明です。 DBからデータを取り出す DBからデータを取り出して、Railsのインスタンスに変換するには、4種類のModelのクラスメソッドから適切なものを使います。 データを取り出してくる際、DBのレコードをRubyのクラスのインスタンスとして変換してくれます。 メソッド名 データの取り出し方 .all テーブルのすべてのレコードを取得 .where 条件を指定して、それにあうレコードを取得 .find 数値や文字を指定して、それと同じidカラムの値を持つレコードを取得 .find_by 条件を指定して、それにあうレコードから最初の1つを取得 これらはクラスメソッドなのでModel名.メソッド名の形で記述します。 books_controller.rb # BookというModelがある状態 def index @books = Book.all # booksテーブルのレコード全てをBookクラスのデータに変換 end def show @book = Book.find(1) # booksテーブルのidカラムの値が1のレコードを、Bookクラスのデータに変換 end 期日に間に合わなかったので、以下後日追記致します。。。 取り出したデータを扱う 取り出したデータから、インスタンスメソッドを呼び出す(取り出したデータ.メソッド名)で、データの読み書きや削除が行えます メソッド名 処理 .カラム名 カラムの値を取得 .update(編集したいカラム: 値) DBのレコードを書き換える .destroy DBからレコードを削除する books_controller.rb def test # booksテーブルのidカラムの値が1のレコードをBookクラスのインスタンスに変換 book = Book.find(1) # ※booksテーブルにtitleというカラムがある前提 # booksテーブルのid1のレコードのtitleカラムの値を表示 puts book.title # booksテーブルのid1のレコードのtitleカラムの値を'テストデータ'に変更 book.update(title: 'テストデータ') # booksテーブルのid1のレコードを削除 book.destroy end データをDBに追加する DBにレコードを追加するには、先にModelのインスタンスを作成して、そこからsaveメソッドを呼び出します。 books_controller.rb def create # newメソッドで、インスタンス変数@titleの値が'テストデータ'のインスタンスを作成 book = Book.new(title: 'テストデータ') # booksテーブルにtitleカラムの値が'テストデータ', idカラムの値が今のレコード数+1、のレコードを追加 book.save end まとめ 以下の事を理解していれば問題ありません。 Modelのメソッドは、DBとRailsのデータを相互に変換してくれる カラム名メソッドを使うと、そのカラムの値が取得できる update, destroyメソッドで、編集、削除が行える newとsaveを組み合わせることで、DBにレコードを追加できる 最後に 以上でRailsのデータベース関連の説明は終わりです。 少しでも読んでくださった方のRailsを触る上での不明点が少なくなったり、ストレスが減ったりしたら嬉しいです。
- 投稿日:2021-12-03T21:27:59+09:00
[6行メモ] ActiveRecordのeagar_load後のwhere条件をControllerのParam値で動的に変更したい時
これは「「はじめに」の Advent Calendar 2021」3日目の記事です。 Active Record 初心者が今日学んだこと。 result = Foo.where(name: params[:name]) .eager_load(:bars) if params[:bar_name].present? result.where(bars: { name: params[:bar_name] }) end render :json => result ※もっと良い方法があったら教えて下さい。
- 投稿日:2021-12-03T21:14:30+09:00
コードの見方・関わり方が少し変わった一年でした、という話
グロービスにバックエンドエンジニアとして参画している @ymstshinichiro です。 僕は今年の5月から本格的にGLOPLA LMSの開発にジョインしました。 本記事では、そこから今日までの7ヶ月を通して自分に起こった変化について書いてみたいと思います。 参画以前は 僕は約10年飲食の業界にいましたが、約4年前にキャリアチェンジで SIer > Web業界(社員) > Web業界(フリーランス) という流れで現在に至っており、新卒できちんと先輩から教わったとか、スクールで先生についてもらったみたいなことがありませんでした。 なので、基本的に独学でプログラミングを学び現場のコードを見ながら/直しながら覚えていくという感じでやってきており、 「こうあるべき」みたいなのは書籍を読んで得たものが基本 とはいえ現場では現場のルールや状況がある 結果、その時々で対応していくので、背骨のある設計/実装、綺麗で汎用性の高いコード みたいなのがあまり身についてこなかった という実感がありました。 一方で、急なトラブル対応やスピードを求められる仕様変更、古代に練り込まれたコードのメンテ、色んな意味ですごいSQLなど、アンチパターンや決して教科書通りには行かない現場でどう対応していくかみたいなことに関しては多少経験が積めたかなと思っています。 今のチームでは GLOPLA LMSは、プロダクトの試作段階から経験豊富なメンバーが携わっており、開発当初から下記のような設計方針が貫かれています。 要件を定義する段階でWHYを突き詰める. 無駄なものをできる限り作らない Railsの持つ柔軟なコード表現を積極的に活用していく gemも活用&常に新しいものを使う仕組みを回す 冗長になってもいいのでテストケースのコンテキストはできるだけ抜け漏れなく実装する その上で、shared_example等を活用し読みやすく再利用性の高いテストコードを実装する 時間が経過しても、実装の意図が正確に伝わる表現にこだわる 端的にまとめると「場当たり的な実装ではなく、長期にメンテナンスしていけるための設計/実装を意識したコード」を書くということになります。 そのような現場に入って、自分の意識や行動がどう変わったかを書いていきたいと思います。 全員が維持できるコードを書く 「多少保守性を犠牲にしてもリリースを優先する」というコードを書くことは許されません。(余程の事情がない限り) わかっている情報の中で可能な限り未来を予想し、メンテナンスのコストも含め最もベターな選択肢はどこか?というwhyとhowを考えた上で実装します。 これはユニットテストに関しても同じ(というかむしろテストの精度を上げていくことが本題)で、なぜこのテストケースが存在すべきか誰が見てもわかる状態を目指すべきです。 この結果、テストのコンテキストを分割する、コミットを分ける、コミットメッセージと実装を矛盾なく一致させるなど、コード(と、PR)がどう見られるか?どうやったらレビューしやすいか?を意識するようになりました。 こう書くと当たり前のようですが、以前の僕は特にコミットの仕方に関しては本当にできていなかったですね。。。所属した現場でのルールや意識に影響されたのと、仕事以外でのOSS活動などをやってこなかったというのが大きいと思っています。 外のリポジトリに興味を持つ 以前に在籍していたいくつかの現場では、様々な理由によりあまり積極的にgemを使うということをしていませんでしたが、GLOPLA LMSではそういったことはありません。それが必要であれば積極的に使っていこうという姿勢です。 となると当然ですが、使おうとするgemが本当に我々の望む機能を有しているのか、今後の保守に耐えうることができそうかということを見極める必要が出てきます。 そのために何をするか。 これも当たり前のことなんですが、公開されているリポジトリのソースを読みに行きます。 この時、自分にある変化が起こっていることに気づきました。 以前の僕はこういう外のソース読みに行っても「んーなんかすごい複雑で読むの大変やな...」というのが先に来ていたんですが、"どう読まれるか"を意識してコードを書くようになると「どこから読んだら分かりやすそうか」の勘が働くようになるので、以前よりも全然スラスラと読めるようになりました。 また、「おっ この書き方は分かりやすくていいな」とか、現場でコードを書くだけでは中々気づけなかったテクニックを発見したりと、自分で作る楽しさだけでなく誰かが作ったものに触れる面白さというのがプログラミングにはあるんだなと、今更ながらに気づくことができました。 ちなみに、GLOPLA LMS(というか、Globis全体)の開発メンバーは日常的にOSSコントリビュートしている方が結構多いです。 これも結構珍しい現場なのでは?と個人的には思っています。 そして自分も作る そんなこんなで開発にも慣れてきた頃、チームで使っているGithub(Zenhub)のissueチケット作成が面倒だねーという話になりました。(具体的には、全員で編集したMarkdownのファイルをベースに、チケットを手動で何枚も作り直すという手動コピペが常態化していた) ちょうどGithubのAPI触ってみたいな〜と思っていたところだったので、Markdownをパース → JSONで固めてGithub APIに投げる、という簡単なgemを作って、初めて公開してみました。 Copyist https://rubygems.org/gems/copyist これまで書いてきたようなメンテナブルなコードになっているか?と言われるとちょっと怪しいんですが笑、「このツールをどこかの誰かが使うかもしれない」と思うと、少なくとも致命的なバグは起こさないようにとか、せめて説明ぐらいはわかるように書かなきゃな、とかコードを書く以外にも色んな手間ひまがあってライブラリは作られているんだなという実感があり、改めて世のOSS開発者の皆さんに感謝せねばならんなという気持ちになりました。 また一方で、作って公開することは決して特別なことではなく少し気合を入れれば自分にもできることなんだから、もっと世に貢献するようなコード・プロダクトを作って、これからも楽しみながらエンジニア生活を送って行きたいな〜と思った2021年の師走でした。 最後に GLOPLA LMSをはじめGlobisでは一緒にプロダクト開発をするエンジニアを募集しています。 先述の通り、OSSコントリビュートを積極的に行っていますし、某有名Rubyエンジニアの方が開発に参画していたり、RubyWorld conferenceのスポンサード等、エンジニアの活動を支援する組織文化があります。 また、リモートワークや開発環境など働き方の面でも非常に過ごしやすいです。 なんといっても「プロダクトを綺麗に作っていきたい」という思いをお持ちの方、ここにはそれを受け入れる土壌があります。 ぜひ一度コンタクトいただけると嬉しいです! 自分語りな記事を最後までお読みいただきありがとうございましたmm 2022年も、皆様にとって実りと学びのある一年になりますように〜
- 投稿日:2021-12-03T20:00:00+09:00
マンダラートを作りたい #1
マンダラートとはアイデア出しの手法の一つ。 こんな漢字で、連想してアイデアを出してく。 最初から、こんな漢字のマス目を用意して、投稿表示するのが意外と難しい。 投稿ページ、投稿一覧表示ページ、投稿編集ページを合体させたいのだが、それが意外と難しい。(大事なことなので2回言った) class MandalartsController < ApplicationController def index @mandalarts = Mandalart.all new end def new @mandalart = Mandalart.new end def create @mandalart = Mandalart.new(mandalart_params) if @mandalart.save redirect_to mandalarts_path else render 'new' end end index.html.erb <table class="mandalarts"> <% 3.times do %> <tr> <% 3.times do %> <td> <% if @mandalarts %> <%= render "mandalarts/form", %> <% end %> <%= render "mandalarts/form"%> </td> <% end %> </tr> <% end %> </table> mandalarts/_form.html.erb <%= form_with model: @mandalart do |f|%> <% if @mandalart %> <%= f.text_area :text ,value: @mandalart.text%> <% else %> <%= f.text_area :text %> <% end %> <%= f.submit%> <% end %> ひとまずこんな感じ。 <%= f.text_area :text ,value: @mandalart.text%> とすることで、 すでにあるオブジェクトのテキストが、フォームの中にある状態にはなったが、submitを押すと、また新しいオブジェクトが生成されて、 フォームの中にあるテキストは、変わっていない。 編集のフォームをindex.html.erbに表示したい! 問題点は、form_withの性質上modelオプションの引数のオブジェクトが空なら、newアクションを呼び出してしまう。 <%if @mandalart == nil %> <%= @mandalart.text = "" %> <% end %> <%= form_with model: @mandalart do |f|%> とかにすれば、editアクションが呼び出されて、 マンダラートができるのではないか。 一旦諦めて、投稿フォームと、編集フォームを別々にすることに mandalarts_controller.rb class MandalartsController < ApplicationController def index # 最初から配列の要素を9つ入れとく # 配列の要素の中身があれば、何もしない # 配列の要素の中身があれば、""の要素を入れとく @mandalarts = Mandalart.all if @mandalarts == [] @mandalarts = [] 9.times do |mandalart| mandalart = Mandalart.create(text: '') @mandalarts << mandalart end end end def new @mandalart = Mandalart.new end def create @mandalart = Mandalart.new(mandalart_params) if @mandalart.save redirect_to mandalarts_path else render 'new' end end def edit @mandalart = Mandalart.find(params[:id]) end def update @mandalart = Mandalart.find(params[:id]) if @mandalart.update(mandalart_params) redirect_to mandalarts_path else render 'edit' end end private def mandalart_params params.require(:mandalart).permit(:text) end end mandalarts/index.html.erb <table class="mandalart-table"> <tr> <% @mandalarts[0..2].each do |mandalart| %> <td> <div class="mandalart-text"> <%= mandalart.text%> </div> <%= link_to "記入", edit_mandalart_path(mandalart.id)%> </td> <% end %> </tr> <tr> <% @mandalarts[3..5].each do |mandalart| %> <td> <div class="mandalart-text"> <%= mandalart.text%> </div> <%= link_to "記入", edit_mandalart_path(mandalart.id)%> </td> <% end %> </tr> <tr> <% @mandalarts[6..8].each do |mandalart| %> <td> <div class="mandalart-text"> <%= mandalart.text%> </div> <%= link_to "記入", edit_mandalart_path(mandalart.id)%> </td> <% end %> </tr> </table> こうすることで3×3のマンダラートが一応完成した。 DRYとはいい難いが、今の自分の技術でマス目状にするにはこうするしかなかった。 def index # 最初から配列の要素を9つ入れとく # 配列の要素の中身があれば、何もしない # 配列の要素の中身があれば、""の要素を入れとく @mandalarts = Mandalart.all if @mandalarts == [] @mandalarts = [] 9.times do |mandalart| mandalart = Mandalart.create(text: '') @mandalarts << mandalart end end end コメントにもあるが、最初に、9個要素がある配列を用意しないと、 viewでエラーが起きるので、9個要素がある配列を用意する処理を書いた。 (この処理は切り出してもいいかもしれない) if @mandalarts == [] とすることで、すでに配列の中身があれば、9.timesとかいう処理は実行されない。 常に3×3のマンダラートを表示させることに成功 次回はこのテストを書いていくことにする。
- 投稿日:2021-12-03T19:25:08+09:00
【Rails】gmailを使ってメールを送信しようとすると、Net::SMTPAuthenticationError (535-5.7.8 Username and Password not accepted.とかいう地獄の対処法【Mailer】
症状 RailsのMailerを使い、gmailを通してメール送信設定をしようとしたところ、以下のエラーが表示されました。 翻訳すると「ユーザー名とパスワードは受け入れられません」でした [ActiveJob] [ActionMailer::MailDeliveryJob] [af6c5cd6-78e7-40b7-a56f-8e982a58fa84] Error performing ActionMailer::MailDeliveryJob (Job ID: af6c5cd6-78e7-40b7-a56f-8e982a58fa84) from Async(mailers) in 1184.31ms: Net::SMTPAuthenticationError (535-5.7.8 Username and Password not accepted. Learn more at ): C:/Ruby30-x64/lib/ruby/3.0.0/net/smtp.rb:1015:in `check_auth_response' C:/Ruby30-x64/lib/ruby/3.0.0/net/smtp.rb:776:in `auth_plain' C:/Ruby30-x64/lib/ruby/3.0.0/net/smtp.rb:768:in `public_send' C:/Ruby30-x64/lib/ruby/3.0.0/net/smtp.rb:768:in `authenticate'C:/Ruby30-x64/lib/ruby/3.0.0/net/smtp.rb:604:in `do_start' C:/Ruby30-x64/lib/ruby/3.0.0/net/smtp.rb:557:in `start' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/mail-2.7.1/lib/mail/network/delivery_methods/smtp.rb:109:in `start_smtp_session' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/mail-2.7.1/lib/mail/network/delivery_methods/smtp.rb:100:in `deliver!' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/mail-2.7.1/lib/mail/message.rb:2159:in `do_delivery' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/mail-2.7.1/lib/mail/message.rb:260:in `block in deliver' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/actionmailer-6.0.4.1/lib/action_mailer/base.rb:589:in `block in deliver_mail' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.0.4.1/lib/active_support/notifications.rb:180:in `block in instrument' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.0.4.1/lib/active_support/notifications/instrumenter.rb:24:in `instrument' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.0.4.1/lib/active_support/notifications.rb:180:in `instrument' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/actionmailer-6.0.4.1/lib/action_mailer/base.rb:587:in `deliver_mail' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/mail-2.7.1/lib/mail/message.rb:260:in `deliver' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/actionmailer-6.0.4.1/lib/action_mailer/message_delivery.rb:115:in `block in deliver_now' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/actionmailer-6.0.4.1/lib/action_mailer/rescuable.rb:17:in `handle_exceptions' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/actionmailer-6.0.4.1/lib/action_mailer/message_delivery.rb:114:in `deliver_now' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/actionmailer-6.0.4.1/lib/action_mailer/mail_delivery_job.rb:23:in `perform' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activejob-6.0.4.1/lib/active_job/execution.rb:40:in `block in perform_now' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.0.4.1/lib/active_support/callbacks.rb:112:in `block in run_callbacks'C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/i18n-1.8.10/lib/i18n.rb:314:in `with_locale' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activejob-6.0.4.1/lib/active_job/translation.rb:9:in `block (2 levels) in <module:Translation>' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.0.4.1/lib/active_support/callbacks.rb:121:in `instance_exec' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.0.4.1/lib/active_support/callbacks.rb:121:in `block in run_callbacks'C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.0.4.1/lib/active_support/core_ext/time/zones.rb:66:in `use_zone' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activejob-6.0.4.1/lib/active_job/timezones.rb:9:in `block (2 levels) in <module:Timezones>' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.0.4.1/lib/active_support/callbacks.rb:121:in `instance_exec' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.0.4.1/lib/active_support/callbacks.rb:121:in `block in run_callbacks'C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activejob-6.0.4.1/lib/active_job/logging.rb:25:in `block (4 levels) in <module:Logging>' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.0.4.1/lib/active_support/notifications.rb:180:in `block in instrument' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.0.4.1/lib/active_support/notifications/instrumenter.rb:24:in `instrument' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.0.4.1/lib/active_support/notifications.rb:180:in `instrument' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activejob-6.0.4.1/lib/active_job/logging.rb:24:in `block (3 levels) in <module:Logging>' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activejob-6.0.4.1/lib/active_job/logging.rb:45:in `block in tag_logger' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.0.4.1/lib/active_support/tagged_logging.rb:80:in `block in tagged' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.0.4.1/lib/active_support/tagged_logging.rb:28:in `tagged' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.0.4.1/lib/active_support/tagged_logging.rb:80:in `tagged' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activejob-6.0.4.1/lib/active_job/logging.rb:45:in `tag_logger' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activejob-6.0.4.1/lib/active_job/logging.rb:21:in `block (2 levels) in <module:Logging>' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.0.4.1/lib/active_support/callbacks.rb:121:in `instance_exec' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.0.4.1/lib/active_support/callbacks.rb:121:in `block in run_callbacks'C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.0.4.1/lib/active_support/callbacks.rb:139:in `run_callbacks' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activejob-6.0.4.1/lib/active_job/execution.rb:39:in `perform_now' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activejob-6.0.4.1/lib/active_job/execution.rb:25:in `block in execute' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.0.4.1/lib/active_support/callbacks.rb:112:in `block in run_callbacks'C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activejob-6.0.4.1/lib/active_job/railtie.rb:43:in `block (4 levels) in <class:Railtie>' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.0.4.1/lib/active_support/execution_wrapper.rb:88:in `wrap' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.0.4.1/lib/active_support/reloader.rb:72:in `block in wrap' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.0.4.1/lib/active_support/execution_wrapper.rb:88:in `wrap' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.0.4.1/lib/active_support/reloader.rb:71:in `wrap' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activejob-6.0.4.1/lib/active_job/railtie.rb:42:in `block (3 levels) in <class:Railtie>' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.0.4.1/lib/active_support/callbacks.rb:121:in `instance_exec' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.0.4.1/lib/active_support/callbacks.rb:121:in `block in run_callbacks'C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.0.4.1/lib/active_support/callbacks.rb:139:in `run_callbacks' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activejob-6.0.4.1/lib/active_job/execution.rb:23:in `execute' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activejob-6.0.4.1/lib/active_job/queue_adapters/async_adapter.rb:70:in `perform' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb:363:in `run_task' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb:352:in `block (3 levels) in create_worker' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb:335:in `loop' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb:335:in `block (2 levels) in create_worker' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb:334:in `catch' C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/concurrent-ruby-1.1.9/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb:334:in `block in create_worker' 今回の解決方法 アプリのアプリパスワードの設定方法を ・アプリ選択:「メール」 ・デバイス選択:「windows」 にしたら解決しました。(2段階認証ON状態) 今まで、その他で設定していたため、弾かれていたみたい。。。 やったことけど、解決しなかったこと(念のため残す) IMAP有効 以下で紹介されているgmailのIMAPを有効にしてみた。しかし、変化なし。。。 安全性の低いアプリを許可 デフォルトでは安全性の低いアプリケーションからGmailへのアクセスが制限されているそうで、2段階認証を外して安全性の低いアプリを許可しました。しかし、変化なし。。。 環境間違えてない? 念のため、config/environments/のdevelop、test、productionのそれぞれに設定を追加。 production config.action_mailer.perform_deliveries = true config.action_mailer.default_options = { from: ENV['EMAIL_ADDRESS'] } config.action_mailer.default_url_options = { host: 'localhost:3000' } config.action_mailer.delivery_method = :smtp config.action_mailer.smtp_settings = { address: 'smtp.gmail.com', port: 587, user_name: ENV['EMAIL_ADDRESS'], password: ENV['EMAIL_PASSWORD'], authentication: 'plain', enable_starttls_auto: true } ちなみに、ENVファイルは以下で設定。 ENV EMAIL_ADDRESS=example@gmail.com EMAIL_PASSWORD=アプリパスワード すべてのメールで POP を有効にする 以下のサイトを参考にして、POP設定を有効にしたものの変化なし。。。 figaro 環境変数のENV[`EMAIL_PASSWORD]などをfigaroを使わずに使っていたので、installしてサーバー再起動を実行。 しかし、変化なし。。。 debbug.loggerで参照してみると、普通に値は取れたので機能していることは確認できた。 参考
- 投稿日:2021-12-03T19:22:10+09:00
【Rails】redirect_toとrenderの処理の違いについて【初学者の疑問点を簡潔に解説】
はじめに 本記事は、プログラミングの学習を始めて1ヶ月の初学者が、学習を進めていて疑問に思った点について調べた結果を備忘録も兼ねてまとめたものです。 そのため、記事の内容に誤りが含まれている可能性があります。ご容赦ください。 間違いを見つけた方は、お手数ですが、ご指摘いただけますと幸いです。 今回の疑問点 今回の疑問点は、 redirect_toとrenderの処理の違い です。 以前、バリデーションの設定を行った際に上記について疑問を抱きました。 また、処理の違いをしっかりと理解しておかないとエラーの発生につながるので今回まとめていきます。 疑問点についての解説 結論 結論から記載しますとredirect_toとrenderの処理はそれぞれ以下のようになります。 redirect_toの処理の流れ ①controller(redirect_to) → ②HTTPリクエスト → ③ルーティング → ④controller → ⑤view ①controllerの処理でredirect_toを実行 ②redirect_toの引数で指定したURLにHTTPリクエストを実行 ③HTTPリクエストされたURLに対応するルーティング処理を実行 ④ルーティング処理に対応したcontroller、アクションが呼び出され処理を実行 ⑤処理に応じたviewのレンダリングを実行 renderの処理の流れ ①controller → ②view ①controllerの処理でrenderを実行 ②renderのオプションで指定したviewファイルを表示する 特に注意を要するポイント redirect_toとrenderの処理の違いの中でも、renderを使用してviewファイルを表示したときにはアクションを呼び出し処理をしているわけではないことに注意しましょう。 以下で具体例を用いて説明します。 app/controllers/books_contllore.rb class BooksController < ApplicationController def index @books = Book.all @book = Book.new end def create @book = Book.new(book_params) @book.user_id = current_user.id if @book.save redirect_to book_path(@book), notice: "You have created book successfully." else render 'index' end end end 上記の記述で、renderの処理の際にindexアクションは呼び出されず、createアクションから直接index.html.erbを表示しするためエラーが発生します。 indexアクションをご確認ください。 @books = Book.all @book = Book.new 2つのインスタンス変数が定義されております。 次にcreateアクションをご確認ください。 @book = Book.new(book_params) @book.user_id = current_user.id のみでindexアクションで定義されていた @books = Book.all が定義されていません。 先ほども申し上げた通り、render処理の際にはindexアクションは実行されないので、上記の記述のまま、処理をしてもcreateアクション内では@books = Book.allが定義されていないため、Viewがデータを取得できずにエラーとなります。 以下のように記載することでエラーを解消することができます。 app/controllers/books_contllore.rb class BooksController < ApplicationController def index @books = Book.all @book = Book.new end def create @book = Book.new(book_params) @book.user_id = current_user.id if @book.save redirect_to book_path(@book), notice: "You have created book successfully." else @books = Book.all #追記 render 'index' end end end また、render処理の際にはindexアクションが実行されないことから、@book = Book.newも実行されず、フォームの中に入力した内容がそのまま表示されます。 まとめ 最後にポイントをまとめます。 render処理の場合にはアクションが実行されない。 上記のことから、render処理を行う際には、呼び出すViewファイルに渡す変数が全てrender処理を行うアクションに定義されているか注意する必要がある。
- 投稿日:2021-12-03T16:55:25+09:00
rails グループ機能を多対多で実装
テックアカデミーの卒業課題でオリジナルアプリを製作中 実装済みもアプリの主な機能は下記の通り ・ユーザ登録機能 ・ログイン機能 ・ユーザプロフィール ・投稿機能 ・フォロー、フォロワー ・投稿のお気に入り ・グループ機能 【グループ機能に取り入れたい機能】 ・グループの作成、編集、消去 →グループ名 ・グループへの参加 ・参加グループでのチャット機能 (未実装) 【グループ機能の実装手順】 1 ER図の作成 2 グループの作成「ユーザ(users)とグループ(groups)を1対多で作成」 →投稿機能を参考に作成 3 グループへの参加「ユーザ(users)とグループ(groups)を多対多で作成」 →中間テーブルとして「group_users」を作成 ・joinings(ユーザが参加しているグループ一覧) ・joineds(グループの参加ユーザ一覧) の一覧を実装 4 参加グループでのチャット機能 →現在試行錯誤中 グループの作成自体は「一対多」と「多対多」の応用で割と簡単に実装できたが、参加グループでのチャット機能の実装に苦戦中。
- 投稿日:2021-12-03T16:51:46+09:00
Railsで日時を取得したいのに、2000年1月1日ばかり保存されてしまう件について
はじめに 現在、Ruby on Railsにて睡眠を記録するアプリを作成しています。form_withを使用して日時を記録する際に、登録したデータの日付が2000年1月1日としか保存されませんでした。非常に単純な原因でしたので、備忘録として投稿します。 開発環境 DBにはMySQLを使用しています。 Mac OS Big Sur 11.6 VSCode Ruby 3.0.2p107 Rails 6.1.4.1 MySQL 8.0.27 for macos11.6 on arm64 (Homebrew) 原因 migrationファイルでDateTime型とすべきところをTime型にしていた。 MySQLについて調べるべきなのに、Rubyのことを調べていた。 背後要因 日付だけでなく時刻も扱える Date のサブクラスです。 DateTime は deprecated とされているため、 Timeを使うことを推奨します。 Ruby 3.0.0 リファレンスマニュアル class DateTime(要約) RubyのTime型について調べたときに、リファレンスを見て「なるほど、Ruby3.0以降はDateTimeは非推奨なのか。ならばmigrationを作るときもTime型を使えばいいのか!」と安易な考えに至ってしまいました。 やりたかったこと やりたかったことは、「フォームを利用して、就寝した時間と起床した時間をDBに保存できるかを確かめるための試験的な機能を作る」ことでした。 登録画面で就寝および起床時間を選択 登録ボタンを押したらデータをDBに保存 正常に保存できたら睡眠データの一覧ページにリダイレクト このような機能を目指して作成していました。 migration すでに生成してあるUserモデルと関連を持たせています。UserとSleepLogは、1対多の関係になります。 ***_create_sleep_logs.rb class CreateSleepLogs < ActiveRecord::Migration[6.1] def change create_table :sleep_logs do |t| t.references :user, foreign_key: true t.time :sleep_at t.time :wake_at t.timestamps end add_index :sleep_logs, %i[user_id created_at] end end なお、ここで問題の原因となったのがsleep_atカラムとwake_atカラムです。本来はここでdatetime型とすべきところを、time型としてしまいました。 routes ルーティングはresourcesを利用してindex以外のアクションを生成しています。 routes.rb resources :sleep_logs, except: :index これでsleep_logs_pathにPOSTすればsleep_logs_controllerのcreateアクションが利用できるようになりました。 $ rails routes | grep sleep_logs sleep_logs POST /sleep_logs(.:format) sleep_logs#create controller データを正常に保存できれば、ユーザーの個別ページで睡眠データ一覧を表示するようにしています。 sleep_logs_controller.rb class SleepLogsController < ApplicationController def new end def create sleep_log = current_user.sleep_logs.build(sleep_log_params) if sleep_log.save flash.now[:success] = "記録が保存されました" redirect_to @current_user else flash[:invalid] = "エラーが発生しました" render 'new' end end (省略) private def sleep_log_params params.require(:sleep_log).permit(:sleep_at, :wake_at) end end view 登録用フォームのviewです。form_withのtime_selelctを利用して、時間を選択できるようにしています。とりあえず試験的なデータを保存できればいいのでdefaultオプションを設定しています。 new.html.erb <%= form_with(url: sleep_logs_path, scope: :sleep_log, local: true) do |f| %> <%= f.label :sleep_at, "就寝時間" %> <%= f.time_select :sleep_at, default: Time.zone.now - 8.hours %> <%= f.label :wake_at, "起床時間" %> <%= f.time_select :wake_at, default: Time.zone.now %> <%= f.submit "記録する" %> <% end %> 特にcssなどは当てていないため、chrome上ではこのように表示されます。 睡眠データ一覧ページのviewについては省略させていただきます。 実際に登録してみる とりあえず準備が整ったので、睡眠データを保存してみます。 登録前 データが1件もないため、メッセージが表示されています。 登録後 登録したデータ自体のid、関連付けしてあるuserのuser_id、日付、就寝時間、起床時間の順に表示されています。 投稿内容を編集している現在(2021/12/03)の日付を表示したいのですが、01/02となってしまっています。 どんな日付が入っているのか? rails consoleを利用して、睡眠データの中身を確認してみます。sleep_atとwake_atは2000年の1月、timestamp(created_atとupdated_at)は正しく時間が表示されています。 [#<SleepLog:0x000000012ef4dc90 id: 1, user_id: 1, sleep_at: Sun, 02 Jan 2000 07:50:00.000000000 JST +09:00, wake_at: Sat, 01 Jan 2000 15:50:00.000000000 JST +09:00, created_at: Fri, 03 Dec 2021 15:50:41.277532000 JST +09:00, updated_at: Fri, 03 Dec 2021 15:50:41.277532000 JST +09:00>] paramsの内容を調べてみる 原因を探るため、まずは「paramsに正しいデータが保存されているか」を調べてみることにしました。controllerにraiseを追加して、わざと例外を発生させてみます。 sleep_logs_controller.rb def create sleep_log = current_user.sleep_logs.build(sleep_log_params) raise if sleep_log.save flash.now[:success] = "記録が保存されました" redirect_to @current_user else flash[:invalid] = "エラーが発生しました" render 'new' end end paramsの中身は...? このようになっていました。どうやらparams[:sleep_log]内には、sleep_atとwake_atそれぞれの年、月、日、時、分が格納されているようです。年には2021、月には12, 日には3が入っています。paramsには問題なさそうです。 問題の解決 いろいろと検索してみた結果、「DBの型がおかしいのかもしれない」と勘付きました。RubyのTime型では日付も扱うことができますが、MySQLのTime型では時間を取り扱うだけで日付は保存できないようですね… migrationの修正 sleep_atをwake_atをdatetime型に変換し、rails db:migrate:resetを実行します。 ***_create_sleep_logs.rb t.datetime :sleep_at t.datetime :wake_at 再度、登録してみる フォームを利用して再度データを登録してみます。 DateTime型に変更した結果、日付も正しく保存されるようになりました!! 最後に 問題が発生したときは、何が原因かを探る前に「何を取り扱っているのか」を考えるべきだと感じました。今回で言えば、MySQLのTime型とDateTime型の違いについて調べればすぐに判明したのに、Rubyのことばかり調べて数時間を無駄にしてしまったからです。 これからも何度も壁にぶつかると思いますが、がんばって乗り越えて行こうと思います。
- 投稿日:2021-12-03T13:51:32+09:00
【技育展2021】本当に"はじめてのアプトプット"をしたら意外にも優秀賞をもらえた
技育展とは? 株式会社サポーターズが開催する、学生エンジニアのアウトプットを展示するイベントです。 アウトプット作品を作る+それをプレゼンすることがイベントの主な内容です。 私は2021年開催の回に参加しました。 チームは高校時代の同級生の友人と2人でした。 何を作ったの? 失敗ドットコムというWebサイトを作成したました。 このサイトは、みんなの失敗談を知識として集積して、他の人の挑戦に役立てようというコンセプトから生まれました。 主な機能は、自分の失敗談を投稿したり、他人の失敗談を閲覧することです。 例えば、こんな投稿があったりします。 審査結果は? 以下のようなルールで入賞者が選出されます。 最優秀賞(1作品) 30万円/テーマ 優秀賞(1作品) 10万円/テーマ 「はじめてのアウトプット」は優秀賞のみ (賞金3万円×12作品)となります 参加賞 5000円(登壇者全員) 私が参加したのは「はじめてのアウトプット」というテーマのため、優秀賞しか存在しませんでした。 たしか、「はじめてのアウトプット」は60チームぐらいあったため、全体の1/5が優秀賞を貰えます。 そして… 私のチームは優秀賞をいただくことができました。(でも、よく考えれば1/5ってだいぶ多い) 本当にはじめてのアウトプットだったのでかなり嬉しかったです。 ここから先ではなぜ優秀賞を貰えたのかを振り返りたいと思います。 優秀賞を取るために工夫したことは? 開発フェーズ 実は何もありません。 なぜなら、エントリー時には既にサービスが完成していたからです。 (エントリー前の開発時にどのような工夫を行ったのかは、後で述べています) ちなみに、技育展の応募資格は下記のようになっており、既成作品を提出することはルール違反ではありません。 ・学生が作成した作品であること(学校種別、学年不問) ・新規作成or既成作品かは問いません ・個人制作orチーム制作いずれでも可 プレゼンフェーズ めちゃくちゃ力を入れました。 なぜなら、自分自身が「はじめてのアウトプット」の中でも技術レベルが相当低い方であることは容易に想像がついていたため、プレゼンで勝負するしかないと思ったからです。 下記のGoogle Slideがプレゼン本番で利用したスライドです。 技術レベルが低いからこそ、サービスの価値という側面で攻めた内容にしました。 (エントリー前に)開発するときに工夫したことは? 開発に着手する前から、失敗談を閲覧・投稿できるWebサイトというコンセプト自体は固まっていました。 そして、開発段階で工夫したことは、このコンセプトを元になるべくそれっぽいものを作るということでした。 「それっぽい」の定義にオリジナリティーがありすぎる気がするため補足すると、 『今の技術力の範囲で、「えwwこれ絶対素人が作ったサイトじゃんwwwぷぷぷwww」となることをなるべく避けられてる』ことを表すと思ってください。 なぜ「それっぽさ」を追求したかというと、素人が作った感が出ている時点で同志の駆け出しエンジニアでもない限り、そのサービスは利用する人はほとんどいないと思っていたからです。(超個人的意見です) 「それっぽさ」を追求して実際に行ったことの例は下記の通りです。 UIにこだわる。特にトップページ。 投稿内容に見出しや太字、イタリックなどを入れられるようにする。 画像を投稿できるようにする。 カテゴリを2階層にする。(ex.学習カテゴリの中の大学受験カテゴリ) 閲覧数をカウントし、人気の記事を表示する。 利用規約・プライバシーポリシー・お問い合わせページを作成する。 審査員からどんな評価をもらった? アプローチがおもしろい。 発想が面白かった。 内容も面白く、サービスとしてのクオリティも高かった。 考え方の視点が今までなくて確かにと納得できる考え方で,とてもサービスを使ってみたいと感じました。 投稿方法もmarkdownではなく普通の人でも使いやすそうでUIもかわいく人気が出そうと感じた。 コンセプトが明確だったため。 課題と解決するプロダクトの説明が明瞭完結。 テーマがおもしろく、そのテーマを実現するためのシステムもしっかりしていた。 アイデアが面白い 発想、動機が面白い。 利用層が安定しそうである。 アイデアが良い これも普及してほしい アイディアが良かった。 「失敗談」への着目が面白いなと思いました。 よく成功談が注目されるが、失敗という経験談に注目した点。 カテゴリ分けや分かりやすいUIがなおよい。 失敗を集めるっていうのがあまり思い浮かばなかった発想で驚きました。使ってみたいです。 失敗談を投稿するという発想がおもしろかったため。全体的な完成度も高く実用的だと思った。 失敗経験から失敗を防ぐための学びを得るという考えに共感し、UIもきれいで見やすかったため。 UIがとてもきれいでした。 制作背景とそこからの発想や視点、裏付けがしっかりしていて、それに沿ったコンテンツを実現させた技術力、全てすごいと感じました! コンセプトがすばらしいと思った。 成功のパターンはたくさんあるけれど、失敗のパターンは限られてくるので、失敗を学ぶことで成功すると以前聞いたことがあります。このサイトで失敗を共有すれば確かにみんなが成功に近づくだろうなと思いました。 成功談ではなく失敗談を集めることで誇張されることはなく、また役に立つという発想が大変面白かったです。投稿周りもしっかりしていて、これから投稿数が増えるのが楽しみです。 コンセプトが面白い サイトも見易く、またミッションにも強く共感したから。 失敗の体験談は話しづらいことでもあるので、こういうアプリがあるといいなと思った。 発想が面白かった。 共感の翁。 失敗談にフォーカスしている点に強く共感を抱いた。体験ベースのアウトプットという点に価値を感じた。 失敗が重要というコンセプトに共感する。沢山の人に使ってほしい。 失敗を見れるサイトは需要があると思う。ユーザーもある程度いて、使いやすいデザインになっていると思う。 作品の着眼点がすごくいいなと思いました 逆転の発想のようなアイデア リリース済みでユーザもいる点 プレゼンテーションでの説得力が高かった。サイトも単なるデモ以上に機能しており、ターゲットに対して明確にソリューションを打ち出せている。今後の機能開発に期待したい。 アイディア・UIに関する評価内容が多く、概ね狙い通りです。 最後に 失敗ドットコムで失敗談を投稿しませんか? おそらく、とても多くの人の参考になります。 投稿できることなんてないよという方は人気の記事だけでもご覧になってください!
- 投稿日:2021-12-03T13:24:30+09:00
【Ruby】trimしたいときはstripしよう
strip Rubyにはtrimメソッドがないので代わりにstripメソッドを使う。 文字列先頭と末尾の空白文字を全て取り除いた文字列を生成して返す。 空白文字の定義は" \t\r\n\f\v" 文字列右側からは"\0"も取り除くが、左側の"\0"は取り除かない。 pry(main)> p " s".strip => "s" # 文字と文字の間の空文字は取り除かない pry(main)> p " s f ".strip => "s f" pry(main)> p " s f\n".strip => "s f" pry(main)> p "\0 s f\0".strip => "\u0000 s f" 参考
- 投稿日:2021-12-03T12:42:43+09:00
【ruby on rails】 gem impressionistで追加したPV数カウント機能が動作しない場合がある
0.概要 文章を投稿できるアプリをruby on rails で作り 投稿の詳細画面でpv数をカウントする機能を導入済み しかし、一部の詳細画面ではPV数が追加されない 1. 前提(カウント機能追加までに実施したこと) Gemfileに以下を導入しbundle install gem 'impressionist', '~>1.6.1' さらにターミナルで以下実施 rails g impressionist 以下が作成される db/migrate/〇〇〇〇〇〇〇〇〇〇〇〇〇_create_impressions_table.rb class CreateImpressionsTable < ActiveRecord::Migration[5.2] def self.up create_table :impressions, :force => true do |t| t.string :impressionable_type t.integer :impressionable_id t.integer :user_id t.string :controller_name t.string :action_name t.string :view_name t.string :request_hash t.string :ip_address t.string :session_hash t.text :message t.text :referrer t.text :params t.timestamps end add_index :impressions, [:impressionable_type, :message, :impressionable_id], :name => "impressionable_type_message_index", :unique => false, :length => {:message => 255 } add_index :impressions, [:impressionable_type, :impressionable_id, :request_hash], :name => "poly_request_index", :unique => false add_index :impressions, [:impressionable_type, :impressionable_id, :ip_address], :name => "poly_ip_index", :unique => false add_index :impressions, [:impressionable_type, :impressionable_id, :session_hash], :name => "poly_session_index", :unique => false add_index :impressions, [:controller_name,:action_name,:request_hash], :name => "controlleraction_request_index", :unique => false add_index :impressions, [:controller_name,:action_name,:ip_address], :name => "controlleraction_ip_index", :unique => false add_index :impressions, [:controller_name,:action_name,:session_hash], :name => "controlleraction_session_index", :unique => false add_index :impressions, [:impressionable_type, :impressionable_id, :params], :name => "poly_params_request_index", :unique => false, :length => {:params => 255 } add_index :impressions, :user_id end def self.down drop_table :impressions end end さらにカウントさせる投稿(posts)テーブルに「impressions_count」カラムを追加 db/migrate/〇〇〇〇〇〇〇〇〇〇〇〇〇_add_impressions_count_to_posts.rb class AddImpressionsCountToPosts < ActiveRecord::Migration[5.2] def change add_column :posts, :impressions_count, :integer, default: 0 end end 上記で rails db:migrate さらにカウント対象のpostsモデル、コントローラーに記述を追加する app/models/post.rb class Post < ApplicationRecord is_impressionable counter_cache: true end app/controllers/posts_controller.rb class PostsController < ApplicationController def index # 一覧画面 @posts = Post.all end def show # 詳細画面 @post = Post.find(params[:id]) impressionist(@post, nil, unique: [:session_hash.to_s]) end .... end show画面閲覧時にカウントさせる to_sメソッド記載はSessionIdエラー防止のため 具体的なPV数は以下のようなindex画面で質問の概要と並べる形で表示させる app/views/posts/index.html.erb <% @posts.each do |post| %> (~質問一覧~省略~) <%= post.impressions_count %> (~~省略~) <% end %> 2.PV更新有無 show画面確認→index画面へ戻るとPV数が1追加されてるが 一部更新されないものも存在 以下はpostのshow.html閲覧時にPV数が通常更新されるデータ(投稿id(post.id)が28のもの)を 使用データベース(自分の場合mysql)上で確認した結果である ※「rails g impressionist」でテンプレテーブルを作ったため 使わないカラムが多いが、impressionable_idがpostテーブルのid(今回の場合28)を表す MySQL [アプリ名]> select * from impressions where impressionable_id=28; +-----+---------------------+-------------------+---------+-----------------+-------------+-----------+------------------------------------------------------------------+----------------+----------------------------------+---------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------+---------------------+---------------------+ | id | impressionable_type | impressionable_id | user_id | controller_name | action_name | view_name | request_hash | ip_address | session_hash | message | referrer | params | created_at | updated_at | +-----+---------------------+-------------------+---------+-----------------+-------------+-----------+------------------------------------------------------------------+----------------+----------------------------------+---------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------+---------------------+---------------------+ | 312 | Post | 28 | NULL | posts | show | NULL | 9238b0b3c610130cbc0b3334e16a4c1eb838330b596d2c6bbed5714cfd52b6a6 | 116.83.115.249 | 248e8c24dc2cdeacfa9832404436eb26 | NULL | https://warerano3594.com/public/posts?utf8=%E2%9C%93&range=PV%E6%95%B0%E3%81%AE%E5%A4%9A%E3%81%84%E9%A0%86%E3%81%AB&commit=%E5%9B%9E%E7%AD%94%E4%B8%A6%E3%81%B9%E6%9B%BF%E3%81%88 | NULL | 2021-12-02 06:30:57 | 2021-12-02 06:30:57 | | 352 | Post | 28 | NULL | posts | show | NULL | b37e953bd673cbbac98bf507882f4637eebd007f42890d41dfdb474ae2921cda | 116.83.115.249 | 54b8e2634cec17f36079eeca7a0a8bd0 | NULL | https://warerano3594.com/public/posts?utf8=%E2%9C%93&range=%E6%8A%95%E7%A8%BFNo%E3%81%8C%E5%8F%A4%E3%81%84%E9%A0%86%E3%81%AB&commit=%E5%9B%9E%E7%AD%94%E4%B8%A6%E3%81%B9%E6%9B%BF%E3%81%88 | NULL | 2021-12-02 07:26:06 | 2021-12-02 07:26:06 | | 449 | Post | 28 | NULL | posts | show | NULL | c3204d34ca373f27dcc7940014c3a8cbcac6ae01317edc14ca2853cc59a89f3a | 116.83.115.249 | 9c84102ac332dd37558a1e68f930055d | NULL | https://warerano3594.com/public/posts?utf8=%E2%9C%93&range=PV%E6%95%B0%E3%81%AE%E5%B0%91%E3%81%AA%E3%81%84%E9%A0%86%E3%81%AB&commit=%E5%9B%9E%E7%AD%94%E4%B8%A6%E3%81%B9%E6%9B%BF%E3%81%88 | NULL | 2021-12-02 08:47:53 | 2021-12-02 08:47:53 | 更新されない投稿内容画面(post/投稿id)の特徴を確認したところ、最新更新日時のレコードでpv数カウントに使用する「session_hash」カラムがnullとなっているものだと分かった MySQL [アプリ名]> select * from impressions where session_hash is null; +-----+---------------------+-------------------+---------+-----------------+-------------+-----------+------------------------------------------------------------------+---------------+--------------+---------+----------+--------+---------------------+---------------------+ | id | impressionable_type | impressionable_id | user_id | controller_name | action_name | view_name | request_hash | ip_address | session_hash | message | referrer | params | created_at | updated_at | +-----+---------------------+-------------------+---------+-----------------+-------------+-----------+------------------------------------------------------------------+---------------+--------------+---------+----------+--------+---------------------+---------------------+ | 478 | Post | 67 | NULL | posts | show | NULL | dfac460ae80d0693781edfe0de46c576ae612735c52907b3de2a85abf890407c | 34.219.180.21 | NULL | NULL | NULL | NULL | 2021-12-02 09:28:20 | 2021-12-02 09:28:20 | | 479 | Post | 69 | NULL | posts | show | NULL | 0f009e99f0ac6885adb6dadd69ef5487cc4c79facfce65e7a5be872ffd240903 | 54.187.149.38 | NULL | NULL | NULL | NULL | 2021-12-02 09:29:04 | 2021-12-02 09:29:04 | | 480 | Post | 74 | NULL | posts | show | NULL | cd99ffbbf1e06c9426dd24c1f14ca775cb400b67fd3ebb6f094370ec1abb4e03 | 34.220.223.62 | NULL | NULL | NULL | NULL | 2021-12-02 09:30:02 | 2021-12-02 09:30:02 | | 484 | Post | 68 | NULL | posts | show | NULL | 582e752304d947291ab514ddbc2bb602b585b7b89d741706c3cd5f67b1fdd573 | 52.26.112.150 | NULL | NULL | NULL | NULL | 2021-12-02 09:32:55 | 2021-12-02 09:32:55 | 3.対処 session_hashが何故nullとなってしまうのか根本原因は未だわかっていないため 対処療法となるが以下のように「session_hash」カラムがnullとなっている データを削除 delete from impressions where session_hash is null; 上記実施後に確認したところ、閲覧数カウントが止まっていたものが、再度増えていくようになった。
- 投稿日:2021-12-03T12:26:40+09:00
戻るボタンを実装したらdropdownが動かなくなった
概要 戻るボタンを実装しようと思い、 <%= link_to :back do %> <button type="button" class="btn btn-info">戻る</button> <% end %> と書いてみた。 すると、この戻るボタンからページを変遷させると、ヘッダーにあるdropdownが動かなくなってしまった。 調査 調べてみると、このコードは自動的にjsを呼び出しているらしい。 turbolinksを無効にすれば実装できるかもしれない ということで 解決方法 <div data-turbolinks="false"> <%= link_to :back do %> <button type="button" class="btn btn-info">戻る</button> <% end %> </div> <div data-turbolinks="false"> で囲んであげると解決できました。 jsが呼び出されることとturbolinksの関係についてはまだ理解できていないので もっと勉強したいと思います。。
- 投稿日:2021-12-03T12:19:31+09:00
【Rails】本番環境でimages配下の画像が表示されない場合の対処法【Heroku】
やりたいこと Herokuの本番環境でassets/images下の画像を表示させたい。 エラー内容 開発環境では表示されるが、本番環境では画像が表示されない。 原因 通常本番環境ではアセットパイプラインを通る処理が自動実行されない。 解決策 本番環境上でアセットパイプラインを通るようにプリコンパイル処理を実行する rake assets:precompile RAILS_ENV=production or 本番環境上でアセットパイプラインを自動で通るように設定を変更する config/production.rbファイルのconfig.assets.compile = falseをtrueにする。 trueだとprecompileしていないファイルを動的にコンパイルするようになる。 参考 Rails初学者がつまずきやすい「アセットパイプライン」 Rails Assetの管理についてまとめる - Qiita
- 投稿日:2021-12-03T08:27:55+09:00
【Rails】フォロー/フォロワー機能のアソシエーションについて【初学者の疑問点を詳しめに説明】
はじめに 本記事は、Railsの学習を始めて1ヶ月の初学者が、学習を進めていて疑問に思った点について調べた結果を備忘録も兼ねてまとめたものです。 そのため、記事の内容に誤りが含まれている可能性があります。ご容赦ください。 間違いを見つけた方は、お手数ですが、ご指摘いただけますと幸いです。 今回の疑問点 今回の疑問点は、 フォロー/フォロワー機能のアソシエーションについて です。 現在、Twitterクローンを作成しており、フォロー/フォロワー機能の実装の際に上記について疑問を抱きました。 疑問点についての解説 いいね機能実装時との比較 おそらく、多くの方がフォロー/フォロワー機能実装の前にいいね機能の実装に取り組んでいることと思いますので、いいね機能と比較して確認していきます。 いいね機能のアソシエーションの記述 Favoriteモデルの記述 app/models/favorite.rb class Favorite < ApplicationRecord belongs_to :user # FaviriteモデルとUserモデルの1対1の関係 belongs_to :tweet # FavoriteモデルとTweetモデルの1対1の関係 end Userモデルの記述 app/models/user.rb class User < ApplicationRecord has_many :tweets # UserモデルとTweetモデルの1対多の関係 has_many :favorites # UserモデルとFavoriteモデルの1対多の関係 has_many :favorite_tweets, through: :favorites, source: :tweet # UserモデルとTwitterモデルの多対多の関係(Favoriteモデル経由) end Tweetモデルの記述 app/models/tweet.rb class Tweet < ApplicationRecord belongs_to :user # TweetモデルとUserモデルとの1対1の関係 has_many :favorites # TweetモデルとUserモデルとの1対多の関係 end 各モデルの関係 上記の通り記述することで各モデルを以下の通り関連付けています。 〜〜〜Userモデル〜〜〜 ⇄ 多対多 ⇄ 〜〜〜Tweetモデル〜〜〜 ⇅1対1及び1対多 ⇅1対1及び1対多 〜〜〜〜〜〜〜〜〜〜〜〜〜〜Favoriteモデル〜〜〜〜〜〜〜〜〜〜〜〜〜〜 各モデルを頂点とした三角形の相互関係となっています。 フォロー/フォロワー機能のアソシエーションの記述 記述を見る前に各モデルの関係性から確認します。 フォロー/フォロワー機能の場合には以下の通りとなります。 〜〜〜〜〜Userモデル〜〜〜〜〜 ⇅ 1対多の関係 〜〜〜〜Relationshipモデル〜〜〜〜 このまま、実装しようとしてもフォローする側のユーザーとフォローされる側のユーザーを区別することができないため、うまくいきません(フォロー数とフォロワー数を表示したいのにフォロー数とフォロワー数の合計しか出せない等)。 うまく実装するためには、先程のいいね機能の時のように三角形の関係を作ってあげる必要があります。 そこで以下のように記述します。 Relationshipモデルの記述 relationship.rb class Relationship < ApplicationRecord belongs_to :following, class_name: "User" # RelationshipモデルとUserモデル(folloeing)との1対1の関係 belongs_to :follower, class_name: "User" # RelationshipモデルとUserモデル(follower)との1対1の関係 end 上記のように記述することで、Userモデルを便宜上following(いいね機能での「いいね」するUser)とfollower(いいね機能での「いいね」されるTweet)の2つに分けることができました。 ここで注目していただきたいのが、class_name: "User"という記述です。 class_name: "User"は、参照先のモデルクラス名を指定するために記述しています。 なぜ指定しなければならないかというと、関連名に基づいて参照先のモデルクラス名をRailsが推測しているからです。 今回は関連名にUserモデルのクラス名userを使わず、following及びfollowerとしているため、Railsによる関連名からの参照先モデルの推測ができないため、明示してあげる必要があります。 Userモデルの記述 app/models/user.rb class User < ApplicationRecord #フォローする側のUserから見て、フォローされる側のUserを(Relationshipを経由して)集める。なのでfollowing_id(フォローする側) has_many :active_relationships, class_name: "Relationship", foreign_key: :following_id # Relationship(active_relationshipsを経由して「follower」モデルのUser(フォローされた側)を集めることを「followings」と定義 has_many :followings, through: :active_relationships, source: :follower #フォローされる側のUserから見て、フォローしてくる側のUserを(Relationship経由で)集める。なのでfollower_id(フォローされる側) has_many :passive_relationships, class_name: "Relationship", foreign_key: :follower_id # Relationship(active_relationshipsを経由して「following」モデルのUser(フォローする側)を集めることを「followers」と定義 has_many :followers, through: :passive_relationships, source: :following end 続いてuser.rbファイルに上記の通り記述します。 has_many :active_relationships, class_name: "Relationship", foreign_key: :following_id has_many :passive_relationships, class_name: "Relationship", foreign_key: :follower_id の2つの記述は、いいね機能のuser.rb及びtweet.rbファイルの has_many :favorites に当たります。 いいね機能の際にはFavoriteモデルに対してUserモデルとTweetモデルからひとつずつ矢印が伸びていましたが、今回は、Userモデルのfollowing(いいね機能での「いいね」するUser)とfollower(いいね機能での「いいね」されるTweet)からひとつずつ矢印が伸びることになるのでUserモデルに2通り記述します。 2通り書く際に両方の関連名をrelationshipsと同名にすることはできませんので、relationship.rbの時と同様に関連名を変えていきます。 今回は has_many :active_relationships, class_name: "Relationship" has_many :passive_relationships, class_name: "Relationship" としています。 文の最後に foreign_key: :following_id foreign_key: :follower_id とありますが、これは外部キー(foreign key)を指定するための記述です。 指定する理由は、class_nameによる参照先モデル名の指定同様、関連名がモデル名の複数形(relationships)ではないため、Railsが推測をすることができないからです。 本来、関連名→モデル名→外部キーと関連付けられているものが、関連名を変更したことにより、関連性がなくなっています。 また、外部キーの名前は原則、親モデル名_idとされており、この通り命名することで親モデルと外部キーが関連付けられます。しかし、今回はfollowing_id、follower_idであり同名のモデルは存在しませんので親モデルを推測することはできません。(※relationship.rbにおいて便宜上Userモデルをfollowingとfollowerの2つに分けていますが、実際にUserモデルが2つになったわけではないことにご注意ください。) ここで、なぜ同様に関連名を変更したrelationship.rbにおいて外部キーを指定していないのか疑問に思う方もいらっしゃると思います。 これは、Relationshipモデルを親モデルとする外部キーが他のモデルに設定されていないからです。 (もし、外部キーが設定されていれば、同様に外部キーを指定する必要があるでしょう。) 続いて以下の記述について解説します。 has_many :followings, through: :active_relationships, source: :follower has_many :followers, through: :passive_relationships, source: :following 上記の記述は、いいね機能のuser.ebの has_many :favorite_tweets, through: :favorites, source: :tweet に当たります。 has_many, throughはthroughの後に記述したモデルを経由してデータを取得できるようにします。今回の場合は、Userモデル(following又はfollowers)からRelationshipモデル(active_relationships又はpassive_relationships)を経由してUserモデル(following又はfollowers)のデータを取得することができます。(※Relationshipモデルは便宜上、active_relationshipsとpassive_relationshipsに分かれていますが、実際に2つに分かれたわけではないことにご注意ください。Relationshipモデル自体は1つのままです。) 本記述は、フォロー/フォロワー一覧画面を作成する際に必要になります。 Relationshipモデルを経由しますが、実質的にはUser(following)モデルとUser(follower)モデルとの多対多の関連付けをしています。 また、今回も関連名が変更されていますので、参照先モデルを指定しなければなりません。 指定のための記述が、 source: :follower source: :following になります。 先ほどまでは、class_nameで指定していましたが、ここではsourceを使います。 class_nameとsourceはどちらも参照先モデルを指定するために使用しますが、class_nameは1対多の関係の際にsourceは多対多の関係の際に使用します。 has_manyは1対多の関係ですが、has_many, throughは多対多の関係になりますので、ここではsourceを使います。 上記の記述により、Relationshipモデルを経由したデータの取得が可能になるため、 本来は、user.rbファイルにfollowed_by?の定義をする必要がありますが、ここでは割愛いたします。 各モデルの関係 上述の通り設定をすると各モデルの関係性は以下の通りとなります。 Userモデル{〜〜〜(following)〜〜〜 ⇄ 多対多 ⇄ 〜〜〜(follower)〜〜〜} ⇅1対1及び1対多 ⇅1対1及び1対多 〜〜〜〜〜〜〜〜〜〜〜〜〜〜Relationshipモデル〜〜〜〜〜〜〜〜〜〜〜〜〜〜 いいね機能と同様に三角形の関係となっております。 上記のようになれば、フォローする側のユーザーとフォローされる側のユーザーが区別できるため、うまく実装できるのではないでしょうか。 まとめ 長くなってしましましたので最後にポイントをまとめます。 - User同士を関連付ける必要があり、いいね機能と同じやり方では、フォローする側とされる側を区別できない。区別するためには、擬似的にUserモデルとRelationshipモデルを2つに分ける必要がある。 - 関連名を変更する場合には、class_name又はsourceで参照先モデルを指定する必要がある。また、必要があれば、foreign_keyで外部キーも指定する必要がある。 - フォロー/フォロワー一覧画面を作成するためには、has_many, throughを使ってUser(following)とUser(follower)の多対多の関連付けをする。
- 投稿日:2021-12-03T08:27:55+09:00
【Rails】フォロー/フォロワー機能のアソシエーションについて【初学者の疑問点を詳しめに解説】
はじめに 本記事は、Railsの学習を始めて1ヶ月の初学者が、学習を進めていて疑問に思った点について調べた結果を備忘録も兼ねてまとめたものです。 そのため、記事の内容に誤りが含まれている可能性があります。ご容赦ください。 間違いを見つけた方は、お手数ですが、ご指摘いただけますと幸いです。 今回の疑問点 今回の疑問点は、 フォロー/フォロワー機能のアソシエーションについて です。 現在、Twitterクローンを作成しており、フォロー/フォロワー機能の実装の際に上記について疑問を抱きました。 疑問点についての解説 いいね機能実装時との比較 おそらく、多くの方がフォロー/フォロワー機能実装の前にいいね機能の実装に取り組んでいることと思いますので、いいね機能と比較して確認していきます。 いいね機能のアソシエーションの記述 Favoriteモデルの記述 app/models/favorite.rb class Favorite < ApplicationRecord belongs_to :user # FaviriteモデルとUserモデルの1対1の関係 belongs_to :tweet # FavoriteモデルとTweetモデルの1対1の関係 end Userモデルの記述 app/models/user.rb class User < ApplicationRecord has_many :tweets # UserモデルとTweetモデルの1対多の関係 has_many :favorites # UserモデルとFavoriteモデルの1対多の関係 has_many :favorite_tweets, through: :favorites, source: :tweet # UserモデルとTwitterモデルの多対多の関係(Favoriteモデル経由) end Tweetモデルの記述 app/models/tweet.rb class Tweet < ApplicationRecord belongs_to :user # TweetモデルとUserモデルとの1対1の関係 has_many :favorites # TweetモデルとUserモデルとの1対多の関係 end 各モデルの関係 上記の通り記述することで各モデルを以下の通り関連付けています。 〜〜〜Userモデル〜〜〜 ⇄ 多対多 ⇄ 〜〜〜Tweetモデル〜〜〜 ⇅1対1及び1対多 ⇅1対1及び1対多 〜〜〜〜〜〜〜〜〜〜〜〜〜〜Favoriteモデル〜〜〜〜〜〜〜〜〜〜〜〜〜〜 各モデルを頂点とした三角形の相互関係となっています。 フォロー/フォロワー機能のアソシエーションの記述 記述を見る前に各モデルの関係性から確認します。 フォロー/フォロワー機能の場合には以下の通りとなります。 〜〜〜〜〜Userモデル〜〜〜〜〜 ⇅ 1対多の関係 〜〜〜〜Relationshipモデル〜〜〜〜 このまま、実装しようとしてもフォローする側のユーザーとフォローされる側のユーザーを区別することができないため、うまくいきません(フォロー数とフォロワー数を表示したいのにフォロー数とフォロワー数の合計しか出せない等)。 うまく実装するためには、先程のいいね機能の時のように三角形の関係を作ってあげる必要があります。 そこで以下のように記述します。 Relationshipモデルの記述 relationship.rb class Relationship < ApplicationRecord belongs_to :following, class_name: "User" # RelationshipモデルとUserモデル(folloeing)との1対1の関係 belongs_to :follower, class_name: "User" # RelationshipモデルとUserモデル(follower)との1対1の関係 end 上記のように記述することで、Userモデルを便宜上following(いいね機能での「いいね」するUser)とfollower(いいね機能での「いいね」されるTweet)の2つに分けることができました。 ここで注目していただきたいのが、class_name: "User"という記述です。 class_name: "User"は、参照先のモデルクラス名を指定するために記述しています。 なぜ指定しなければならないかというと、関連名に基づいて参照先のモデルクラス名をRailsが推測しているからです。 今回は関連名にUserモデルのクラス名userを使わず、following及びfollowerとしているため、Railsによる関連名からの参照先モデルの推測ができないため、明示してあげる必要があります。 Userモデルの記述 app/models/user.rb class User < ApplicationRecord #フォローする側のUserから見て、フォローされる側のUserを(Relationshipを経由して)集める。なのでfollowing_id(フォローする側) has_many :active_relationships, class_name: "Relationship", foreign_key: :following_id # Relationship(active_relationshipsを経由して「follower」モデルのUser(フォローされた側)を集めることを「followings」と定義 has_many :followings, through: :active_relationships, source: :follower #フォローされる側のUserから見て、フォローしてくる側のUserを(Relationship経由で)集める。なのでfollower_id(フォローされる側) has_many :passive_relationships, class_name: "Relationship", foreign_key: :follower_id # Relationship(active_relationshipsを経由して「following」モデルのUser(フォローする側)を集めることを「followers」と定義 has_many :followers, through: :passive_relationships, source: :following end 続いてuser.rbファイルに上記の通り記述します。 has_many :active_relationships, class_name: "Relationship", foreign_key: :following_id has_many :passive_relationships, class_name: "Relationship", foreign_key: :follower_id の2つの記述は、いいね機能のuser.rb及びtweet.rbファイルの has_many :favorites に当たります。 いいね機能の際にはFavoriteモデルに対してUserモデルとTweetモデルからひとつずつ矢印が伸びていましたが、今回は、Userモデルのfollowing(いいね機能での「いいね」するUser)とfollower(いいね機能での「いいね」されるTweet)からひとつずつ矢印が伸びることになるのでUserモデルに2通り記述します。 2通り書く際に両方の関連名をrelationshipsと同名にすることはできませんので、relationship.rbの時と同様に関連名を変えていきます。 今回は has_many :active_relationships, class_name: "Relationship" has_many :passive_relationships, class_name: "Relationship" としています。 文の最後に foreign_key: :following_id foreign_key: :follower_id とありますが、これは外部キー(foreign key)を指定するための記述です。 指定する理由は、class_nameによる参照先モデル名の指定同様、関連名がモデル名の複数形(relationships)ではないため、Railsが推測をすることができないからです。 本来、関連名→モデル名→外部キーと関連付けられているものが、関連名を変更したことにより、関連性がなくなっています。 また、外部キーの名前は原則、親モデル名_idとされており、この通り命名することで親モデルと外部キーが関連付けられます。しかし、今回はfollowing_id、follower_idであり同名のモデルは存在しませんので親モデルを推測することはできません。(※relationship.rbにおいて便宜上Userモデルをfollowingとfollowerの2つに分けていますが、実際にUserモデルが2つになったわけではないことにご注意ください。) ここで、なぜ同様に関連名を変更したrelationship.rbにおいて外部キーを指定していないのか疑問に思う方もいらっしゃると思います。 これは、Relationshipモデルを親モデルとする外部キーが他のモデルに設定されていないからです。 (もし、外部キーが設定されていれば、同様に外部キーを指定する必要があるでしょう。) 続いて以下の記述について解説します。 has_many :followings, through: :active_relationships, source: :follower has_many :followers, through: :passive_relationships, source: :following 上記の記述は、いいね機能のuser.ebの has_many :favorite_tweets, through: :favorites, source: :tweet に当たります。 has_many, throughはthroughの後に記述したモデルを経由してデータを取得できるようにします。今回の場合は、Userモデル(following又はfollowers)からRelationshipモデル(active_relationships又はpassive_relationships)を経由してUserモデル(following又はfollowers)のデータを取得することができます。(※Relationshipモデルは便宜上、active_relationshipsとpassive_relationshipsに分かれていますが、実際に2つに分かれたわけではないことにご注意ください。Relationshipモデル自体は1つのままです。) 本記述は、フォロー/フォロワー一覧画面を作成する際に必要になります。 Relationshipモデルを経由しますが、実質的にはUser(following)モデルとUser(follower)モデルとの多対多の関連付けをしています。 また、今回も関連名が変更されていますので、参照先モデルを指定しなければなりません。 指定のための記述が、 source: :follower source: :following になります。 先ほどまでは、class_nameで指定していましたが、ここではsourceを使います。 class_nameとsourceはどちらも参照先モデルを指定するために使用しますが、class_nameは1対多の関係の際にsourceは多対多の関係の際に使用します。 has_manyは1対多の関係ですが、has_many, throughは多対多の関係になりますので、ここではsourceを使います。 上記の記述により、Relationshipモデルを経由したデータの取得が可能になるため、 本来は、user.rbファイルにfollowed_by?の定義をする必要がありますが、ここでは割愛いたします。 各モデルの関係 上述の通り設定をすると各モデルの関係性は以下の通りとなります。 Userモデル{〜〜〜(following)〜〜〜 ⇄ 多対多 ⇄ 〜〜〜(follower)〜〜〜} ⇅1対1及び1対多 ⇅1対1及び1対多 〜〜〜〜〜〜〜〜〜〜〜〜〜〜Relationshipモデル〜〜〜〜〜〜〜〜〜〜〜〜〜〜 いいね機能と同様に三角形の関係となっております。 上記のようになれば、フォローする側のユーザーとフォローされる側のユーザーが区別できるため、うまく実装できるのではないでしょうか。 まとめ 長くなってしましましたので最後にポイントをまとめます。 - User同士を関連付ける必要があり、いいね機能と同じやり方では、フォローする側とされる側を区別できない。区別するためには、擬似的にUserモデルとRelationshipモデルを2つに分ける必要がある。 - 関連名を変更する場合には、class_name又はsourceで参照先モデルを指定する必要がある。また、必要があれば、foreign_keyで外部キーも指定する必要がある。 - フォロー/フォロワー一覧画面を作成するためには、has_many, throughを使ってUser(following)とUser(follower)の多対多の関連付けをする。
- 投稿日:2021-12-03T01:14:53+09:00
メーカー技術者からエンジニアになってみて
人生充実してますか?? こんにちは! エンジニアになって、過ごしている時間全てが自己投資になっていると感じている、 takaakiといいます。 26歳です。 前職の内容 工学部を卒業後、地元の電子部品メーカーで技術総合職として、ゼネラリストをやっていました。 業務は、こんな感じ。 ・新製品の開発 ・製造工程の設計 ・製品の性能向上活動 ・品質管理 研究設備や工場に行っての業務が多かったため、毎日片道40分かけて出勤していました。 当時不満点があったかと言われると、特にありませんでした。 逆に、上司や先輩方、同期、後輩方にはとても恵まれて、 すごく幸せな環境だったな、と感じています。 エンジニアになろうと思ったきっかけ 3つあります。 ①人生において、時間と場所に縛られたくないと思ったから ②自身の市場価値を上げないと、これからの時代豊かに安定して暮らすのは苦しいと感じたから ③将来的に自身のビジネスを持ちたいと思ったから まず1つ目。 なぜ縛られたくないか? それは生きているうちにできるだけ沢山の経験をしたい。 そして、大切な人たちとできるだけ一緒にいたい。 そう考えるからです。 前職は基本的に毎日出社でした。 業務上、設備がないと仕事にならないので、しょうがありませんでした。 しかし、コロナになって家でゆっくりする時間が増え、 それと同時に自分の人生について考える時間が増えました。 そこで、本やYouTubeで自由な働き方をしている方達がいることを知りました。 初めはお決まりの、「そんなことできる訳ない」と考えていましたが、 心は正直で、そんな人生に惹かれました。 仕事しながら時には世界を回ったり、 時には大切な人たちの身近なところ居てあげたいと思っています。 次に2つ目。 なぜ市場価値が上がらないことに危機感を感じたのか? 一番影響を受けたのは『LIFE SHIFT 100年時代の人生戦略』を読んだことです。 今の仕事を80歳まで続けられるのか? 今の会社がなくなったら、もしくは居られなくなったら、 自分は家族を支えることができるのか? 近年はドックイヤーと言われ、 時代の流れが凄まじく、 大手の大企業でも安泰とは言えない時代となってきました。 経済的に安定するには、 「誰かに依存している状態」でいることはとてもリスクが高いです。 会社を飛び出しても周りから「来てほしい」と思ってもらえるような人間になる。 それこそが、これからの時代を強く生き抜いていく基本戦略だと思っています。 最後に3つ目。 なぜビジネスを持ちたいのか。 素直に、経済力をつけたい、 そして、豊かで生き生きとした社会作りをしていきたい。 こういうことを、パワーを持って実現していきたいと考えているからです。 何をやりたいかは具体的になっていませんが、 多くの人にもっと自分の心に素直な生き方ができる世の中にしていきたいと考えています。 僕自身が周りに言われるがままの人生を、25歳まで続けてきてしまいました。 もっと早く行動できていれば、 もっと豊かな人生にできていたと感じています。 ただ、人生いつからでも遅くはないと思います。 今日が一番若い日だと思って、一歩踏み出しやすい社会にできたらなと思っています。 エンジニアとして働いてみた感想(前職と比べてどう変わったか) 圧倒的に毎日が充実しています。 全ての時間が自身の将来への自己投資になっていると感じます。 事実、スキルがアップし、実務経験期間も積み上がっています。 日々市場価値が上がっていると感じられることで、 漠然とした不安は一切なく、 半年後、一年後、5年後がとても楽しみに日々過ごしています。 ただ、求められる仕事のレベルはとても高いです。 楽な仕事だとは思わない方がいいです。 独学の時点で9割やめていくような分野です。 一方、参入障壁が高いからこそ需要が高いまま推移していくし、 現在の会社でエンジニアを採用したいと思っても、 なかなか来てもらえないという実態から、 エンジニアの需要はさらに高まっているとヒシヒシと感じています。 このことから、確かに独学、エンジニアとしての業務は大変ですが、 自分の時間や体力、お金のリソースと投入する価値は十分にあったと思っています。 今後はどんなエンジニアを目指したいか 社会に対して真の価値があるものを提供できるエンジニアになりたいと思っています。 聞いた話として、「DXの一人歩き」があります。 「流行っているから焦ってDXしたが、むしろ使いにくくて業務の改善になっていない」 こういったことが散見されるようですが、 これでは目的を達成しているとは到底思えません。 しっかりとクライアントや社会の要求を聞き、 時にはこちらから情報を提供して、 本当に価値のあるシステムを構築できるエンジニアになりたいと思っています。 また、それをエンジニアで終わりにせずに、 ビジネスまで持っていきたいと考えています。 最後まで読んでいただき、ありがとうございました! 読者の方の人生に、少しでも良い影響を与えることができたら幸いです。
- 投稿日:2021-12-03T00:01:29+09:00
【LINE×Rails】Rails初学者も作れるLINE Botアプリケーション
概要 この記事では、先日リリースしたReLINE【"猫さん"】で得た LINE Messaging API の知見を使って、投稿に応じて返信するテキストを変化させるLINE Botアプリケーションを作成します。 記事の内容に沿って実際に手を動かして取り組んでもらうと、今回私が作成したものと同じものができるので、お時間のある方はチャレンジしてもらえると嬉しいです! 【注意点】 この記事では上記のLINE Botアプリケーションを作成する流れを記載していきます。そのため技術的な話はほとんど出てきませんが、身近なLINEアプリを使ってプログラミングへの楽しさや作成する面白さなどを感じてもらえたら幸いです。 【今回挑戦する4ケース】 1."おまじない"(アブラカタブラ、チチンプイプイ、ヒラケゴマ のいずれかテキスト)が投稿された場合 => It is Omajinaiと返す 2.rubyというテキストが投稿された場合 => Is it Programming language? Ore?と返す 3.スタンプが投稿された場合 => Thanks!!と返す 4.上記以外のイベントが発生した場合 => .......と返す 尚、今回はngrokを使用して、Railsは開発環境のままでも楽しめるようにします。 (以下の画像は完成イメージです) 本記事の全体の流れ 1.LINE側の設定 2.Rails側の設定 3.ngrokの立ち上げ 4.その他諸々の設定 1.LINE側の設定 【大まかな流れ】 LINEアカウントを使用してLINE Developersにログイン プロバイダーの作成 Messaging APIを選択から作成まで チェンネルIDとチェンネルシークレットの値を控える チャンネルアクセストークンを発行して値を控える ■ LINEアカウントを使用してLINE Developersにログイン LINE Developersにアクセスして、右上にある「ログイン」からLINEアカウントを使用してログインします。 ■ プロバイダーの作成 新規プロバイダーを作成します。「作成」ボタンをクリックします。新規プロバイダー作成がポップアップで表示されるので、プロバイダー名を入力して「作成」をクリックしてください。 ■ Messaging APIを選択から作成まで 新規プロバイダーを作成できたら、Messaging APIを選択してください。 入力画面に遷移したら必須項目を埋めて、「作成」ボタンをクリックしてください。 【必須項目に関しての補足】 チャンネルの種類:Messaging API プロバイダー:先程作成したプロバイダー名 チャンネル名:(好きなチャンネル名を入力) チェンネル説明:(適宜入力) 大業種:(分類の中から選択) 小業種:(分類の中から選択) メールアドレス:自分のメールアドレス LINE公式アカウント利用規約の内容に同意します:チェックを入れる LINE公式アカウントAPI利用規約の内容に同意します:チェックを入れる 以下の内容でMessaging APIチャンネルを作成しますか?というポップアップが表示されたら「OK」をクリックし、続いて表示される情報利用に関する同意については「同意する」をクリックしてください。 ■ チェンネルIDとチェンネルシークレットの値を控える 下記画像の緑色で隠している2箇所をコピーして控えておいてください(後程Railsの設定のところで使用します)。 チャンネルID チャンネルシークレット ■ チャンネルアクセストークンを発行して値を控える タグを「Messaging API設定」に切り替えて、チャンネルアクセストークン(長期)を発行し、こちらもコピーして控えておきます(後程Railsの設定のところで使用します)。 以上で一旦LINE側の設定は終了になります。 2.Rails側の設定 【大まかな流れ】 開発環境 rails new からサーバー起動確認まで Gem(line-bot-api)を加える ルーティングの設定 credentials.yml.encの編集 コントローラーの設定 development.rbにngrokを使用する設定を追加 ■ 開発環境 ● Ruby:3.0.2 ● Ruby on Rails:6.1.4.1 * credentials.yml を使用するのでRailsのバージョンが5.2以上であれば大丈夫です。Rubyに関してもRailsが動けば問題ないかと思われます。 ■ rails new からサーバー起動確認まで では、早速rails new でアプリケーションを立ち上げて、ちゃんとサーバーが起動するかを確認しましょう。 $ rails new hello_hogefuga $ cd hello_hogefuga $ bin/rails s Yay! You're on Rails! が表示されたらOKです。 ■ Gem(line-bot-api)を加える この開発で必要なgem line-bot-apiをGemfileに加えて、bundle installを実行します。 # Gemfile gem 'line-bot-api' $ bundle install ■ ルーティングの設定 次にルーティングを設定します。 # config/routes.rb Rails.application.routes.draw do post '/callback', to: 'hoges#callback' end ■ credentials.yml.encの編集 今回は開発環境で動かすだけなので必要ではないかもしれませんが、万が一情報が漏れてしまうとマズイので、チャンネルID・チャンネルシークレット・チャンネルアクセストークン(長期)の値をcredentials.ymlに加えましょう。 *vimの使い方は各自で調べてください。 $ EDITOR='vim' bin/rails credentials:edit channel_id: 11111 # 先程控えたチャンネルID channel_secret: 'hogehogehoge' # 先程控えたチャンネルシークレット channel_token: 'fugafugafugafugafugafuga' # 先程控えたチャンネルトークン(長期) ■ コントローラーの設定 次にコントローラーを作成し、config/routes.rbで設定したアクションの中身を記述しましょう。 *今回は体験重視のため、ロジック部分もコントローラーに記載しております。 $ bin/rails g controller hoges (以下のコントローラーの記述は公式GitHUbを参考に少し手を加えております) # app/controllers/hoges_controller.rb class HogesController < ApplicationController protect_from_forgery except: :callback OMAJINAI = /アブラカタブラ|チチンプイプイ|ヒラケゴマ/ def callback client = Line::Bot::Client.new do |config| config.channel_id = Rails.application.credentials.channel_id config.channel_secret = Rails.application.credentials.channel_secret config.channel_token = Rails.application.credentials.channel_token end body = request.body.read signature = request.env['HTTP_X_LINE_SIGNATURE'] return head :bad_request unless client.validate_signature(body, signature) events = client.parse_events_from(body) events.each do |event| message = case event when Line::Bot::Event::Message { type: 'text', text: parse_message_type(event) } else { type: 'text', text: '........' } end client.reply_message(event['replyToken'], message) end head :ok end private def parse_message_type(event) case event.type when Line::Bot::Event::MessageType::Text reaction_text(event) # ユーザーが投稿したものがテキストメッセージだった場合に返す値 else 'Thanks!!' # ユーザーが投稿したものがテキストメッセージ以外だった場合に返す値 end end def reaction_text(event) if event.message['text'].match?(OMAJINAI) 'It is Omajinai' # 定数OMAJINAIに含まれる文字列の内、いずれかに一致した投稿がされた場合に返す値 elsif event.message['text'].match?('ruby') 'Is it Programming language? Ore?' # `ruby`という文字列が投稿された場合に返す値 else event.message['text'] # 上記2つに合致しない投稿だった場合、投稿と同じ文字列を返す end end end *Line::Bot::Clientに用意されているメソッドに関しては、公式ドキュメントや公式GitHubなどを参照してください。 ■ development.rbにngrokを使用する設定を追加 次にngrokを使用できるように、development.rbに以下の記述を加えます。 # config/environments/development.rb require 'active_support/core_ext/integer/time' Rails.application.configure do # ===== 省略 ===== config.hosts << '.ngrok.io' end 以上でRails側の設定は完了です。 3.ngrokの立ち上げ 【大まかな流れ】 ngrokのインストール ngrokの起動とURL(https)を控える Railsサーバーを立ち上げ直す ■ ngrokのインストール HomebrewがインストールされているMacでしたら、以下のコマンドでngrokをインストールできます。 (Homebrewが入っていない方はこちらのページからインストールを行ってください) $ brew install ngrok ■ ngrokの起動とURL(https)を控える インストールが完了したら以下のコマンドを実行してngrokを立ち上げます。 $ ngrok http 3000 立ち上がると下記画像のようになるので、緑色の枠の部分(https.....)をコピーして控えておいてください(後程その他諸々の設定のところで使用します)。 ■ Railsサーバーを立ち上げ直す 2.Rails側の設定の最後でconfig/environments/development.rbを編集しているので、Railsサーバーを立ち上げ直します。 # 別にターミナルを開いてサーバーを立ち上げ直す $ bin/rails s *ngrokは立ち上げる度に付与されるURLが異なりますので、都度控えて後述のWebhook URLの編集部分を変更してください。 以上でngrokの立ち上げは完了です。 4.その他諸々の設定 【大まかな流れ】 Webhookの設定 Webhookの利用を有効にする Webhook URLを編集 検証を行う 応答設定 グループ・複数人チャットへの参加を許可する:無効 応答メッセージ:無効 あいさつメッセージ:無効 ■ Webhookの設定 LINE Developersに戻り、残りの設定を行いましょう。 まずはMessaging API設定のタグをクリックし、Webhookの利用をアクティブにします。 次に、Webhook URL横にある「編集」ボタンをクリックし、表示された入力画面にngrokで控えた「https://.....ngrok.io」の値を貼り付け、その後ろにRails側で設定したルーティングのURLを加えます。 https://.....ngrok.io/callback 入力が整ったら「更新」ボタンをクリックしてください。 ここまでの設定で問題が無ければ「検証」ボタンを押すと成功のポップアップが表示されるので確認してみましょう。 もし成功以外が表示されてしまった際は、rails c を使ってcredentials.ymlで設定した値がちゃんと呼び出せるか、入力している値に差異がないか、その他設定で打ち間違いがないかなどなど、一度確認してみてください。 # credentials.ymlがちゃんと呼び出せるかの確認方法 $ rails c $ Rails.application.credentials.channel_id # チャンネルID $ Rails.application.credentials.channel_secret # チャンネルシークレット $ Rails.application.credentials.channel_token # チャンネルトークン(長期) ■ 応答設定 「検証」で成功が表示されたら、応答メッセージが表示されている行の右側にある「編集」ボタンをクリックしてください。 応答設定のページに遷移したら、基本設定、詳細設定の中身を以下の画像のように修正してください。 【基本設定・詳細設定の補足】 応答モード:Bot あいさつメッセージ:オフ 応答メッセージ:オフ Webhook:オン 修正を終えたら完了です。 LINE DevelopersのMessaging API設定のところで表示されているQRコードを、スマートフォンのLINEアプリで読み込んで友だち追加を行ったら、LINE Botと1対1のトークルーム内でアブラカタブラ・ruby・スタンプを投稿してみましょう。 (この記事の始めに掲載した画像と同じようなものが表示されたのではないでしょうか?) 最後に 作成は以上になります。お疲れ様でした! ちょっとだけ今回の機能部分の話をすると、受け取ったeventsの中身、特にmessage['text']の値をRubyのmatch?メソッドと正規表現を使って、match?メソッドの返り値がtrueであるか否かで条件分岐させています。(割りと単純ですよね!?) 今回は全てテキストメッセージを返す仕様になっていますが、別の値、例えば外部APIを叩いてその結果を返したりすることもできるかと思いますので、意欲のある方はチャレンジしてもらえればと思います?✨ 最後まで読んでいただき、ありがとうございました!? 参考URL ・LINE Developers Messaging API https://developers.line.biz/ja/docs/messaging-api/ ・GitHub line/line-bot-sdk-ruby https://github.com/line/line-bot-sdk-ruby