- 投稿日:2020-08-01T23:50:46+09:00
[Rails]よく作る、基本的な機能まとめ
はじめに
WEBエンジニアとして勉強していて、「Progateやドットインストールは終わったけど、これで仕事ができるようになるの?」と疑問に思っている人向けに、個人的によく作る、基本的な機能をまとめてみました。
ここでまとめている機能を一人で作れるようになれば、そのスキルを応用してより発展的な機能を作ることもできるのではないでしょうか。
また、この記事で書いている機能は、単体で完結する1つの機能というよりは、他の機能と連携させて機能するといった場合もあります。
タイトルにRailsと書いてますが、この記事で書いている機能については大体のWEBサービスでよく作るものだと思います。
対象となる方
- これから仕事でRailsを書いていく予定の人
- WEBサービスってどんな機能があるのか知りたい人
注意事項
この記事では以下のことは詳しく扱いません。
- 具体的な実装方法(他に多くの参考情報があるため)
- Ajax
サンプルコードとして、ブログアプリを想定したコードを書いています
目次
個人的によく作る、基本的な機能を10個あげました。
- CRUD機能
- 検索機能
- パスワード認証機能
- SNS認証機能
- メール送信機能
- ファイルアップロード機能
- 管理者機能
- 権限機能
- 論理削除機能
- API機能
それぞれの機能の概要と主に連携する機能、そして機能を提供するgemについて説明していきます。
それでは順番にいきましょう!
CRUD機能
概要
CRUD機能は、最も基本となる機能です。CRUDは「クラッド」と呼びます。
以下の4つの機能で構成されています。
- 登録機能(Create)
- 参照機能(Read)
- 変更機能(Update)
- 削除機能(Delete)
4つの頭文字をとってCRUDと言われています。
それぞれ簡単に解説します。
- 登録機能(Create)
データベースに情報を登録するための機能です。
SQLだとINSERT
が実行されます。
基本的には情報を入力するフォームと登録するためのボタンがあり、ボタンを押すことで入力された情報をデータベースに保存するといった形が多いです。注意点として、特定のページにアクセスしたときにPV数を記録する、といった仕様のときはフォームはないので、データを表示する処理の中でデータベースに保存するといった必要があります。
posts_controller.rbdef create @post = Post.new(post_params) if @post.save redirect_to @post, notice: 'ブログを投稿しました' else render :new end end
- 参照機能(Read)
データベースから情報を取り出すための機能です。
SQLだとSELECT
が実行されます。
基本的には情報を表示するページで使います。注意点として、情報は何度も表示されることが多いので、表示すると同時に他の情報の登録や変更をするようなコードを書いた場合は、データが増えても本当に問題ないか検討したほうがいいと思います。
posts_controller.rbdef index @posts = Post.all end def show @post = Post.find(params[:id]) end
- 変更機能(Update)
データベースにある情報を変更するための機能です。
SQLだとUPDATE
が実行されます。
データベースに既にある情報に対して、情報を入力するフォームと変更するためのボタンがあり、ボタンを押すことで入力された情報で上書きします。
登録機能(Create)と似たようなコードになることがあります。posts_controller.rbdef update @post = Post.find(params[:id]) if @post.update(post_params) redirect_to @post, notice: 'ブログを更新しました' else render :edit end end
- 削除機能(Delete)
データベースにある情報を削除するための機能です。
SQLだとDELETE
が実行されます。
データベースに既にある情報に対して、削除したいときに使います。注意点として、削除するときに関連する他の情報を消したほうが良いときがあります。
Railsだとdependentオプションを追加することで対応できます。posts_controller.rbdef destroy @post = Post.find(params[:id]) @post.destroy redirect_to posts_url, notice: 'ブログを削除しました' endpost.rbhas_many :comments, dependent: :destroy #=> 'dependent: :destroy'を追加すると、postを削除したときに関連するcommentsも全て削除される主に連携する機能
- ほとんど全部の機能
gem
- 特になし
- RailsのScaffoldコマンドなら自動的にCRUD機能が作られます
検索機能
概要
検索機能はデータベースに保存されている情報の中から、特定の条件に一致するデータを取得するときに使います。
CRUDの参照機能(Read)と合わせて作ることが多いです。
SQLだとWHERE
を指定してSELECT
が実行されます。Railsだと一覧画面に検索条件を入力するフォームがあることが多いです。
あと個人的には、検索条件をリセットするボタンをつけたほうが良いと思います。
これは単純に一覧画面へのリンクにして、ボタンの名前を「検索条件をリセット」でいいと思います。index.html.erb<%= link_to '検索条件をリセット', posts_path %>主に連携する機能
gem
パスワード認証機能
概要
パスワード認証機能は、メールアドレスとパスワードの2つを入力してデータベースに保存されているユーザーと一致したらログインさせるといった機能になります。
SQLだとWHERE
を指定してSELECT
が実行されます。
具体的には、まず入力されたメールアドレスに一致するユーザーを取得して、入力されたパスワードがあっているかを判定します。
パスワードは暗号化してデータベースに保存します。Railsだと
ログイン状態
にするには、sessionにユーザー情報を保存することが多いです。sessions_controller.rbdef create user = User.find_by(email: params[:user][:email].downcase) if user && user.authenticate(params[:user][:password]) session[:user_id] = user.id redirect_to root_url else render :new end end主に連携する機能
gem
SNS認証機能
概要
SNS認証機能は、FacebookやGoogle、TwitterなどのSNSに登録されているユーザー情報を取得して、自分で作ったアプリにログインさせるといった機能になります。
各SNSでは、アプリケーションの登録作業が必要です。
例えば、Facebookでは、開発者登録をしてアプリ登録を行うことでSNS認証に必要なアプリIDとSecretIDを取得できます。それらを環境変数で読み込むといったことが多いです。
また、SNSから取得できるユーザー情報は、SNSによって形式が異なることがほとんどです。Railsでは、omniauthを使うことでそういった異なる形式を一つの統一された形式に変換してくれます。
主に連携する機能
gem
メール送信機能
概要
メール送信機能は、何らかのアクションがあった時にユーザーに通知するためにメールを送信といった機能になります。
例えば、会員登録したユーザーにメールを送ったり、重要な情報をリマインドするためにメールを送ったりします。
メールを送る前にテンプレートを用意しておき、テキスト形式またはHTML形式でメールを送信します。
HTML形式ではCSSを使ってリッチなコンテンツを送信することができますが、CSSはインラインで書いたり、テーブルレイアウトを使ったほうが良い等、いくつかポイントがあります。Railsでは、メール送信をするためにはSMTPの設定をする必要があり、個人的にはSendgridを使うことが多いです。
開発環境では実際にメールを送信しなくても、本文を確認することができる便利なgemもあります。主に連携する機能
gem
ファイルアップロード機能
概要
ファイルアップロード機能は、ユーザーが選択した画像ファイル等をアップロードできる機能になります。
例えば、プロフィール画像を変更したり、サムネイル画像を変更したり、またはPDFファイルを添付するといった使い方があります。
基本的にはアップロードしたファイルは、S3などのクラウドストレージに保存するようにして、アプリ内では保持しないようにします。
また、ファイルを選択した時にJavascriptでプレビュー表示をすることも多いです。Railsでは標準でActiveStorageを使ったシンプルなアップロード機能が提供されています。
より細かいカスタマイズが必要な場合は、carrierwaveなどのgemを使うこともあります。主に連携する機能
gem
管理者機能
概要
管理者機能は、CMSなどのように、アプリ内の情報を管理するために管理者を用意する機能です。
ユーザーが見る画面と、管理画面といった形で分けて実装することがあります。
基本的には、管理者アカウントを作成して、各データをCRUDで操作したり、メール送信を行ったりしますが、管理するための多くの機能が含まれていることが多いです。Railsでは、gemを使ったりnamespaceを使ってユーザーと管理者を分けるといった方法があります。
routes.rb# 管理者のルーティング(全ての操作ができる) namespace :admin do resources :posts end # ユーザーのルーティング(閲覧のみできる) resources :posts, only: %i[index show]主に連携する機能
gem
権限機能
概要
権限管理は、ユーザーによって出来ること、出来ないことを管理するための機能です。
例えば、管理者はデータの削除ができるけど、一般は削除できないといった感じです。
また、管理者には削除ボタンを表示するけど、一般には表示しないというように見た目も気にする必要があります。
よくあるのが、ユーザーに管理者フラグのようなカラムを追加して判断するといったものがあります。Railsでは、自分で実装するかgemを使った方法があります。
個人的には権限が1つくらいなら自前で実装したほうが楽ですが、複数の権限がある場合にはgemを使ったほうがシンプルに実装できると思います。posts_controller.rb# 管理者のみ削除できる def destroy return redirect_to root_path unless current_user.is_admin? @post = Post.find(params[:id]) @post.destroy redirect_to posts_url, notice: 'ブログを削除しました' end主に連携する機能
gem
論理削除機能
概要
論理削除機能は、削除機能と違って、データを削除するのではなく、削除フラグなどを使って残しておく機能です。
CRUD機能の削除は物理削除といって、SQLだとDELETE
でしたが、論理削除ではUPDATE
を実行します。
削除フラグをUPDATEしてONにする感じです。Railsでは、自分で実装するかgemを使った方法があります。
どちらにも共通して、多いのはdeleted_at
のようなカラムを用意して削除した日付を記録するといった方法です。主に連携する機能
gem
API機能
概要
API機能は、アプリ内の情報を他のアプリに連携できるようにする機能です。
連携するときのフォーマットはJSON
が多いです。
例えば、カレンダーアプリとタスク管理アプリを作ったとして、カレンダーの予定が登録されたらタスク管理アプリに新規でタスクを追加したい、といった時にAPIで情報を連携するといった使い方があります。
他にも、データを管理するAPIサーバーと、データを表示するフロントエンドアプリケーションの2つを連携しながら開発することもあります。Railsでは標準でAPIモードという機能があり、これを利用することでAPI専用のアプリを作ることができます。
もちろん、通常のRailsアプリを作っていて、あとからAPI機能を追加するといったことも可能です。posts_controller.rbdef index posts = Post.all render json: posts end主に連携する機能
gem
まとめ
以上、いかがでしょうか?
今回は個人的によく作る、基本的な機能を10個あげました。
- CRUD機能
- 検索機能
- パスワード認証機能
- SNS認証機能
- メール送信機能
- ファイルアップロード機能
- 管理者機能
- 権限機能
- 論理削除機能
- API機能
特に
CRUD機能
は必ずといってもいいほど、よく作ります。
実際に、Railsアプリを開発するときも、CRUD機能を中心にして、他の機能を作っていくことが多いです。これらを一人で実装できれば、ある程度のスキルはあるのではないでしょうか。
また、初めて参加したプロジェクトでコードを読む時にも役に立つと思います。この記事について、何かありましたらお気軽にコメントください
- 投稿日:2020-08-01T23:39:42+09:00
mini_magickでmini_magick_processing_errorが出る
- 投稿日:2020-08-01T23:28:42+09:00
scopeでnilを返すとallが適用される
scopeで
find
やfind_by
、first
やlast
メソッドは使うべきではないuser.rbscope :normal, -> (type) { where(type: type).first }このようなscopeでnilを返すと、allが適用されてしまい、
User.all
が返ってしまう。
これを防ぐためには、scopeでは、ActiveRecord::Relationオブジェクトを返すようにする。
なので、ここでは、first
メソッドを付けずにwhere
メソッドで取得する。
whereメソッドでは対象のデータない場合は、nilは返さず、[]
を返す。
この[]
はActiveRecord::Relationオブジェクト。user.rbscope :normal, -> (type) { where(type: type) } def normal_record(type) normal(type).first endもしくは、scopeの呼び出し先で、
first
メソッドを使うのもあり。
normal(type).first
参考記事
- 投稿日:2020-08-01T23:03:34+09:00
[rails]deviseで登録・ログインができない問題
deviseで登録・ログインができない
deviseでユーザー登録とログイン画面を作成した後に、
デフォルト(email)以外の情報で登録・ログインをしたい時用の方法です。viewページのフォームを編集したのに、登録・ログインできないトラップがありますので、
参考にしてください。以下の場合、sing_upとaccount_updateのkeyを変更しています。
app/controllers/application.controller.rbclass ApplicationController < ActionController::Base before_action :configure_permitted_parameters, if: :devise_controller? protected def configure_permitted_parameters if resource_class == User devise_parameter_sanitizer.permit(:sign_up, keys: [:name,:email]) devise_parameter_sanitizer.permit(:sign_in,keys:[:email]) devise_parameter_sanitizer.permit(:account_update,keys:[:name,:email]) end end enddefore _action
before_action :configure_permitted_parameters, if: :devise_controller?before_actionは一種のフィルターのようなもので、application.contollerに記述することで、全てのコントローラーに対して、実行されます。
:devise_contoller?とはdeviseを生成した際にできるヘルパーメソッドの一つで、
deviseに関連する画面に飛ぶなら、ということです。
こうすることで全てのdevise画面でconfigure_permitted_parametersが起動します。つまりもしそれがdeviseのコントローラーだったら(devise_controller?というメソッドの返り値がtrueだったら)configure_permitted_parametersを呼び出すということです。
- 投稿日:2020-08-01T22:32:24+09:00
ec2にdocker環境を構築してみた
railsアプリをec2を利用して本番環境でデプロイした際に、dockerを導入してみました。
ec2にdocker及びdocker-composeをインストールするには以下を参考にしました。
https://qiita.com/y-do/items/e127211b32296d65803aec2にsshログインし、mysqlにマイグレーションするまでは以下が詳しく書いてあります。
https://qiita.com/naoki_mochizuki/items/f795fe3e661a3349a7ceMySQLの設定以降
amazon linuxではデフォルトがmariadbのため以下のエラーが出ます。
$ sudo service mysqld start Redirecting to /bin/systemctl start mysqld.service Failed to start mysqld.service: Unit not found.該当する方は以下を参考にmysqlをインストールしてください。
https://qiita.com/hamham/items/fd77bb0bb167a150dc8e#mysql57%E3%81%AE%E5%B0%8E%E5%85%A5また、ここまで手順どおり進めても以下のコマンドでエラーが出ます。
$ rake db:create RAILS_ENV=production rake db:migrate RAILS_ENV=production rake aborted! Mysql2::Error::ConnectionError: Unknown MySQL server host 'db' (2) Tasks: TOP => db:migrate (See full trace by running task with --trace)こちらはdockerの導入のためにdatabase.ymlの設定を以下のように変更したためです。
host: localhost #変更前 host: db #変更後そのため、データベースの作成にはdockerコマンドで実行する必要があります。
しかし、dockerコマンドを実行しようとすると以下のエラーになります。ERROR: Couldn’t connect to Docker daemon at http+docker://localhost – is it running?権限不足によるエラーのため、以下を参考に権限を変更してください。
https://qiita.com/dnnnn_yu/items/14a31721a2870b735938ここまで進んだらdockerコマンドが使用できると思いますので以下のコマンドを実行して下さい。
$ docker-compose up -d コンテナの作成(バックグラウンドで起動モード) $ docker ps -a 起動しているかの確認データベースを作成しようとするとエラーが出ます。
$ docker-compose run web rails db:create RAILS_ENV=production Mysql2::Error::ConnectionError: Access denied for user 'root'@'172.18.0.5' (using password: NO)これは参考サイトでdatabase.ymlのproduction環境のpasswordが設定されていないからです。
以下のように設定してあげましょう。// database.yml production: <<: *default host: <%= ENV['DB_HOST'] %> database: <%= ENV['DB_DATABASE'] %> username: <%= ENV['DB_USERNAME'] %> password: <%= ENV['DB_PASSWORD'] %>これでデータベースの作成ができると思います。
$ docker-compose run web rails db:create RAILS_ENV=production
- 投稿日:2020-08-01T22:30:35+09:00
[rails]deviseで作成したviewページの編集が反映されない
- 投稿日:2020-08-01T21:56:22+09:00
minio を同じサーバーの別dockerコンポーネントで立ち上げ、それをnginxプロキシーで外向きの https:// で設置して、fogでそのhttps:// URL をendpoint あるいはasset_hostとして指定すると、IPアドレスになって、サービスを検知できない問題
自分メモです。
正確には、minio を同じサーバーのdockerコンポーネントで立ち上げdocker network経由でも使えるようにした上で、それを同時にnginxプロキシーで外向きの https:// に設定しているので、CarrierWaveのfogでは、その https:// URL をendpointあるいはasset_hostとして指定した場合、Railsで画像アップロードしようとすると、その指定した https:// URL がIPアドレスに変換されてしまい、サービスを検知できない問題……
とにかくこの問題は起こりました。解決策は、StackOverflow の下記ページにありました。でも、たどり着くのに少し時間がかかった。
asset_hostはそれでいい。しかし、endpointはdockerのコンテナ名を指定すべきと。StackOverflow: Can't upload file through carrierwave fog-aws to minio (docker-compose)
qiita.rbCarrierWave.configure do |config| config.fog_provider = 'fog/aws' config.fog_credentials = { provider: 'AWS', aws_access_key_id: 'development', aws_secret_access_key: 'development', region: 'us-east-1', host: 'minio', # <- ここをコンテナ名に/サービスのURL(ドメイン名)だとIPアドレスになってしまう endpoint: 'http://minio:9000' # <- ここも } config.fog_directory = 'test' config.fog_public = false end
- 投稿日:2020-08-01T21:56:22+09:00
minio を同じサーバーのdockerコンポーネントで立ち上げている場合に、それをnginxプロキシーで外向きの https:// に設定して、carriawave経由のfogでそのhttps;//ドメインをendpoint あるいはasset_hostとして指定して、Railsで画像アップロードしようとすると、ドメインがIPアドレスになって、サービスを検知できない問題
自分用のメモです。
Rails のstarageを未だ使っていないのがそもそもいけないのだろうか。
とにかくこの問題は起こりました。解決策は、StackOverflow の下記ページにありました。たどり着くのに少し時間がかかった。
StackOverflow: Can't upload file through carrierwave fog-aws to minio (docker-compose)
qiita.rbCarrierWave.configure do |config| config.fog_provider = 'fog/aws' config.fog_credentials = { provider: 'AWS', aws_access_key_id: 'development', aws_secret_access_key: 'development', region: 'us-east-1', host: 'minio', # <- コレ endpoint: 'http://minio:9000'# <- コレ } config.fog_directory = 'test' config.fog_public = false end
- 投稿日:2020-08-01T21:56:22+09:00
minio を同じサーバーのdockerコンポーネントで立ち上げている場合に、それをnginxプロキシーで外向きの https:// に設定して、CarrierWave経由のfogでそのhttps://ドメインをendpoint あるいはasset_hostとして指定して、Railsで画像アップロードしようとすると、ドメインがIPアドレスになって、サービスを検知できない問題
自分メモです。
タイトルは実際には
「minio を同じサーバーのdockerコンポーネントで立ち上げdocker network経由でも使えるようにした上で、それを同時にnginxプロキシーで外向きの https:// に設定しているので、CarrierWave経由のfogでは、その https:// URL をendpointあるいはasset_hostとして指定した場合、Railsで画像アップロードしようとすると、その指定した https:// URL がIPアドレスに変換されてしまい、サービスを検知できない問題」とすべきなのですが……
とにかくこの問題は起こりました。解決策は、StackOverflow の下記ページにありました。でも、たどり着くのに少し時間がかかった。
asset_hostはそれでいい。しかし、endpointはdockerのコンテナ名を指定すべきと。StackOverflow: Can't upload file through carrierwave fog-aws to minio (docker-compose)
qiita.rbCarrierWave.configure do |config| config.fog_provider = 'fog/aws' config.fog_credentials = { provider: 'AWS', aws_access_key_id: 'development', aws_secret_access_key: 'development', region: 'us-east-1', host: 'minio', # <- ここをコンテナ名に/サービスのURL(ドメイン名)だとIPアドレスになってしまう endpoint: 'http://minio:9000' # <- ここも } config.fog_directory = 'test' config.fog_public = false end
- 投稿日:2020-08-01T20:59:24+09:00
Carrierwave導入
Gem file追加
Gemfilegem 'carrierwave' gem 'mini_magick'terminal$ bundle installUploader作成
terminal$ rails g uploader imageapp/models/gear.rbclass Gear < ApplicationRecord mount_uploader :image, ImageUploader endapp/uploaders/image_uploader.rbclass ImageUploader < CarrierWave::Uploader::Base include ~~ CarrierWave::MiniMagick ~~ process resize_to_fit: [400, 500] # それぞれコメントアウトされているので、コメントを外す。 def default_url 'board_placeholder.jpg' end # default画像をasset/imageに保存config/application.rbmodule GolfGearReview class Application < Rails::Application config.autoload_paths += Dir[Rails.root.join('app', 'uploaders')] end endCarrierwave:
https://github.com/carrierwaveuploader/carrierwaveDBテーブルへ画像保存用のカラムを追加
terminal$ rails g migration add_image_to_gear image:string $ rails db:migrategears_controller.rbprivate def gear_params params.require(:gear).permit(:name, :description, :club, :maker_id, :club_id, :image) end # :imageの受け渡しを許可new/edit action時のfile_field追加
app/views/gears/_form.html.erb<%= simple_form_for @gear do |f| %> <%= f.file_field :image %> <% end %>画像をindexに表示, link_to設定
app/views/gears/index.html.erb<% @gears.each do |gear| %> <%= link_to (image_tag gear.image.url), gear %> <% end %>
- 投稿日:2020-08-01T19:42:43+09:00
[Rails / IBM Watson] 使い方メモ
この記事は何?
別のサイトで記録していた内容を移設中です(移設作業進行中...)
※元はここに掲載していました。Text to Speech について
- IBMのコンソール画面でインスタンスを作成
username, password をメモしておく
参考(IBM)- audioファイル置き場生成
mkdir public/audios
- ソースを書く
app/controllers/hoge_controller.rb# ↓アクション def text_to_speech json_text = { text: '喋らせたいテキスト' }.to_json headers = { "Content-Type": 'application/json', "Accept": 'audio/wav', # 作成されるファイル形式を指定。 "params": { voice: 'ja-JP_EmiVoice' } # 言語の指定。日本人のえみさんに話していただいています。 } response = RestClient.post make_tts_url, json_text, headers # Encoding::UndefinedConversionError in StudiesController#request_voice # "\xFF" from ASCII-8BIT to UTF-8 # response.force_encoding('UTF-8') しておくことが必要 IO.write('public/audios/sample.wav', (response.force_encoding('UTF-8')).body) end private def make_tts_url user_name = '自分のusername' pass = '自分のpassword' end_point = 'stream.watsonplatform.net/text-to-speech/api' return "https://#{user_name}:#{pass}@#{end_point}/v1/synthesize" endapp/views/hoge.html.erb<%= audio_tag("sample.wav", autoplay: false, controls: true, type: 'audio/wav') %> <%# public/audios/sample.wavを再生してくれる。 %> <%# app/assets/audios に同名ファイルがあると、上手くいかないことがある。 %>感想 (※2018年01月28日当時)
機械音声(Watson)はまだまだぎこちないですね。
「はーい、ワトソン」と喋らせると、「わーい、わとそん」と発音したりします>д<
それから、ローマ字読みなんかを書くと、アルファベット一つずつ読み上げたりもします。
たとえば「hoge」を「ほげ」と呼んでほしいとことですが、「えいちおーじーいー」と読みます;。;
参考URL
- IBM Watson text-to-speechを試す(Ruby POST版)
https://qiita.com/xojan0120/items/5ee0a0805cf43ba38d04- IBM 公式 Text to Speech APIリファレンス
https://www.ibm.com/watson/developercloud/text-to-speech/api/v1/#introduction- ブラウザと音声ファイルの対応関係を調べる方法について(マイクロソフトのページ)
https://msdn.microsoft.com/ja-jp/library/gg589524(v=vs.85).aspx- 次にチャレンジしたいこと。他にも無料APIがあるらしい
- Railsでなく、javascriptでも再生できるらしい
https://mae.chab.in/archives/1975
- 投稿日:2020-08-01T18:13:21+09:00
【チーム開発にて】他メンバーのブランチに移動して、rails sした時のエラー【Rails】
参考対象者
- チーム開発初心者
- Railsでのアプリ開発にて、データベース関連のエラーで困ってる方
- Git初心者
環境
$ rails -v Rails 6.0.3.1$ ruby -v ruby 2.7.0p0 (2019-12-25 revision 647ee6f091) [x86_64-darwin19]$ git --version git version 2.27.0$ mysql --version mysql Ver 14.14 Distrib 5.7.29, for osx10.15 (x86_64) using EditLine wrapper状況
- 他メンバーのブランチに移動し、レビューするために挙動確認したかったが、サーバーを立ち上げるときにエラーが出た。
- どうやら、データベース関連のエラーらしい。
ActiveRecord::PendingMigrationError
上記を参考に、解決する。
ちなみに私は、$ rails db:migrateで解決。
でも、その後次のようなエラーが。。。
Multiple migrations have the name ~~~.
結論から言うと、~~~には、マイグレーションファイル名が入ります。
私の場合は、
Multiple migrations have the name CreateUsers.となって、自分のマイグレーションファイルを確認してみると、
$ rails db:migrate:status Status Migration ID Migration Name -------------------------------------------------- up 20200618162841 Create tweetposts up 20200620004226 Change tweetposts to tweets down 20200621075518 Create posts down 20200623102444 Change posts to chats down 20200627042358 Create users up 20200627080839 Create users up 20200627083356 Add column to users down 20200627220915 Change datatype content of chats up 20200703201452 ********** NO FILE ********** down 20200710035709 Add user id to tweetsありましたね、CreateUsersファイル。
察しの良い方はお分かりだと思いますが、エラー文の内容から「同名のマイグレーションファイルか存在しているから、どちらを参考にデータベース構築をしたら良いかわからないよ」という状況と推測できます。
なので、
down 20200627042358 Create users
こちらのファイルを削除し、$ rails db:migrate $ bundle exec rails sこれにて、サーバーを起動することができました!
ちなみに、、、
マイグレーションファイルの、
up 20200703201452 ********** NO FILE **********
気になりますよね??
これ、ないファイルを参考にデータベースを構築してるという不可思議な現象なので、削除してしまいたいですね。
これの原因は、マイグレーションをdownさせる前に、マイグレーションファイルを削除してしまったため起こるものなのですが、解決方法は下記をどうぞ。
- 投稿日:2020-08-01T16:02:35+09:00
deviseでのログイン画面実装
deviseでのログイン画面実装
1.Gemfileに「gem 'devise'」を追加
gem 'devise'2.追加したdeviseを、アプリケーションに読み込ませる。
※gemを追加した時に忘れてはいけない$ bundle install3.deviseの初期設定
$ rails g devise:installこれでdeviseを使う準備は完了。
4.Userモデルとマイグレーションファイルを作成
rails g devise モデル名の記述は、devise独自のルールです。$ rails g devise User5.データベースへ反映
新しくUserテーブルをデータベースへ反映させます。$ rails db:migrate6.deivse用のログイン画面を作成
$ rails g devise:views7.rails s -b 0.0.0.0でサーバー起動させ、画面確認
- 投稿日:2020-08-01T15:42:15+09:00
現場で学んだRailsリファクタリングの話
現場で学んだ話
Railsの開発で意識している一般的なアーキテクチャの話です。
fatコントローラー、fatモデルにならないように意識していることを備忘録メモ。乱文のため、適宜修正していきます。
コントローラーはシンプルに!!
コントローラーに追わせる責務は、究極的にこう考えています。
「インスタンスを作り、それに対してメソッドを実行して、レスポンスを返す」
これだけ。
こう言うのは最悪。
example.contoroller.rbこんなアクションとかありえないです def index @most_funded_projects = [] most_funded_projects = Project.most_funded.includes(:main_image) most_funded_projects.each do |most_funded_project| break if @most_funded_projects.count >= 3 if @site_project_descriptions[most_funded_project.id].present? @most_funded_projects << most_funded_project end end if @most_funded_projects.count < 4 current_site.published_projects.order(updated_at: :desc).each do |project| break if @most_funded_projects.count >= 3 @most_funded_projects << project end end endコントローラーにゴリゴリのビジネスロジックが書かれていますね。こんなコントローラーをかつて書いてたりして、注意されました。scafoldとかでコントローラーを作ると以下のようなシンプルな形のコントローラーができると思いますが、基本的にはこんな感じに沿って作るのが理想です。
example.rbdef index @friends = Friend.all respond_to do |format| format.html # index.html.erb format.json { render json: @friends } end end 要は究極的には、以下のような形が基本 @hoge = Hoge.new @hoge.hoge レスポンスを返すコントローラーが膨らんだ場合は、まずはモデルへ!!
コントローラーでは、そのモデルに紐づくインスタンスを作り、それに対してメソッドを実行するだけのイメージが重要でした。なので、一旦、あらゆるビジネスロジックはモデルに寄せることを考るといいです。
ただ、何でもかんでも寄せるのは別の問題が起きるので、モデルに寄せるのは、主に永続化に関わるものや、単一モデルの操作で完結するビジネスロジックを優先的に寄せるイメージです。モデルに実装するパターンは以下が一般的。
- モデルに紐付いたテーブルのレコード取得(ActiveRecordの操作)
- モデルに紐付いたテーブルをベースとした関連テーブルを含めたデータ取得
- モデルに紐付いたテーブルのデータ更新
- その他、単一モデルに紐づくビジネスロジック
単一テーブルの単純な更新など、シンプルな処理が主に該当し、それ以外は単一モデルで完結するビジネスロジックなどかなーと思います。
サービスクラスの活用
複数のモデル操作やファイル操作などの処理を、アトミックなビジネスロジックのひとまとめの単位としてサービス層として切り出します。
基本的には、以下のような形で、ActiveRecordにひもづかない(テーブルと1対1で紐づかない)通常のクラスを作ります。クラスを作り、既存のモデルから複数モデルに跨る複雑なロジックを分離する感じです。使い方は単純で、クラスのインスタンスを作り、それに対して生やしたメソッドを実行するだけです。
exmple.rbclass ExampleService def initialize .... end def example .... end endconsernやnamespaceを切ってmodelsディレクトリ配下にクラスを作る
紹介した方法でも、モデル、サービスクラスがfatになった場合は、modelsディレクトリ配下に、namespaceを切って、テーブルと1対1になっていない単なるクラスを作り、そこにfatになったモデルやサービスクラス内の汎用的に使えそうなメソッドをこちらに移管させることで、モデル、サービスクラスがfatになるのを防ぐテクニックもあります。また、consernを使って、冗長なものを一つにまとめたりします。例えば、models/{table name}/{use case}.rbを作成して、処理を切り出すイメージです。見通しをよくするためにも、 インスタンス変数の定義はControllerにて行うようにします。
Controller/Service/Modelの分担まとめ
- ControllerとService層は基本的に手続き処理※のみを行う。※手続き処理とは、関数の呼び出しや、CRUDにまつわる処理を行うこと
- ControllerとService層は返って来たデータを使うだけでロジックやデータの加工には関知しない
- Service層は、Controller内の処理が複数Modelにまたがる際に、複数のモデルに生えているメソッドをうまく活用して、サービスクラスにメソッドを作り、主にインスタンスに対して使用する。例えば、複数のモデルに跨る処理などでは、サービスクラス内で、元々、跨る想定の各モデルに生えているインスタンスメソッドを活用し、サービスクラス内でメソッドを生やし、コントローラーでは、サービスクラスのインスタンスを作って、それをレシーバーにしてメソッドを実行するような形をとる。
- サービスクラスもできる限りfatにしたくないので、ビジネスロジックが嵩ばれば、consernやnamespaceを切ってさらに別のクラスをmodelsディレクトリ配下作るイメージ。models/{table name}/{use case}.rbを作成して、処理を切り出す。見通しをよくするためにも、 インスタンス変数の定義はControllerにて行うようにする。
- ロジックやデータの加工は必ずModelで行うようにする
- 投稿日:2020-08-01T15:34:39+09:00
Strong Parametersとは?
Strong Parametersとは
Strong ParametersはRails4系から追加されたセキュリティを向上させるための仕組みです。
指定した値以外は受け取らないようにして、攻撃者による意図しないコードの実行を防止するセキュリティ対策。フォームからデータを送信するときは、「マスアサインメント脆弱性」というセキュリティ上の問題があります。
簡単に伝えると、データ送信時に不正なリクエストによって、予期しない値が変更されてしまう脆弱性です。
Railsでは、この脆弱性を防ぐ「Strong Parameters」の仕組みが用意されています。こんな感じです。
Strong parametersは必ずprivateより下に記述します。app/controller/user_controller.rbclass UsersController < ApplicationController def create user = User.new(user_params) end private def user_params params.require(:user).permit(:name, :email) end end簡単に言うと、userに関する値(パラメーター)が送信されても「name」「email」しか許可しませんよということです。
- 投稿日:2020-08-01T14:05:09+09:00
【Rails】コールバックでターミナルにバリデーションエラーを出力する
◯◯.saveや◯◯.createに失敗する時の原因を教えてくれなくて困る
例えばmessageモデルのインスタンスのsaveに失敗した時
Started POST "/groups/2/messages" for ::1 at 2020-08-01 13:56:59 +0900 Processing by MessagesController#create as */* Parameters: {"authenticity_token"=>"PKsrO+YPeqs8AmQ+sB+9YacFtKCb+gpdTkHPTgxG4/vs6wE0swZrWNYXKyBe4ipm9aQDII8PpHSUzL3JPeYPpw==", "message"=>{"content"=>""}, "group_id"=>"2"} User Load (25.1ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 6 ORDER BY `users`.`id` ASC LIMIT 1 Group Load (0.6ms) SELECT `groups`.* FROM `groups` WHERE `groups`.`id` = 2 LIMIT 1 ↳ app/controllers/messages_controller.rb:30:in `set_group' (0.2ms) BEGIN ↳ app/controllers/messages_controller.rb:11:in `create' User Load (0.4ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 6 LIMIT 1 ↳ app/controllers/messages_controller.rb:11:in `create' (0.2ms) ROLLBACK
ROLLBACK
とだけ出てパッと見では原因がわからない。【対策1】コントローラでsave!を使う方法
save
やcreate
をsave!
、create!
にするとどのバリデーションが原因なのかエラーが表示される。【対策2】モデルでコールバックを使う方法
本題。
after_validation
を使ってself.errors.messages
などをputs
メソッドで表示する。<メリット>
・deviseを使ったuserモデルのように、コントローラを触るのが面倒な場合でも使える
・application_record.rbに一度書けばそれぞれのモデルで使える手順1. pry-railsのgemを導入する
バリデーションエラーが発生した場合にbinding.pryを走らせたいので、いつもどおりpry-railsのgemを導入する。
Gemfilegroup :development, :test do 〜省略〜 gem 'pry-rails' endターミナルbundle手順2. models/application_record.rbにコールバックを追加する
models/application_record.rb
に以下を追加する。application_record.rbclass ApplicationRecord < ActiveRecord::Base self.abstract_class = true ## 以下を追加する after_validation :display_validation_result private def display_validation_result ## development以外のときはオフ return false unless Rails.env.development? puts "============【display_validation_result】============" puts "◆◆◆◆◆◆#{self.class.name}モデル◆◆◆◆◆◆" puts "+++++属性値一覧++++++" self.attributes.each do |key, value| puts "#{key}: #{value}" end ## deviseを使ったuserモデルの場合passwordもputsする if self.class.name == "User" puts "password: #{self.password}" end puts "++++++++++++++++++++++++++++" ## メインはここから puts "★★★★★★バリデーションエラー★★★★★★" if self.errors.messages.length == 0 puts "発生しませんでした" else puts "バリデーションエラー発生" puts self.errors.messages binding.pry end puts "★★★★★★★★★★★★★★★★★★★★★★★★★★★★★" puts "===========================" end endやっていることはバリデーションエラーが発生した時(self.errors.messages.lengthが0出ない時)に
self.errors.messages
をターミナルに表示してbinding.pryを走らせているだけ。その他はおまけの情報。結果
バリデーションエラーが発生してインスタンスのsaveに失敗する時、以下のようにターミナルに出力される。
============【display_validation_result】============ ◆◆◆◆◆◆Userモデル◆◆◆◆◆◆ +++++属性一覧++++++ "id: " "email: dwada" "encrypted_password: $2a$11$zvwhx8qOe7u62tLbGQCaMOjItYjtjzg5yuaInysQ9wd15SMpMsBti" "nickname: dadwa" "first_name: dwadad" "first_name_reading: dadawdwa" "last_name: dwada" "last_name_reading: dadwadwa" "birthday: 2015-04-10" "reset_password_token: " "reset_password_sent_at: " "remember_created_at: " "password: dwadad" ++++++++++++++++++++++++++++ ★★★★★★バリデーションエラー★★★★★★ バリデーションエラー発生 {:email=>["は不正な値です"], :password_confirmation=>["とパスワードの入力が一致しません"], :first_name_reading=>["はカタカナで入力して下さい。"], :last_name_reading=>["はカタカナで入力して下さい。"], :password=>["は不正な値です"]} From: /Users/hogehoge/hogehoge/app/models/application_record.rb:27 ApplicationRecord#display_validation_result: 6: def display_validation_result 7: puts "============【display_validation_result】============" 8: puts "◆◆◆◆◆◆#{self.class.name}モデル◆◆◆◆◆◆" 9: puts "+++++属性一覧++++++" 10: self.attributes.each do |key, value| 11: p "#{key}: #{value}" 12: end 13: 14: if self.class.name == "User" 15: p "password: #{self.password}" 16: end 17: 18: puts "++++++++++++++++++++++++++++" 19: puts "★★★★★★バリデーションエラー★★★★★★" 20: if self.errors.messages.length == 0 21: puts "発生しませんでした" 22: else 23: puts "バリデーションエラー発生" 24: puts self.errors.messages 25: binding.pry 26: end => 27: puts "★★★★★★★★★★★★★★★★★★★★★★★★★★★★★" 28: puts "===========================" 29: end [1] pry(#<User>)>
- 投稿日:2020-08-01T11:37:42+09:00
セレクターで投稿内容をカテゴライズ
カテゴライズしたいModelを作成
terminal$ rails g model Club name:string $ rails db:migrate今回はGear ModelにClub(Driver, Iron...)属性
を追加するので、Club Model作成Gear Modelにclub_idを追加
terminal$ rails g migration add_club_id_to_gears club_id:integer $ rails db:migrateAssociation追加
app/models/gear.rbclass Gear < ApplicationRecord belongs_to :club endapp/models/club.rbclass Club < ApplicationRecord has_many :gears endgear_controllerのnew actionでclub選択できるよう設定
app/controllers/gears_controller.rbdef new @gear = current_user.gears.build @clubs = Club.all.map{ |c| [c.name, c.id] } end def create @gear.club_id = params[:club_id] end def edit @clubs = Club.all.map{ |c| [c.name, c.id]} end def update @gears.club_id = params[:club_id] end private def gear_params params.require(:gear).permit(:club_id) endformタグにセレクター追加
app/views/gears/_form.html.erb<%= simple_form_for @gear do |f| %> <%= select_tag(:club_id, options_for_select(@clubs), :prompt => "Select a Club") %> <%= f.button :submit %> <% end %>editのview編集
app/views/gears/edit.html.erb<%= simple_form_for @gear, :html => { :multipart => true} do |f| %> <%= f.input :name, label: "Gear Name" %> <%= f.input :description %> <%= f.label :Maker %> <%= f.select :maker_id, @makers %> <%= f.label :club %> <%= f.select :club_id, @clubs %> <%= f.button :submit %> <% end %>
- 投稿日:2020-08-01T07:14:22+09:00
【Rails】マイグレーションファイルを削除する
参考対象者
- マイグレーションファイルの操作の仕方を知りたい方
環境
$ rails -v Rails 6.0.3.1$ ruby -v ruby 2.7.0p0 (2019-12-25 revision 647ee6f091) [x86_64-darwin19]$ mysql --version mysql Ver 14.14 Distrib 5.7.29, for osx10.15 (x86_64) using EditLine wrapperマイグレーションファイルを削除する
Add column to usersというファイルを削除したかったとします
- 削除したいファイルのマイグレーションIDを確認する
$ rails db:migrate:status Status Migration ID Migration Name -------------------------------------------------- up 20200618162841 Create tweetposts up 20200620004226 Change tweetposts to tweets down 20200621075518 Create posts down 20200623102444 Change posts to chats down 20200627042358 Create users up 20200627080839 Create users up 20200627083356 Add column to users down 20200627220915 Change datatype content of chats down 20200710035709 Add user id to tweets
- ID指定して、downさせる
$ rails db:migrate:down VERSION=Add column to users
- downさせたファイルをゴミ箱へ
$ rm db/migrate/20200703201452_add_column_to_users.rbdownさせる前に、マイグレーションファイルを削除してしまったとき
********** NO FILE **********
というファイルで、up状態のファイルを削除したい
- 削除したいファイルのマイグレーションIDを確認する
$ rails db:migrate:status Status Migration ID Migration Name -------------------------------------------------- up 20200618162841 Create tweetposts up 20200620004226 Change tweetposts to tweets down 20200621075518 Create posts down 20200623102444 Change posts to chats down 20200627042358 Create users up 20200627080839 Create users up 20200627083356 Add column to users down 20200627220915 Change datatype content of chats up 20200703201452 ********** NO FILE ********** down 20200710035709 Add user id to tweets
- 削除したマイグレーションファイル名と同名のファイルを作成する
$ touch db/migrate/20200703201452_add_column_to_users.rb20200703201452_add_column_to_users.rbclass AddColumnToUsers < ActiveRecord::Migration def change end end
- downさせて、作成したマイグレーションファイルを削除する
$ rails db:migrate:down VERSION=20200703201452$ rm db/migrate/20200703201452_add_column_to_users.rb
- 投稿日:2020-08-01T00:36:38+09:00
resourcesでルーティングを設定する方法
resourcesでルーティングを設定する
ルーティングは、個別に設定も可能です。
ただし、railsでの基本的な7つのアクションは開発の際によく使われるので、
そのたびに設定するのは効率的とはいえません。
こんなときは、resourcesメソッドを利用すると、ルーティングを1行書くだけで自動設定されます。Rails.application.routes.draw do resources :blogs endrails routesコマンドで確認。
$ rails routes全てのurlが設定されていることを確認。
$ rails routes Prefix Verb URI Pattern Controller#Action blogs GET /blogs(.:format) blogs#index POST /blogs(.:format) blogs#create new_blog GET /blogs/new(.:format) blogs#new edit_blog GET /blogs/:id/edit(.:format) blogs#edit blog GET /blogs/:id(.:format) blogs#show PATCH /blogs/:id(.:format) blogs#update PUT /blogs/:id(.:format) blogs#update DELETE /blogs/:id(.:format) blogs#destroy rails_service_blob GET /rails/active_storage/blobs/:signed_id/*filename(.:format) active_storage/blobs#show rails_blob_representation GET /rails/active_storage/representations/:signed_blob_id/:variation_key/*filename(.:format) active_storage/representations#show rails_disk_service GET /rails/active_storage/disk/:encoded_key/*filename(.:format) active_storage/disk#show update_rails_disk_service PUT /rails/active_storage/disk/:encoded_token(.:format) active_storage/disk#update rails_direct_uploads POST /rails/active_storage/direct_uploads(.:format) active_storage/direct_uploads#create
- 投稿日:2020-08-01T00:09:35+09:00
Railsのバリデーションの種類と使い方
プログラミングの勉強日記
2020年8月1日 Progate Lv.226
バリデーションとは
不正なデータがデータベースに保存されないようにデータをチェックするシステムをバリデーションという。バリデーションに引っかかった場合(不正なデータの場合)にはデータベースに保存されない。
基本的な書き方
モデルクラスに
validates
メソッドを指定することで、バリデーションをかけることができる。(バリデーションはモデルで設定する。)このときに、validatesを用いてカルム名と内容を指定する。model/ファイル名.rbvalidates :カラム名(シンボルで指定),検証ルール(こちらもシンボルで指定)バリデーションの種類
空ではい(空のデータを登録できないようにする)
{presence: true}を用いることでそのカラムの値が存在するかどうかをチェックできる。
models/posts.rbclass Post < ApplicationRecord validates :content, {presence: true} endboolean属性が空でないこと
models/users.rbclass User < ApplicationRecord validates :birthplace, inclusion: { in: [true, false] } endチェックボックス
models/users.rbclass User < ApplicationRecord validates :gender, {acceptance: true} end文字数の指定
length
を用いて{maximum:数値}
を指定することで最大文字数を{minimum:数値}
で最小文字列を指定することができる。またin
を使うことで範囲やその文字数のみのバリデーションをかけることもできる。models/posts.rbclass Post < ApplicationRecord # 最大140文字(140文字以下) validates :content, {length : {maximum:140} } # 50文字以上 validates :content, {length : {minimum:50} } # 1文字以上75文字以下 validates :content, {length: {in: 1..75} } # 5文字のみ validates :content, {length: {is: 5} } end数値の指定
数値のみの入力を許可するときには、
numericality
を使う。models/actorrbclass Actor < ApplicationRecord validates :height, {numericality: true} end
numericality
は様々なオプションがあるので、これらを用いることでより詳細なバリデーションをかけることができる。
オプション 概要 only_integer integerのみ equal_to 指定された値と等しいか greater_than_or_equal_to 指定された値以上 less_than_or_equal_to 指定された値以下 greater_than 指定された値よりも大きいか less_than 指定された値よりも小さいか odd trueに設定した場合、奇数か even trueに設定した場合、偶数か models/actor.rbclass Actor < ApplicationRecord # 50より大きく250より小さい validates :height, numericality: {greater_than: 50,less_than: 250} end任意で指定した値が含まれているかどうか
指定した値が含まれているか確認するときは
inclusion
を使う。models/actor.rbclass Actor < ApplicationRecord validates :blood_type, inclusion: {in: ['A','B','O','AB']} end指定した値が含まれていないか確認するときは
exclusion
を使う。models/actor.rbclass Actor < ApplicationRecord validates :gender, exclusion: {in: ['male','female']} end値の重複
値の重複がないかをチェックするための
{uniqueness: true}
というバリデーションもある。models/posts.rbclass Post < ApplicationRecord validates :content, {uniqueness: true} end
おまけ
複数のバリデーションの指定
バリデーションの内容はハッシュになっているので、コンマで区切ることで複数指定できる。
models/posts.rbclass Post < ApplicationRecord validates :content, {presence: true, length : {maximum:140}} end自分で決めたエラーメッセージを表示する
message
を追加することで自分で指定したエラーメッセージを表示させることもできる。models/actor.rbclass Actor < ApplicationRecord validates :height, numericality: {greater_than: 50,less_than: 250, message: " : Please input 50~250"} end