- 投稿日:2020-07-14T22:58:53+09:00
新・Railsポートフォリオ作成の備忘録②
5.UsersのProfileを作成する
以下記事を参照に、Usersのprofile作成をする。
https://qiita.com/ryokky59/items/71b5a853989721b89c6e<注意点>
※ app/controllers/user/apprication_controller.rb
のファイルをいじるよう記載があるが、当方の場合、app/controllers/apprication_controller.rbが生成されたので、こちらをいじった。※resources :users, only: [:show] と get 'users/show'の二つが作成されていたので、
resources :users, only: [:show] だけにした。<生じたエラー>
上記記事を参照してshowページまで無事にたどり着くか試したかったが上手くいかなかった。++++++++++++エラー内容++++++++++++++
このサイトにアクセスできません
localhost で接続が拒否されました。
++++++++++++++++++++++++++++++++++この時、$ rails routes コマンドでルートが正しく設定されているか確認するよう教わった。
https://pikawaka.com/rails/rake_routes<解決策>
以下のようにコードを書き換えるように教わった。Rails.application.routes.draw do devise_for :sellers, controllers: { sessions: 'sellers/sessions', passwords: 'sellers/passwords', registrations: 'sellers/registrations' } devise_for :users, controllers: { sessions: 'users/sessions', passwords: 'users/passwords', registrations: 'users/registrations' } resources :users, only: [:show] ← ここ end記事を参考にした際、resources :users, only: [:show]
を一番上に置いた。
ルーティングは一番上にあるコードが優先的になる為、
例えば、/users/sessionsも/users/passwordsも/users/:idのルーティングとして読み込まれ、引数の1のようにsesssionsとpasswordsも引数の一つとして扱われてしまっていた。<その他修正点>
Vessel_Trader_Queen/app/controllers/application_controller.rb
class ApplicationController < ActionController::Base protect_from_forgery with: :exception # deviseコントローラーにストロングパラメータを追加する # before_action:configure_permitted_parameters, if: :devise_controller? protected def configure_permitted_parameters # サインアップ時にnameのストロングパラメータを追加 devise_parameter_sanitizer.permit(:show, keys: [:name]) # アカウント編集の時にnameとprofileのストロングパラメータを追加 devise_parameter_sanitizer.permit(:account_update, keys: [:name, :introduction]) end # ログイン後、マイページに移動する def after_sign_in_path_for(resource) user_path(resource) end endVessel_Trader_Queen/app/controllers/users_controller.rb
class UsersController < ApplicationController # ログイン済ユーザーのみにアクセスを許可する before_action :authenticate_user!, only: [:show] def show if params[:id] @user = User.find(params[:id]) #追記 end end endVessel_Trader_Queen/app/models/user.rb
class User < ApplicationRecord # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable and :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable before_save { self.email = email.downcase } # validates :name, presence: true, length: { maximum: 50 } VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX } # validates :type, presence: true, length: { maximum: 50 } # validates :industry, length: { maximum: 50 } # validates :introduction, length: { maximum: 1000 } endVessel_Trader_Queen/app/views/users/show.html.erb
<h1>Users#show</h1> <p>名前 : <%= @user.name %></p> <p>メールアドレス : <%= @user.email %></p> <p>プロフィール : <%= @user.introduction %></p> <%= link_to "ログアウト", destroy_user_session_path, method: :delete %>※正しこの時点では「名前とプロフィールが保存できなかった」
6.Sourcetreeの導入
Sourcetreeが便利だということでSourcetreeをインストールした。
7.git pull の使用
この時、先生がほとんどのコードを書き換えたので、git pull を使用した。
参考になったのは、以下のページである。https://qiita.com/wann/items/688bc17460a457104d7d
git fetch + git merge = git pull
git cloneは、チーム開発の初期段階、特に自身のローカルにアプリのコードがない場合に使用する、というイメージ。
8.名前とプロフィールが保存できない問題の解決
5.のコードを更に以下のように修正した。
また、他の項目も付け足した。Vessel_Trader_Queen/app/controllers/application_controller.rb
class ApplicationController < ActionController::Base protect_from_forgery with: :exception # deviseコントローラーを実行する前に、configure_permitted_parametersメソッドを実行する before_action :configure_permitted_parameters, if: :devise_controller? # sign_upアクション時にnameとintroductionカラムの保存を許可する def configure_permitted_parameters devise_parameter_sanitizer.permit(:sign_up, keys: [:name, :introduction, :industry, :user_type ]) end # ログイン後、マイページに移動する def after_sign_in_path_for(resource) user_path(resource) end endVessel_Trader_Queen/app/controllers/users_controller.rb
class UsersController < ApplicationController # ログイン済ユーザーのみにアクセスを許可する before_action :authenticate_user!, only: [:show] def show if params[:id] @user = User.find(params[:id]) #追記 end end endVessel_Trader_Queen/app/models/user.rb
class User < ApplicationRecord devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable before_save { self.email = email.downcase } # validates :name, presence: true, length: { maximum: 50 } VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX } endVessel_Trader_Queen/app/views/users/show.html.erb
<h1>Users#show</h1> <p>名前 : <%= @user.name %></p> <p>メールアドレス : <%= @user.email %></p> <p>自己紹介 : <%= @user.introduction %></p> <p>業種 : <%= @user.industry %></p> <p>業務形態 : <%= @user.user_type %></p> <%= link_to "ログアウト", destroy_user_session_path, method: :delete %>config/routes.rb
Rails.application.routes.draw do devise_for :sellers, controllers: { sessions: 'sellers/sessions', passwords: 'sellers/passwords', registrations: 'sellers/registrations' } devise_for :users, controllers: { sessions: 'users/sessions', passwords: 'users/passwords', registrations: 'users/registrations' } resources :users, only: [:show] end
- 投稿日:2020-07-14T22:26:10+09:00
【Rails】多階層カテゴリーから商品一覧を表示させるプロセスとは
前提条件
以下のことが終わっていることとする。終わっていないと機能が正しくなっているか確認が難しい。
・ancestryを用いてカテゴリーテーブルを作成している。
・カテゴリー:商品 = 1:多の関係になっている。
・カテゴリーの階層が3段階になっておりそれぞれの命名が「親」、「子」、「孫」になっている
・商品モデルのcategory_idには、最下層のカテゴリーidが登録されている。
・gem kaminariがインストール済
・カテゴリーコントローラーファイルの作成及び、カテゴリーの取得ができるコードが書かれている。
カテゴリー別に商品一覧を表示させる
カテゴリーの詳細が見たいわけなのでshowアクションを用います。
showアクション定義
app/controllers/categories_controller.rb
before_action :set_category, only: :show def show @items = @category.set_items @items = @items.where(buyer_id: nil).order("created_at DESC").page(params[:page]).per(9) end private def set_category @category = Category.find(params[:id]) endモデルメソッド定義
app/models/category.rb
has_many :items has_ancestry def set_items # 親カテゴリーだった場合 if self.root? start_id = self.indirects.first.id end_id = self.indirects.last.id items = Item.where(category_id: start_id..end_id) return items # 子カテゴリーだった場合 elsif self.has_children? start_id = self.children.first.id end_id = self.children.last.id items = Item.where(category_id: start_id..end_id) return items # 孫カテゴリーだった場合 else return self.items end end@items = @category.items と記述するだけでは、
商品モデルのcategory_idには最下層のidが付与されているので@categoryが孫であった場合のみ、情報が取得されることになってしまう。
なので予め、上記のようにカテゴリーが親なのか子なのか孫なのかといったような条件分岐をするとうまくいくはずビュー
app/views/categories/show.html.haml
.items-container .items-index .title = "#{@category.name}の商品一覧" .title__border - if @items %ul.lists = render "items/item", items: @items = paginate @itemsこのままでは他のカテゴリーへのリンクがまだ設定されていないが、
それはまた、書いていきます
- 投稿日:2020-07-14T22:20:15+09:00
【before_action】
before_actionとは
before_actionはコントローラーで定義された処理を実行する前に共通の処理を行うことができるメソッドのこと
今回はコントローラー内のメソッドの実行内容が重複している場合のbefore_actionの使用例を書いていきます。
上の画像のようにeditアクションとshowアクションの実行内容が同じである場合は共通の処理としてまとめてしまった方が可読性も上がり、変更の際も便利なので処理をまとめる。
まず該当のアクションを削除。
その後先ほど削除した共通していた処理内容をprivateメソッドの部分にset_actionとして定義する。
最後にコントローラーの上部にbefore_actionを記述する。
今回の場合はset_actionを適用させたいのはeditとshowアクションのみなのでオプションとしてonlyを記述することで該当のアクションにのみset_actionを実行させれば良い。
- 投稿日:2020-07-14T22:15:22+09:00
Ruby on RailsのポートフォリオにDockerを組み込む!
現在の状態
みなさんこんにちわ今回Qiita初投稿を行います!
間違っているところがあると思いますがその時は優しく教えてください(笑)
今回、AwsとDockerを用いて既に作成済のRailsのポートフォリオをデプロイしています。
現在の状態は、railsとAws用いてポートフォリオを作成したのですが、環境構築が大変だと思い、環境周りをコードで管理しやすいDockerを用いてポートフォリオを際デプロイしたいと思います。構成図
デプロイの際の構成図は以下の通りです。
めちゃくちゃ簡単に説明するとクライアントから通信リクエストが来るとNginxに行き動的な処理が必要な際はpumaに通信を行いその際にpumaとmysqlも通信する流れとなっています。
今回はこの構成をDockerを用いて作成していきたいと思います。必要な物
今回デプロイする際はAwsのEC2とRDSをを使用します。
Dockerで環境構築を行いますが、データの永続化の観点から今回データベースはDockerではなくRDSのMysqlをしようします。
以下作業に必要な準備と作業です。・AwsでEC2、RDSを用いてデプロイする際に必要な準備を行う(ネットワーク構成など...)
・local環境とEc2にDokcerをインストール
・local環境とEc2にDocker-composeをインストール参考にした記事
・EC2上でRailsアプリケーションにDockerを導入する(Rails、Nginx、RDS)
・Rails On DockerでのAWSデプロイができたので,中身を整理します。作業
まずはじめにDockerファイルを作成します。
ここではDockerfileを用いてRuby周りの環境を構築するコードを書きます。FROM ruby:2.5.7 RUN apt-get update -qq && \ apt-get install -y build-essential \ libpq-dev \ nodejs \ vim RUN mkdir /アプリの名前 WORKDIR /アプリの名前 ADD Gemfile /アプリの名前/Gemfile ADD Gemfile.lock /アプリの名前/Gemfile.lock RUN gem install bundler RUN bundle install ADD . /アプリの名前 RUN mkdir -p tmp/sockets RUN mkdir -p tmp/pids次にDocker-composeを作成します。
ここではDockerコンテナの管理やマントする箇所の指定を行っていきます!Docker-compose.ymlversion: '3' services: app: build: . command: bundle exec puma -C config/puma.rb -e production volumes: - .:/アプリの名前:cached - public-data:/アプリの名前/public - tmp-data:/アプリの名前/tmp - log-data:/アプリの名前/log web: build: context: containers/nginx volumes: - public-data:/アプリの名前/public - tmp-data:/アプリの名前/tmp ports: - 80:80 volumes: public-data: tmp-data: log-data:次に以下のファイルを作成します。
ここではNginxコンテナの設定を行っていきます。FROM nginx:1.15.8 RUN rm -f /etc/nginx/conf.d/* ADD nginx.conf /etc/nginx/conf.d/アプリの名前.conf CMD /usr/sbin/nginx -g 'daemon off;' -c /etc/nginx/nginx.confcontainers/nginx/nginx.confupstream FashionInformation_app { server unix:///アプリの名前/tmp/sockets/puma.sock; } server { listen 8000; server_name ドメイン名; access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; root /アプリの名前/public; client_max_body_size 100m; error_page 404 /404.html; error_page 505 502 503 504 /500.html; try_files $uri/index.html $uri @アプリの名前; keepalive_timeout 5; location @アプリの名前 { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_pass http://アプリの名前; } }Railsアプリケーションをデプロイする
ここまできたらEC2にgitをクローンします!
ec2-user@ip-xxx-xx-xx-xxx ~]$ git clone GitHubのリポジトリのURLイメージのビルド
ec2-user@ip-xxx-xx-xx-xxx]$ cd myapp [ec2-user@ip-xxx-xx-xx-xxx myapp]$ docker-compose buildサーバー起動前の準備
[ec2-user@ip-xxx-xx-xx-xxx myapp]$ docker-compose run app rails assets:precompile RAILS_ENV=productionサーバー起動前の準備
[ec2-user@ip-xxx-xx-xx-xxx myapp]$ docker-compose up -dデータベースの作成、マイグレーションファイルの読み込み
[ec2-user@ip-xxx-xx-xx-xxx myapp]$ docker-compose exec app rails db:create db:migrate RAILS_ENV=productionパブリックIPにアクセスして、正しく表示されれば成功です!
- 投稿日:2020-07-14T21:41:10+09:00
Docker+Rails環境でActive Storageを導入する
Active Storageとは?
Rails5.2系から利用可能な、ファイルアップローダーです。
develop(開発)環境ではローカルに画像を保存、product(本番)環境ではAmazon S3やGCS、Microsoft Azure Storageに画像を保存したりと、ファイルのアップロード先を簡単に切り替えることができます。ファイルアップローダー用のGemでは、carrierwave などが有名ですが、新規に画像用のカラムを追加しなければいけなかったりと、なかなか大変です。
対するActiveStorageは、画像追加したいテーブルに対してカラム追加をする必要がありません。carrierwaveよりお手軽に導入できるので、その手順を紹介していきたいと思います。導入手順
※既に投稿機能は実装済みであることが前提です。(Productモデル、Articleモデルなど)
今回は、dev環境でAmazon S3にアップロードする手順を紹介していきます。Active Storageのインストール
$ docker compose run web rails active_storage:install $ docker compose run web rails db:migrateすると、active_storage_blobsとactive_storage_attachmentsという名前の2つのテーブルが作成されます。
active_storage_blobsは実際にアップロードしたファイルが保存されるテーブルで、active_storage_attachmentsは中間テーブルになります。
【Rails】Active Storageを使って画像をアップしよう! | Pikawaka - ピカ1わかりやすいプログラミング用語サイト様より引用
実際にActive Storageを扱っていく上で、上記の2つのテーブルには一切触れません。
単一ファイルのみアップロードする場合
productclass Product < ApplicationRecord has_one_attached :image #カラム名を記述 end上記、カラム名と書いていますが好きな名前でいいです。(僕はimageとしています。)
カラム名と書いていますが、実際にProductモデルにimageカラムは追加する必要がありません。products_controller.rbdef new @product = Product.new end def create @product = Product.new (product_params) @product.producer_id = current_producer.id if @product.save redirect_to product_path(@product), notice: "投稿に成功しました" else render :new end end def product_params params.require(:product).permit(:name, :description, :price, :content, :image) endストロングパラメーターに、 :image を追加します。
new.html.erb<h2>商品の出品</h2> <%= form_for @product do |f| %> <label>商品名</label> <p><%= f.text_field :name %></p> <label>出品時のタイトル</label> <p><%= f.text_field :content %></p> <label>商品の値段</label> <p><%= f.number_field :price %></p> <label>画像</label> <p><%= f.file_field :image %></p> #画像投稿部分 <label>商品内容</label> <p><%= f.text_area :description %></p> <%= f.submit %> <% end %>画像を表示する際は、
<%= image_tag @product.image %>
とすれば表示できます。複数ファイルをアップロードする場合
product.rbclass Product < ApplicationRecord has_many_attached :images #複数形 endproducts_controller.rbdef new @product = Product.new end def create @product = Product.new (product_params) @product.producer_id = current_producer.id if @product.save redirect_to product_path(@product), notice: "投稿に成功しました" else render :new end end def product_params params.require(:product).permit(:name, :description, :price, :content, images: []) #images: []という形で、配列にする endnew.html.erb<h2>商品の出品</h2> <%= form_for @product do |f| %> <label>商品名</label> <p><%= f.text_field :name %></p> <label>出品時のタイトル</label> <p><%= f.text_field :content %></p> <label>商品の値段</label> <p><%= f.number_field :price %></p> <label>画像</label> <p><%= f.file_field :images, multiple: true %></p> #画像投稿部分 <label>商品内容</label> <p><%= f.text_area :description %></p> <%= f.submit %> <% end %>show.html.erb<% if @product.images.attached? %> <% @product.images.each do |image| %> <%= image_tag image %> <br> <% end %> <% end %><% if @product.images.attached? %> で、
@product
が画像を持っているかどうかを判断しています。
@product
が持っているimages(画像)を、each文で回して images[ ] の中身を全て表示しています。ファイルの保存先の指定
config/environments/development.rb を確認します。
config.active_storage.service = :localという部分がありますが、これはデフォルトでローカルに保存するように設定されています。
S3に保存したい場合は、config.active_storage.service = :amazonに変更します。
次に config/storage.yml を設定していきます。
amazon、GCS,AzureStorageがコメントアウトされてますが、amazonの部分のコメントをすべて外しましょう。config/storage.ymlamazon: service: S3 access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> region: "ap-northeast-1" # 東京リージョン bucket: "S3で自分が作ったバケット名"Gemfileを追加します。
gem "aws-sdk-s3", require: falseGemfileを追加したので、docker-compose build します。(この部分は人によります。)
$ docker-compose build認証情報の記述
credentials.yml.encに、アクセスキーとシークレットアクセスキーを記述していきます。
Docker無しの環境であれば、$ EDITOR=vim rails credentials:editで行けるのですが、Docker環境の場合上記のコマンドでは上手くいきませんでした。
Rails on Dockerでcredentialsをeditしたい
の記事がとても分かりやすく、Docker+rails環境ででcredential編集ができるようになります。aws: access_key_id: アクセスキー secret_access_key: シークレットアクセスキー上記3行のコメントアウトはすべて外しましょう。
バケットとIAMユーザーの作成
めちゃくちゃ長くなるので割愛しますが、大まかにいうと、
1. IAMユーザー作成(AmazonS3FullAccessのポリシーを付与)
2. IAMユーザー作成時にアクセスキー、シークレットアクセスキーが記述されたcsvファイルをダウンロード(1回しかダウンロードできないので、管理は厳重にしておく)
2. S3のバケットを作成上手くいけばS3アップロード成功となります。
参考にさせていただいた記事
【Rails 5.2】 Active Storageの使い方 - Qiita
- 投稿日:2020-07-14T21:40:34+09:00
Deviseで作成したUserモデル等に対して、プロフィール画像投稿機能を追加する
Deviseで作成したUserモデル等に対して、プロフィール画像投稿機能を追加する
前提:DeviseでUserモデルを作成していること
手順
Railsで画像をアップロードする方法を現役エンジニアが解説【初心者向け】 | TechAcademyマガジンより引用。
CarrierWaveの使い方
CarrierWaveは、以下の3ステップで使用できるようになります。
- gemをインストールする
- アップローダーを作成する
- モデルにカラムを追加し、マウントする 上記の3つの準備をした上で、画像を保存すると画像がpublicフォルダに保存されます。
このようにして進めていきます。
1. Gemのインストール
Gemfile.rbgem 'carrierwave'$ bundle install2. アップローダーの作成
$ rails g uploader Image3. モデルにカラムを追加し、マウントする
deviseで作成したUserモデルに対して、カラムを追加していきます。
カラム追加のマイグレーションスクリプトは、下記の形式になります。$ rails g migration Addカラム名(先頭大文字)Toテーブル名今回は、imageカラムをusersテーブルに追加したいので、
$ rails g migration AddImageToUsers image:stringdeviseはデフォルトの状態だとemailとpasswordしか受け取らない設定になっているので、カラム追加した際は、新しくパラメーターを設定しなければいけません。
今回はimageカラムを追加したので、imageカラムを許可する設定をapplication_controller.rbに記述してあげます。application_controller.rbdef configure_permitted_parameters devise_parameter_sanitizer.permit(:sign_up, keys: [:image]) endアップロードを実装したいモデル、今回はuserモデルに下記のように記述します。
(投稿にも画像を添付したい場合は、Postモデル等にも同じように記述します。)user.rbmount_uploader :image, ImageUploader #追加この記述によって、画像アップローダーを実装することが可能です。
新規登録時にプロフィール画像を設定
新規登録時にプロフィール画像を設定し、かつユーザー編集画面でもプロフィール画像を編集できるようにしたいと思います
app/views/devise/registrations/new.html.erbを編集します。
new.html.erb=================================ここから <div class="field"> <%= f.label :image %><br/> <%= f.file_field :image %> </div> =================================ここまで追加 ・・・・・・・・・・・・・・・ <div class="field"> <%= f.label :email %><br /> <%= f.email_field :email, autofocus: true, autocomplete: "email" %> </div>ユーザー編集画面でプロフィール画像を設定
users_controller.rbclass UsersController < ApplicationController def index end def edit @user = current_user end def update @user = current_user if @user.update(user_params) redirect_to user_path, notice: "ユーザーを更新した" else render :edit end end private def user_params params.require(:user).permit(:email, :image) end endedit.html.erb<h2>編集</h2> <%= form_for @user do |f| %> <label>メールアドレス</label> <%= f.text_field :email %> <label>プロフィール画像</label> <%= f.file_field :image %> <%= f.submit %> <% end %>テンプレート側で画像を表示したい場合は、Userモデルから情報を取ってきて、
<p><%= image_tag user.image.url %></p>
とすれば表示可能となります。
- 投稿日:2020-07-14T20:58:12+09:00
[Rails]payjp(API)を利用する仕組みと方法をかなり丁寧に書いてみる(クレカ登録編)
はじめに
スクールの課題でフリマアプリを作る中で、payjpを利用したクレカ登録・決済機能の実装がありました。
検索すると以前の卒業生が多数記事を書いてくれてるのでコードは見つかるのですが、何をやっているの?という所は結局公式のリファレンス読むのが一番だよねとなったので理解したことについて記載しておくものです。この記事では
payjpを利用するってそもそもどういうこと?(APIの説明)
payjpの呼び出し方
クレカ登録の実装
登録したクレカによる決済実装上記について記載していきます。
※現在payjpはv2バージョンを利用することが推奨されております。よりセキュアな実装として登録フォーム自体もpayjp側で用意してくれているのがv2なのですが、今回の課題はフォームの実装もこちら側のタスクとして含まれていたため、旧型のv1で実装を進めています。
とはいえ呼び出し方や使えるメソッドが異なるだけで、どちらも公式を読みながらやればあまり差がなく実装できるかと思います。環境
ruby 2.6.5
rails 6.0.3payjpを利用するってそもそもどういうこと?
コードを書いていくにしても、前段としてここの理解がまず大事だと思っています(あまりここに触れた記事はなかった)
payjpは、APIの1種です。
APIっていうのはググれば色々説明が出てくるんですが、「外部向けにソフトウェアの機能を一部提供してあげるよ」って感じです。用意してくれてる側の指示に従いうまくAPIを呼び出すことで、自分のアプリケーションの中でそのソフトウェアの機能を一部使えることができる様になる、という仕組みですね。
具体例を言うと、例えばGogole Mapのサービスがあります。
あれもgoogleが用意してくれたAPIを利用し、指示にしたがってコードを記載することで、自分のアプリケーション上にgoogle mapを表示することが可能になるという仕組みです。payjpもクレカに関するAPIであり、利用することでクレジットカードの登録や決済が可能になります。
APIを利用する時に考え方として個人的に大事だと思っているのが、相手に用意してもらっているという流れをちゃんと把握するということです。
上記を認識した上で実装の流れを考えると、
用意してもらったやり方で、APIを呼び出す準備をする
用意してもらったやり方で、向こうのアプリケーションとやりとりをし、処理を行うといった感じになります。このイメージがあると、以降の作業が具体的に何をしているのか分かりやすくなると思うので重要です。
それが故、用意してくれてる側の公式ドキュメントでやり方を理解する、と言うのが一番適切なアプローチになります。公式の説明書を読む様なものなので。
payjpの呼び出し方
前提として、payjpに登録しテスト公開鍵とテスト秘密鍵を取得しましょう。
APIの認証のために必要なキーです。payjp側はこれらを持って、僕らが「利用を許されたユーザー」であることを判断しています。登録が終わったらv1の公式リファレンスを読みましょう。
アクセスして早々、埋め込むべきスクリプトがちゃんと記載されています。
また、少し下に読み進めると、公開鍵による認証方法も書いています。
実際の記載については次項で見ていきます。クレカ登録の実装
上記の呼び出し方も踏まえ、先に実際のコードを載せます。
その上で流れを解説していきます。
※各ファイル、説明に不要な部分の記載は適宜削除しています。application.html.haml!!! %html %head %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/ %script{src: "https://js.pay.jp/v1/", type: "text/javascript"} = csrf_meta_tags = csp_meta_tag = stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' = javascript_include_tag 'application'new.html.haml.creditCreate .creditCreate__title %h1 クレジットカード情報入力 .creditForm = form_with model: @credit, method: :post, id: "cardCreateForm" do |f| .creditForm__numberfield %label(for="cardnumber-input") カード番号 %span.creditPoint 必須 %br = f.text_field :card_number, type: "text", class: 'cardnumber-input', id:'card-number', placeholder: "半角数字のみ", maxlength: 16 .creditslist = image_tag "jcb.gif", class:"creditsIcon" = image_tag "visa.gif", class:"creditsIcon" = image_tag "master.gif", class:"creditsIcon" = image_tag "amex.gif", class:"creditsIcon" .creditForm__datefield %label 有効期限 %span.creditPoint 必須 %br = f.select :exp_month, [["01",1],["02",2],["03",3],["04",4],["05",5],["06",6],["07",7],["08",8],["09",9],["10",10],["11",11],["12",12]],{} , class: 'dateSelect', name: 'exp_month' 月 = f.select :exp_year, [["20",2020],["21",2021],["22",2022],["23",2023],["24",2024],["25",2025],["26",2026],["27",2027],["28",2028],["29",2029]],{} , class: 'dateSelect', name: 'exp_year' 年 .creditForm__securityfield %label セキュリティコード %span.creditPoint 必須 %br = f.text_field :cvc, type: 'text', class: 'securityInput', id: 'cvc', placeholder: 'カード背面4桁もしくは3桁の番号', maxlength: "4" #card_token.creditForm__submitfield = f.submit '追加する', class: 'creditsSubmit', id: 'token_submit'payjp.js$(function() { $('#cardCreateForm').on('submit', function(e) { e.preventDefault() Payjp.setPublicKey(['PAYJP_PUBLIC_KEY']); var card = { number: document.getElementById("card-number").value, exp_month: document.getElementById("credit_exp_month").value, exp_year: document.getElementById("credit_exp_year").value, cvc: document.getElementById("cvc").value }; if (card.number == "" || card.cvc == "") { alert("入力もれがあります"); } else { Payjp.createToken(card, function(status, response) { if (status === 200 ) { $("#card_number").removeAttr("name"); $("#cvc").removeAttr("name"); $("#exp_month").removeAttr("name"); $("#exp_year").removeAttr("name"); $("#card_token").append( $('<input type="hidden" name="payjp-token">').val(response.id) ); $('#cardCreateForm').get(0).submit(); alert("登録に成功しました"); } else { alert("カード情報が正しくありません"); } }); } }); });credits_controller.rbrequire 'payjp' def create Payjp.api_key = ENV['PAYJP_SECRET_KEY'] if params['payjp-token'].blank? render :new else customer = Payjp::Customer.create( email: current_user.email, card: params['payjp-token'], metadata: {user_id: current_user.id} ) @credit = Credit.new(user_id: current_user.id, customer_id: customer.id, card_id: customer.default_card) if @credit.save redirect_to user_path(current_user.id) else render :new end end end以上がクレカ登録に関するコードです。
処理の流れは以下の通りです。この流れに沿って説明をしていきます。①payjpのAPIを利用するセッティングをする
②jsファイルにて、フォーム送信時に入力内容とpajypを紐付け登録の準備をする
③コントローラーにアクションを飛ばし、payjp上で顧客データを生成・顧客データを紐づくidをテーブルに保存する①payjpのAPIを利用するセッティングをする
application.html.hamlをみましょう
application.html.haml!!! %html %head %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/ %script{src: "https://js.pay.jp/v1/", type: "text/javascript"} = csrf_meta_tags = csp_meta_tag = stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' = javascript_include_tag 'application'scriptの部分がpayjpを呼び出す記載です。
呼び出し方は公式リファレンスに書いてあります。ただ転記するだけです。公式をみていればただ転記するだけなのです。②jsファイルにて、フォーム送信時に入力内容とpajypを紐付け登録の準備をする
jsファイルをみましょう。
フォーム送信のhamlファイルと対応した記載になってるので、並べてみながらだと理解がしやすいと思います。payjp.js$(function() { $('#cardCreateForm').on('submit', function(e) { e.preventDefault() Payjp.setPublicKey(['PAYJP_PUBLIC_KEY']); var card = { number: document.getElementById("card-number").value, exp_month: document.getElementById("credit_exp_month").value, exp_year: document.getElementById("credit_exp_year").value, cvc: document.getElementById("cvc").value }; if (card.number == "" || card.cvc == "") { alert("入力もれがあります"); } else { Payjp.createToken(card, function(status, response) { if (status === 200 ) { $("#card_number").removeAttr("name"); $("#cvc").removeAttr("name"); $("#exp_month").removeAttr("name"); $("#exp_year").removeAttr("name"); $("#card_token").append( $('<input type="hidden" name="payjp-token">').val(response.id) ); $('#cardCreateForm').get(0).submit(); alert("登録に成功しました"); } else { alert("カード情報が正しくありません"); } }); } }); });分割して解説していきます。
payjp.js$(function() { $('#cardCreateForm').on('submit', function(e) { e.preventDefault()jQueryで、フォームボタンをクリックした際の挙動ですと定義しています。
payjp.jsPayjp.setPublicKey(['PAYJP_PUBLIC_KEY']);認証(payjpの利用登録ができていますよ!と言うこと)を示すため、登録し取得したテスト公開鍵をセットします。
これを持ってpayjp側が利用を許可してくれます。setPublicKeyという書き方がいきなり出てきますが、これも公式リファレンスで指定された方法で記述してます。ただのコピペです。恐ることはありません。
payjp.jsvar card = { number: document.getElementById("card-number").value, exp_month: document.getElementById("credit_exp_month").value, exp_year: document.getElementById("credit_exp_year").value, cvc: document.getElementById("cvc").value };JSの記載です。get element by id と言う書き方の通り、各フォーム欄のidを指定・特定することで、それぞれの入力内容を定義し、cardと言う変数に代入し定義しています。
payjp.jsif (card.number == "" || card.cvc == "") { alert("入力もれがあります");jsの記載です。定義した変数cardの、numberもしくはcvcが空だった時に受付をせずエラーを返す処理をしています。
payjp.js} else { Payjp.createToken(card, function(status, response) { if (status === 200 ) { $("#card_number").removeAttr("name"); $("#cvc").removeAttr("name"); $("#exp_month").removeAttr("name"); $("#exp_year").removeAttr("name"); $("#card_token").append( $('<input type="hidden" name="payjp-token">').val(response.id) ); $('#cardCreateForm').get(0).submit(); alert("登録に成功しました"); } else { alert("カード情報が正しくありません"); } }); } }); });カード情報を基にトークンを生成します。
これもpayjp APIの公式リファレンスを読んでください。トークンを生成の部分を読むと、トークンはどのように生成するのかと言う文章での説明と、実際の記載方法が右側に記載されているはずです。
いきなりPayjp.create~と言う記載が出てきましたが、これはリファレンスに「この通り書いたらできるよ」って書いてくれてるだけなので、ちゃんと読めばその通りにするだけです。
ここでnumber等4つの値を渡す必要があると書かれいてがために、先ほどcard変数に4つの値を定義した訳です。順番的にはここで必要とされてるから定義している訳です。if status === 200とは、カード情報が有効であり、正しくトークンが生成された場合にpayjp側が返してくれるステータス値になります。
なのでelseでエラーを返しているのは「正常に登録できなかった場合」を示しているわけです。ここからさらに細かくみていきます。
payjp.js$("#card_number").removeAttr("name"); $("#cvc").removeAttr("name"); $("#exp_month").removeAttr("name"); $("#exp_year").removeAttr("name");JS,jQueryの記載です。
カードが有効だった場合、セキュリティの観点からそれぞれのフォームに入力した値を取り除いています(idで指定したフォームのname属性をremoveする、と言う処理です)payjp.js$("#card_token").append( $('<input type="hidden" name="payjp-token">').val(response.id) ); $('#cardCreateForm').get(0).submit(); alert("登録に成功しました");カードが有効な際にPayjpから帰ってきたデータ(response.id)を、フォームに返す処理をappendで行っています。
type = hiddenを指定することで、あくまでユーザーからは見えないものの、フォームにデータを送っている形です。その上で、submitすることで、Payjpから返してもらったデータをこの後controllerに送りcreateアクションを行っていく、と言うことを実現しています。
コントローラーにうまくデータを飛ばすための記載ということですね。
これを持って次にcontrollerの処理をみていくことができます。③コントローラーにアクションを飛ばし、payjp上で顧客データを生成・顧客データを紐づくidをテーブルに保存する
credits_controller.rbrequire 'payjp' def create Payjp.api_key = ENV['PAYJP_SECRET_KEY'] if params['payjp-token'].blank? render :new else customer = Payjp::Customer.create( email: current_user.email, card: params['payjp-token'], metadata: {user_id: current_user.id} ) @credit = Credit.new(user_id: current_user.id, customer_id: customer.id, card_id: customer.default_card) if @credit.save redirect_to user_path(current_user.id) else render :new end end endここまでうまく処理ができて、最後にcontrollerでデータ作成となります。
順を追ってみていきましょう。credits_controller.rbrequire 'payjp' def create Payjp.api_key = ENV['PAYJP_SECRET_KEY'] if params['payjp-token'].blank? render :newまずrequire 'payjp'でpayjpを使える様にします。
次にSECRET_KEYを指定し、こちらがpayjpの利用権を思ったユーザーであることを示します(環境変数を用いて記載しています)その上で先ほど正常なレスポンスだった場合に送られてきたデータがparams['payjp-token']ですので、そちらがからだった場合は処理を行わずrenderする処理を記載しています。
credits_controller.rbdef create else customer = Payjp::Customer.create( email: current_user.email, card: params['payjp-token'], metadata: {user_id: current_user.id} #ここは任意 )ここで、Payjpの顧客データを作成し、変数customerに代入しています。
いきなりこの書き方が出てきました。
何度も言う様ですが公式リファレンスを見れば全てが買いてあります。今回行いたいのは顧客データの作成なので、リファレンスの中の顧客データの欄をみます。
すると、Payjp::Customer.createという記載方法や、その引数が書かれているはずです。引数の内容をみていけば何をいれるべきなのかは簡単にわかるはずです。
例えばcardと言う引数はトークンIDを指定と書かれているので、先ほど送る様に設定したトークンIDを設定すればいいことがわかります。credits_controller.rbdef create @credit = Credit.new(user_id: current_user.id, customer_id: customer.id, card_id: customer.default_card) if @credit.save redirect_to user_path(current_user.id) else render :new end endここからはRails側のテーブル処理の話です。
先ほどのリファレンスを参照すれば、Payjpの顧客が持つレスポンスデータについても確認することができます。
先ほど作成したPayjpの顧客データは変数customerに代入していたため、customer.id等で指定することで、レスポンスデータをテーブルに保存することができるわけです。(法律上クレカデータを自分のテーブルに保存することはできないため、そのデータ自体はpayjp側に保存してもらい、そのデータを呼び出すための紐付けとしてcustomer_idやcardのdataをテーブルに保存しておくわけです。
終わりに
結構な長文になってしまったので登録のみで終わらせますが、大事なのは流れを理解し、リファレンスを読むこと、これに尽きると思います。
この登録したデータを基にカード決済を行う流れについても、リファレンスを読みながら決済にはどの様な記述が必要か?引数には何を指定するのか?を見ることでわりとすんなり実装できました。
(先ほどテーブルに紐付けたことからわかる通り、決済に必要なカードデータを呼び出して引数に渡してやれば良い、と言うのはそれほど難しくなく想像できるのではないでしょうか)とはいえAPIに慣れていない状態だったのでQiitaの記事にも大分助けられつつの実装にはなりました。が、結論公式が最強、この言葉を実感を持って理解できる経験だったので記録しておきます。
*初学者ゆえ何かあればご指摘いただけると嬉しいです。
- 投稿日:2020-07-14T20:48:01+09:00
[Rails]EC2にデプロイ後、jQuery/JavaScriptが動かない
前提
Rails 6.0.3.1
ruby 2.6.3
unicorn-5.5.5
nginx/1.16.1
EC2にデプロイしたら、jQueryがお仕事をサボっていた
デプロイ後に、ブラウザでトップページを確認すると
jQuery
が動いておらず、トップページのfade inがいつまでたってもなされない状態でした。結論
app/views/layouts/_default.html.erb<%= csrf_meta_tags %> <%= csp_meta_tag %> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %> <%= stylesheet_pack_tag 'application', 'data-turbolinks-track': 'reload' %> #以下を追加 <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>ローカルでは、
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>なしでも動いていましたが、どうやら本番環境では追加しないとうまく動かない場合があるみたいです。
ちなみに
https
ではなく、http
でも動くようですが、SSL化
したアプリケーションでは大体のアプリケーションは、セキュリティの面でSSL化
すると思うので、https
でしてあげた方が良さそうです。
- 投稿日:2020-07-14T20:46:50+09:00
【Rails】チーム開発している時の頻出DB系コマンド
【こんな人に読んで欲しい】
チーム開発やってるけど、DB系コマンドが全然わからない。なんかマイグレーション系のエラーが起きる。どうなってるの?
【この記事を立ち上げた経緯】
ブランチを切ってチーム開発をしている時、(特に初期段階)、各自が各々のブランチでDBを作ってマイグレーションしてしまうので、
マイグレーションファイルやデータベース(テーブル)の状態がバラバラ。
そんな時にブランチを移動してしまうとエラーが起きる原因となります。それをどうやって修正するか?の記事です。かなりよく使うコマンドだと思うので、私もまだまだ勉強中ですが、少しでもご参考になれば幸いです。後半に、本番環境のDBコマンドも載せてあります。
【バージョンやら環境】
Rails 5.2.4.3
Ruby 2.5.1
macOS Catalina 10.15.4
AWS【開発環境(ローカル)】
まずは、現在のマイグレーションファイルの状態から確認。
マイグレーションファイルがデータベース(テーブル)に適用されているかどうかを調べるコマンドです。ターミナルrails db:migrate:status例えばこんな感じ
Migration IDとMigration Nameは、db/migrateの中のファイルとリンクしています。
■Status
・「up」 ▶︎ 適用されている
・「down」 ▶︎ 適用されていない(削除や修正が可能)
ちなみに、
・「********** NO FILE **********」 と表示される場合があります。これは、マイグレーションファイル一回作ったことあるけど、いまはdb/migrate/ディレクトリの下に見当たらないよって場合に表示されるらしいです。
状態もわかったので、ロールバックします。
ターミナルrails db:rollback STEP=Xロールバック、とは「一度データベースからマイグレーションファイルを差し戻す」こと。
「X」には戻したいファイルの数を入れます。4つ戻したければ4。下のファイルからdownになっていきます。
ロールバックすることで、ファイルをupからdown(*****)の状態にします。参考画像では6つファイルありますが、Xに4を入れましてすロールバックするとこんな感じ
全部 downになったので、再度マイグレーションしてテーブルを再作成していきます。ターミナルrails db:migrateなれない時は都度rails db:migrate:statusで状態を確認してみるといいと思います!!
ちなみに、うまくいかない場合はrails db:migrate:resetという、リセットしてマイグレーションするコマンドもあります。ターミナル (リセットとマイグレーションを同時に行う)rails db:migrate:resetどちらにしても、にローカルで保存していた画像やら登録したユーザーやらも消えてしまいますが・・・まあ、やむなしですね。
ちなみに、私の場合は、基本元のブランチで全てdownにしてから、作業するブランチに行ってrails db:migrateをしていました。
【参考までに本番環境】
なお、本番環境でのDBコマンドは下記の通りです。
(デプロイ担当者に捧げます、がんばれ)ステータス確認rails db:migrate:status RAILS_ENV=productionデータベース完全リセット(currentで行いました)RAILS_ENV=production DISABLE_DATABASE_ENVIRONMENT_CHECK=1 bundle exec rake db:migrate:reset※私は本番環境で上記の方法しか使ったことがないですが、他に方法があるんでしょうか?
マイグレーションrails db:migrate RAILS_ENV=productionSeedrake db:seed RAILS_ENV=production以上になります!!!
【一部参考にさせていただいたサイト】
Railsガイド Active Record マイグレーション
※ver6.0のページを見ています。【あとがき】
うちのチームにはDB系のコマンドを交通整理してくれたDB担当メンバーがいるのですが、本当に感謝しかありません。おかげさまでうちのチームはマイグレーションパニックがほとんど起きませんでした。最終課題後少し(多分)
(この記事、大丈夫かな、役に立つかなってドキドキしてます・・・)
- 投稿日:2020-07-14T19:32:06+09:00
Rails5でECサイトを作る⑩ ~注文機能を作る~
はじめに
架空のベーカリーで買い物できるECサイトを作るシリーズ、Rails5でECサイトを作る⑨の続きです。このサイトの主要機能であり最も難しい箇所であるOrderモデル(注文機能)周辺を実装します。
ユーザの動きは以下のようになります。
カートに商品を入れて「注文画面に進む」ボタン押下 ↓ 注文情報入力(orders/new)画面 ・支払方法 ・お届け先 を入力、「確認画面へ進む」ボタン押下 ↓ 注文情報確認(orders/confirm)画面 内容を確認して「注文を確定する」ボタン押下 ↓ orders/thanks画面を表示(静的ページ) 注文履歴を一覧、詳細画面で確認できるポイントは、orders/new画面で入力するお届け先が、「自分の住所」「登録した住所」「新しい住所」から選べることです。新しい住所を選択してnew画面のフォームに内容を入力すると、Orderのデータとして保存されるとともに、Addressモデルにも新規データとして保存されます。
フォームを入れ子にすることと、そのデータを保存する前に確認画面を経由することが、この機能の実装を難しくしている要因です。ソースコード
https://github.com/Sn16799/bakeryFUMIZUKI
Modelのアソシエーション
Controller
app/controllers/orders_controller.rbclass OrdersController < ApplicationController before_action :authenticate_customer! before_action :set_customer def index @orders = @customer.orders end def create if current_customer.cart_items.exists? @order = Order.new(order_params) @order.customer_id = current_customer.id # 住所のラジオボタン選択に応じて引数を調整 @add = params[:order][:add].to_i case @add when 1 @order.post_code = @customer.post_code @order.send_to_address = @customer.address @order.addressee = full_name(@customer) when 2 @order.post_code = params[:order][:post_code] @order.send_to_address = params[:order][:send_to_address] @order.addressee = params[:order][:addressee] when 3 @order.post_code = params[:order][:post_code] @order.send_to_address = params[:order][:send_to_address] @order.addressee = params[:order][:addressee] end @order.save # send_to_addressで住所モデル検索、該当データなければ新規作成 if Address.find_by(address: @order.send_to_address).nil? @address = Address.new @address.post_code = @order.post_code @address.address = @order.send_to_address @address.addressee = @order.addressee @address.customer_id = current_customer.id @address.save end # cart_itemsの内容をorder_itemsに新規登録 current_customer.cart_items.each do |cart_item| order_item = @order.order_items.build order_item.order_id = @order.id order_item.product_id = cart_item.product_id order_item.quantity = cart_item.quantity order_item.order_price = cart_item.product.price order_item.save cart_item.destroy #order_itemに情報を移したらcart_itemは消去 end render :thanks else redirect_to customer_top_path flash[:danger] = 'カートが空です。' end end def show @order = Order.find(params[:id]) if @order.customer_id != current_customer.id redirect_back(fallback_location: root_path) flash[:alert] = "アクセスに失敗しました。" end end def new @order = Order.new end def confirm @order = Order.new @cart_items = current_customer.cart_items @order.how_to_pay = params[:order][:how_to_pay] # 住所のラジオボタン選択に応じて引数を調整 @add = params[:order][:add].to_i case @add when 1 @order.post_code = @customer.post_code @order.send_to_address = @customer.address @order.addressee = @customer.family_name + @customer.first_name when 2 @sta = params[:order][:send_to_address].to_i @send_to_address = Address.find(@sta) @order.post_code = @send_to_address.post_code @order.send_to_address = @send_to_address.address @order.addressee = @send_to_address.addressee when 3 @order.post_code = params[:order][:new_add][:post_code] @order.send_to_address = params[:order][:new_add][:address] @order.addressee = params[:order][:new_add][:addressee] end end def thanks end private def set_customer @customer = current_customer end def order_params params.require(:order).permit( :created_at, :send_to_address, :addressee, :order_status, :how_to_pay, :post_code, :deliver_fee, order_items_attributes: [:order_id, :product_id, :quantity, :order_price, :make_status] ) end endいつものようにform_withで送られた情報をそのまま保存できれば楽なのですが、form_withは確認画面をはさむことができません。そのため、confirm、createアクションではviewから受け取ったパラメータをparams[:hoge]の形で取り出しています。
また、createにおいては一つのアクション内で,「Orderデータを新規作成」「CartItemからOrderItemに商品データを移す(OrderItemの新規作成と登録済みCartItemの削除)」Addressモデル内に一致するデータがない場合のみ新規登録」の3つをこなさなければなりません。一つ一つの動作は複雑ではありませんが、全体のコード量がかなり多くなります。
View
new
app/views/orders/.html.erb<div class="col-lg-10 offset-1 space"> <div class="row"> <div class="col-lg-4"> <h2>注文情報入力</h2> </div> </div> <%= form_with(model: @order, local: true, url: {action: 'confirm'}) do |f| %> <!-- 支払方法 --> <div class="row space"> <h3><strong><%= f.label :支払方法 %></strong></h3> </div> <div class="row"> <div class="col-lg-4 btn-group" data-toggle="buttons"> <label class="btn btn-outline-secondary active" style="width:50%"> <%= f.radio_button :how_to_pay, true, {checked: true} %> クレジットカード </label> <label class="btn btn-outline-secondary" style="width:50%"> <%= f.radio_button :how_to_pay, false, {} %> 銀行振込 </label> </div> </div> <!-- お届け先 --> <div class="row space"> <h3><strong><%= f.label :お届け先 %></strong></h3> </div> <!-- 自身の住所 --> <div class="row"> <p> <label><%= f.radio_button :add, 1, checked: true, checked: "checked" %>ご自身の住所</label><br> <%= @customer.post_code %> <%= @customer.address %> <%= @customer.full_name %> </p> </div> <!-- 登録済み住所 --> <div class="row space-sm"> <p> <label><%= f.radio_button :add, 2, style: "display: inline-block" %>登録住所から選択</label><br> <%= f.collection_select :send_to_address, @customer.addresses, :id, :address %> </p> </div> <!-- 新しい住所 --> <div class="row space-sm"> <p><label><%= f.radio_button :add, 3 %>新しいお届け先</label></p> </div> <div class="row"> <div class="col-lg-12"> <%= f.fields_for :new_add do |na| %> <div class="row"> <div class="col-lg-3"> <strong>郵便番号(ハイフンなし)</strong> </div> <div class="col-lg-6"> <%= na.text_field :post_code, class: 'form-control' %> </div> </div> <div class="row"> <div class="col-lg-3"> <strong>住所</strong> </div> <div class="col-lg-6"> <%= na.text_field :address, class: 'form-control' %> </div> </div> <div class="row"> <div class="col-lg-3"> <strong>宛名</strong> </div> <div class="col-lg-6"> <%= na.text_field :addressee, class: 'form-control' %> </div> </div> <% end %> </div> </div> <!-- お届け先ここまで --> <div class="row space"> <div class="col-lg-2 offset-lg-7"> <%= f.submit "確認画面へ進む", class: "btn btn-danger"%> </div> </div> <% end %> </div>confirm
app/views/orders/.html.erb<div class="col-lg-10 offset-1 space"> <div class="row"> <h2>注文情報確認</h2> </div> <%= form_with(model: @order, local: true) do |f| %> <div class="d-none d-lg-block space"> <div class="row"> <div class="col-lg-5"><h4>商品名</h4></div> <div class="col-lg-2"><h4>単価(税込)</h4></div> <div class="col-lg-2"><h4>数量</h4></div> <div class="col-lg-2"><h4>小計</h4></div> </div> </div> <% sum_all = 0 %> <% @cart_items.each do |cart_item| %> <div class="row space-sm"> <div class="col-lg-3"> <%= link_to product_path(cart_item.product) do %> <%= attachment_image_tag(cart_item.product, :image, :fill, 100, 100, fallback: "no_img.jpg") %> <% end %> </div> <div class="col-lg-2"> <%= link_to product_path(cart_item.product) do %> <%= cart_item.product.name %> <% end %> </div> <div class="col-lg-2"> <%= price_include_tax(cart_item.product.price) %> </div> <div class="col-lg-2"> <%= cart_item.quantity %> </div> <div class="col-lg-2"> <%= sum_product = price_include_tax(cart_item.product.price).to_i * cart_item.quantity %>円 <% sum_all += sum_product %> </div> </div> <% end %> <div class="row space"> <div class="col-lg-12"> <div class="row"> <div class="col-lg-3"> <strong>送料</strong> </div> <div class="col-lg-3"> <%= @order.deliver_fee %>円 </div> </div> <div class="row"> <div class="col-lg-3"> <strong>商品合計</strong> </div> <div class="col-lg-3"> <%= sum_all.to_i %>円 </div> </div> <div class="row"> <div class="col-lg-3"> <strong>ご請求額</strong> </div> <div class="col-lg-3"> <% billling_amount = sum_all + @order.deliver_fee.to_i %> <%= billling_amount.to_i %>円 </div> </div> </div> </div> <div class="row space-sm"> <div class="col-lg-2"> <h3>支払方法</h3> </div> <div class="col-lg-4"> <%= how_to_pay(@order.how_to_pay) %> </div> </div> <div class="row space-sm"> <div class="col-lg-2"> <h3>お届け先</h3> </div> <div class="col-lg-4"> <%= @order.post_code %> <%= @order.send_to_address %> <%= @order.addressee %> </div> </div> <%= f.hidden_field :customer_id, :value => current_customer.id %> <%= f.hidden_field :post_code, :value => "#{@order.post_code}" %> <%= f.hidden_field :send_to_address, :value => "#{@order.send_to_address}" %> <%= f.hidden_field :addressee, :value => "#{@order.addressee}" %> </div> <div class="row space"> <div class="col-lg-2 offset-lg-5"> <%= f.submit "購入を確定する", class: "btn btn-danger btn-lg" %> </div> </div> <% end %> </div>thanks
app/views/orders/.html.erb<div class="col-lg-10 offset-1 space"> <h2>ご購入ありがとうございました!</h2> <h3><%= link_to 'TOPへ戻る', customer_top_path %></h3> </div>「購入を確定する」ボタンを押した後に遷移するページです。静的なページでこれといった機能はないので、上記のように質素な文言だけ載せるも良し、スライダーなどで写真を表示するも良しです。
index
app/views/orders/.html.erb<div class="col-lg-10 offset-1 space"> <div class="row"> <h2>注文履歴一覧</h2> </div> <div class="d-none d-lg-block space"> <div class="row"> <div class="col-lg-2">注文日</div> <div class="col-lg-3">配送先</div> <div class="col-lg-2">注文商品</div> <div class="col-lg-2">支払金額</div> <div class="col-lg-1">状況</div> <div class="col-lg-2">注文詳細</div> </div> </div> <% @orders.each do |order| %> <div class="row space-sm"> <div class="col-lg-2"> <%= simple_time(order.created_at) %> </div> <div class="col-lg-3"> <div class="row"> <%= order.post_code + " " + order.send_to_address %> </div> <div class="row"> <%= order.addressee %> </div> </div> <div class="col-lg-2"> <% sum_all = 0 %> <% order.order_items.each do |order_item| %> <%= order_item.product.name %><br> <% sub_total = price_include_tax(order_item.order_price).to_i * order_item.quantity %> <% sum_all += sub_total.to_i %> <% end %> </div> <div class="col-lg-2"> <%= sum_all += order.deliver_fee.to_i %>円 </div> <div class="col-lg-1"> <%= order_status(order) %> </div> <div class="col-lg-2"> <%= link_to '表示する', order_path(order), class: "btn btn-sm btn-danger" %> </div> </div> <% end %> </div>show
app/views/orders/.html.erb<div class="col-lg-10 offset-1 space"> <div class="row"> <h2>注文履歴詳細</h2> </div> <div class="row"> <div class="col-lg-7"> <div class="row space"> <h3>注文情報</h3> </div> <div class="row"> <div class="container"> <div class="row space-sm"> <div class="col-lg-3"> <strong>注文日</strong> </div> <div class="col-lg-9"> <%= simple_time(@order.created_at) %> </div> </div> <div class="row space-sm"> <div class="col-lg-3"> <strong>配送先</strong> </div> <div class="col-lg-9"> <%= @order.send_to_address %> </div> </div> <div class="row space-sm"> <div class="col-lg-3"> <strong>支払方法</strong> </div> <div class="col-lg-9"> <%= how_to_pay(@order.how_to_pay) %> </div> </div> <div class="row space-sm"> <div class="col-lg-3"> <strong>状況</strong> </div> <div class="col-lg-9"> <%= order_status(@order) %> </div> </div> </div> </div> <div class="row space"> <h3>注文内容</h3> </div> <div class="d-none d-lg-block"> <div class="row"> <div class="col-lg-4"> <strong>商品</strong> </div> <div class="col-lg-3"> <strong>単価(税込)</strong> </div> <div class="col-lg-2"> <strong>個数</strong> </div> <div class="col-lg-2"> <strong>小計</strong> </div> </div> </div> <% sum_all = 0 %> <% @order.order_items.each do |order_item| %> <div class="row space-sm"> <div class="col-lg-4"> <%= order_item.product.name %> </div> <div class="col-lg-3"> <%= price_include_tax(order_item.order_price) %> </div> <div class="col-lg-2"> <%= order_item.quantity %>個 </div> <div class="col-lg-2"> <%= sub_total = price_include_tax(order_item.order_price).to_i * order_item.quantity %>円 <% sum_all += sub_total %> </div> </div> <% end %> </div> <div class="col-lg-5"> <div class="row space"> <h3>請求情報</h3> </div> <div class="row"> <div class="container"> <div class="row space-sm"> <div class="col-lg-6"> <strong>商品合計</strong> </div> <div class="col-6"> <%= sum_all %>円 </div> </div> <div class="row space-sm"> <div class="col-lg-6"> <strong>配送料</strong> </div> <div class="col-lg-6"> <%= @order.deliver_fee %>円 </div> </div> <div class="row space-sm"> <div class="col-lg-6"> <strong>ご請求額</strong> </div> <div class="col-lg-6"> <%= sum_all + @order.deliver_fee.to_i %>円 </div> </div> </div> </div> </div> </div> </div>上図において、「請求情報」欄に商品の合計額を表示したいのですが、上から順番に画面を構築すると「請求情報」と「注文内容」でそれぞれループを回す必要が出てきて二度手間になってしまいます。そこで、ズルいやり方だよなと思いつつ、colで画面を縦に2分割してまず「注文情報」「注文内容」の欄を作り、「注文内容」のループ内でついでに合計額を計算して変数に格納し、「請求情報」欄で変数を呼び出す、ということをしています。
(「請求情報」欄において、DB内で配送料のdefault値を設定し忘れたので商品合計とご請求額が同じ金額になっています。本来は商品合計に配送料800円がご請求額に加算される仕様で、viewのコードもそれに対応したものとなっています。)後記
このECサイトの中枢機能だけあって、コード量も多いし内容も複雑でしたね。今回の実装では、おおよその部分では以前私がスクールのチーム実装で作ったものを踏襲しつつ、ヘルパーやモデルのメソッドを新たに定義してコードを見やすくしたり、画面をレスポンシブ対応にしたりといった改変を加えました。
createアクションで新しい住所を選択したらOrderと同時にAddressにも住所データが登録される仕様について追記です。上記の記述ではAddressモデルのaddressカラムを検索して、一致するものがなければ登録する流れになっています。より正確な検索をするなら、
unless Address.find_by(addressee: @order.addressee, address: @order.send_to_address).exists?として、宛名・住所ともに一致するかを確かめても良いかも知れません。コードは余計に長くなりますが。
何はともあれ、これでcustomerサイトの機能は揃いました。めでたしめでたし。
- 投稿日:2020-07-14T16:49:10+09:00
【Rails】form_withに記述するlocal: trueについて
- 投稿日:2020-07-14T15:23:55+09:00
ActionController::UrlGenerationErrorを解決したお話
解決したいこと
トップページ作成中にマイページへアクセスしようとした際に
https://gyazo.com/979b656dfec9fe3815acef2cd0582933
こちらのエラーが発生%li.Header-nav__listsRight--mypage = link_to "マイページ", user_path こちらのuser_pathに問題がある模様user_pathに現在ログイン中のユーザーを取得させればよかった。
解決法
= link_to "マイページ", user_path(current_user)path指定の後に(current_user)をつければ良いとのことでした
- 投稿日:2020-07-14T15:09:32+09:00
macOS CatalinaにRailsをインストールする
はじめに
macOS CatalinaにRailsをインストールする方法です。
Railsチュートリアルなどでローカル環境にセットアップする場合は、ご参考ください。前提条件
- OS: macOS Catalina
- バージョン: 10.15.5
- Ruby: 2.7.1
- Rails: 5.1.6
インストール手順
1. Command line tools をターミナルからインストール:
xcode-select --install
2. Homebrewをインストール:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
3. rbenvをインストール:
brew update
brew install rbenv
echo 'eval "$(rbenv init -)"' >> ~/.zshrc
source ~/.zshrc
※macOS Catalinaからbashからzshに変更になっています。
4. rbenvを使ってRubyをインストール:
rbenv install 2.7.0
5. デフォルトのRuby を設定:
rbenv global 2.7.0
6. 必要なソフトウェアをインストール:
brew install yarn
7. Railsのインストール:
gem install rails -v 5.1.6
※ターミナルを再起動しないとデフォルトのRubyが呼び出されてエラーになる可能性があります。参考
- 投稿日:2020-07-14T13:29:10+09:00
1つの商品に複数画像を登録。ちょっと何言ってるか、わかる!!~part3~
前提
- ruby on rails 6.0.0 を使用。
- ユーザー機能はdeviseにより導入されているものとする。
- viewファイルは全てhaml形式とする。
- ちなみに使っているのはMacBook Air(Retina, 13-inch, 2020)です。
はじめに
前回(part2)のあらすじです。carrierwaveのuploaderを使って商品登録の際に画像を投稿できる機能を実装しました。
前置きや手順などは part1 に詳しく記載してあるので気になったら騙されたと思ってクリックしてLGTMを押してみましょう。特に何も起こりません。
前回で画像1枚のみの投稿はできるようになったので今回は最大5枚まで一気に登録できるようにしていきます。手順の3番目にあたる、 「jQueryを導入して複数画像の投稿を実装」ですね。
1つ画像を登録するのに3万年かかったのに5つ登録しようと思ったらそれはもう15万年かかるのでは?(あほ)
そんなことはさておき、
画像を複数投稿する
前置きが長くなってしまいましたが、尻込みしていても仕方がないのでさっそくやっていきましょう。
現在の状態だと、画像のフォームは一つしか表示されておらず、一つ選択してしまったらそれで終わりとなってしまっています。そこで登場するのがjavascript。(今回はjQueryを使います)
最終的に、一つ画像を入力したら新しいフォームが出現し、最大5回まで画像を登録できるといった状態ができればゴールとします。jQueryについて
まずはjQueryの導入からですが、Rails6において、私の知る限りjQueryの導入方法には2つの方向性があります。
- gemとしてjQueryをインストールする方法
- webpackerを通してjQueryを呼び出す方法
今回は先述のgemを通した方法でやりました。混乱する人も多いと思うのでjQueryの導入方法は番外編として後ほど投稿しようと思います。というか僕が大混乱しました。
時を飛ばそう
というわけで、無事jQueryが導入できたていで進めていきましょう。
app/views/products/_form.html.haml= form_with model: @product, local: true do |f| = f.text_field :name, placeholder: 'name' #image-box = f.fields_for :images do |i| .group{ data: { index: i.index } } = i.file_field :src, 'file' %br %span.remove 削除 = f.submit 'SEND'まずはformのビューファイルにクラスやIDをつけていきます。{ data: ~ } の部分はカスタムデータ属性の指定です。他は特に問題ないと思います。基礎的なことをhamlで書いているだけです。
app/assets/javascripts/product.js$(function() { const buildFileField = (index)=> { const html = `<div data-index="${index}" class="group"> <input class="file" type="file" name="product[images_attributes][${index}][src]" id="product_images_attributes_${index}_src"><br> <div class="remove">削除</div> </div>`; return html; } let fileIndex = [1,2,3,4,5]; $('#image-box').on('change', '.file', function(e) { $('#image-box').append(buildFileField(fileIndex[0])); fileIndex.shift(); fileIndex.push(fileIndex[fileIndex.length - 1] + 1) }); $('#image-box').on('click', '.remove', function() { $(this).parent().remove(); if ($('.file').length == 0) $('#image-box').append(buildFileField(fileIndex[0])); }); });こちらがjQueryの記述となります。番外編で詳しく書きますが、Rails6のassetsフォルダにjavascriptはないので、自分で作成していただく必要があります。
それでは順を追って解説していきましょう。
let fileIndex = [1,2,3,4,5]; $('#image-box').on('change', '.file', function(e) { // ファイルが選択されたときfileIndexの最初の数字をindexとして持ったフォームを新しく作成する。 $('#image-box').append(buildFileField(fileIndex[0])); // fileIndexの最初の数字を削除して数字をひとつずつ左へずらす。 fileIndex.shift(); // fileIndexの最後の数字に1を足した数字を最後尾に挿入する。 fileIndex.push(fileIndex[fileIndex.length - 1] + 1) });まずはこちら、表示されているフォームで画像を選択した際に、新しいフォームが表示される。といった処理ですね。一行ずつの考え方を追記しておきました。これを繰り返すことで、ひとつ一つのフォームに固有のindexを持たせることができます。
const buildFileField = (index)=> { const html = `<div data-index="${index}" class="group"> <input class="file" type="file" name="product[images_attributes][${index}][src]" id="product_images_attributes_${index}_src"><br> <div class="remove">削除</div> </div>`; return html; }先ほどの buildFileField の処理を記述したものです。先ほど作った固有のindexを引数として渡しています。``で囲われている部分は、_form.html.haml で記述されていたフォームをhtmlとして記述し直し、あとで参照できるようにidとnameをつけているだけです。
$('#image-box').on('click', '.remove', function() { // クリックされた.removeの親要素を削除する。 $(this).parent().remove(); // フォームの数が0になった際、新しいフォームを表示させる。 if ($('.file').length == 0) $('#image-box').append(buildFileField(fileIndex[0])); });最後にフォームを削除する処理ですね。こちらも一行ずつ書いておきました。
さて、ここまでで登録画面における処理はひとまずできました。jQueryの書き方さえ分かっていれば特に難しいこともなかったかと思います。
そういえばfileやremoveなど、一部メソッドなのかクラスなのかがわかりにくかったかもしれませんが、'.~'という形で記述されているのは全てクラスになります。慣れれば簡単に見分けられます。最後に
大袈裟な前置きをしていましたが、実はデータベースへの複数登録自体は前回で終わっているのです。なので今回はフロントにおける作業がメインでしたね。
ようやく形になってきました。今回で画像の複数登録はできたので、次のpartでは複数画像の編集を行っていきたいと思います。
ではまた次のpartでお会いしましょう。
- 投稿日:2020-07-14T13:04:26+09:00
[Rails]ransackで関連するモデル(親や子)のカラムをまたいで検索する方法
実現したいこと
1つのモデルに関連(ネスト)するモデルのカラムまで、検索対象にしたい
具体的には、古着屋の店舗名だけでなく、エリア名(1対多)や取り扱いブランド名(多対多)まで含めて一括検索したい。
結論
フォームタグの要素名に、
関連するモデル名_関連するモデルのカラム名
を指定する関連するモデルが、
対1
(belongs_to: hoge、やhas_one: fuga)例えば、shopモデルに紐づくareaモデルのエリア名(name)を検索条件にしたい時
= f.フォームヘルパー :要素名
の要素名
をarea_name_cont
とする。分解すると
area → 関連するモデル名
name → 関連するモデルのカラム名
cont → 部分一致を指定する述語となります。
= search_form_for(@q, url: shop_search_path) do |f| = f.text_field :area_name_cont # shopモデルに紐づくareaモデルの、エリア名(name)関連するモデルが、
対多
(has_many: hogesなど)例えば、shopモデルに紐づくbrandsモデルのブランド名(name)を検索条件にしたい時
= f.フォームヘルパー :要素名
の要素名
をbrands_name_cont
とする。分解すると
brands → 関連するモデル名 ※shop has_many: brandsなので複数形
name → 関連するモデルのカラム名
cont → 部分一致を指定する述語となります。
= search_form_for(@q, url: shop_search_path) do |f| = f.text_field :brands_name_or_genres_name_cont # shopモデルに紐づくbrandモデルの、ブランド名(name) # shopモデルに紐づくgenreモデルの、ジャンル名(name) # ※紐づくモデルが複数の時は、モデル名が複数形になることに注意ちなみに、
_or_
などでカラム名を繋ぐと、複数カラムを検索対象にできます。実際のransackの使い方などについては、[Rails]ransackを利用した色々な検索フォーム作成方法まとめなどの記事を参考にしてください。
モデル間のアソシエーション
※関連する箇所のみ記載
shop.rb# shopモデル belongs_to :area, optional: true has_many :shop_genres has_many :shop_brands has_many :genres, through: :shop_genres has_many :brands, through: :shop_brandsarea.rb# areaモデル has_many :shopsbrand.rb# brandモデル has_many :shop_brands has_many :shops, through: :shop_brandsshop_brand.rb# shop_brandモデル belongs_to :shop belongs_to :brandshop_genre.rb# shop_genreモデル belongs_to :shop belongs_to :genre関連を調べる方法
ransackable_associations
というメソッドを使うと便利です。1. アプリケーションディレクトリでrails c
terminal# 該当のアプリケーションディレクトリで実行 $ rails c Running via Spring preloader in process 61541 Loading development environment (Rails 5.0.7.2) [1] pry(main)>2.
モデル名.ransackable_associations
を実行terminal# 今回はShopモデルとの関連を調べたいので、Shop.ransackable_associationsとすると Shopモデルに紐づくモデルが表示される [1] pry(main)> Shop.ransackable_associations => ["user", "area", "shop_genres", "shop_brands", "genres", "brands"] [2] pry(main)>参考
Ransackで簡単に検索フォームを作る73のレシピ -026 関連
Ransackで親テーブルや子テーブルのカラムで複数検索する方法
- 投稿日:2020-07-14T12:48:51+09:00
【随時更新】Ruby on Rails 便利なメソッド
- 投稿日:2020-07-14T09:37:59+09:00
railsにgRPCクラアント導入
経緯
会社でPMを担当しているプロダクト(Rails)の実装で他のサービスから情報を取得する必要があり、そのサービスがOpenAPIでなく、gRPCでAPIインタフェースが定義されていたので、gRPCのクライアントを実装しました。
僕が前職でrailsでのgRPC周りを少し触っていたので、相対的に僕がやった方がチームの開発工数が増えると思ったので自分でやることにしました。
それについてのアウトプットです。実装手順
1. gem導入
※前職で使っていたgrufというライブラリを使いました。
Gemfilegem "google-protobuf" gem "grpc-tools" gem "gruf"※ webというコンテナで開発してる
docker-compose run --rm web bundle install2. protoファイルが管理されているリポジトリをsubmoduleでアプリケーションのサブディレクトリとして登録して、protoファイルを元のリポジトリから取ってくる
$ git submodule add [web URL or ssh key] proto $ git submodule init $ cd proto $ git submodule update3. protoファイルをrubyファイルにコンパイル
docker-compose run --rm web grpc_tools_ruby_protoc -I [コンパイル対象のprotoディレクトリ] --ruby_out=[コンパイル後のファイル保存先のディレクトリ] --grpc_out=[コンパイル後のファイル保存先のディレクトリ] [コンパイル対象のprotoディレクトリ内の対象ファイル]4. コンパイル後のrubyファイル(*_pb.rb)の読み込み設定
コンパイル後のファイルは、ファイル名と、クラス名が噛み合っておらず、Railsの読み込み規則に則っていなく自動読み込まれないので、指定してあげる必要がある。
config/initializers/gruf.rbrequire "gruf" Gruf.configure do Dir.glob(Rails.root.join("[コンパイル後のファイル保存先のディレクトリ]/*_pb.rb")).each do |file| require file end endコンパイル後のファイルでは下記のように自動で指定されており、
auto_load_path
に追加しておく必要がある。
e.g. gruf-demoから
require 'Products_pb'
※コンパイル後のファイルは基本修正しないので。
config/application.rbclass Application < Rails::Application config.paths.add [コンパイル後のrubyファイルディレクトリ], eager_load: true end5. クライアントがサーバをコールする部分の実装
ここまでで、全てのコンパイル後のrubyファイルは使えるようになったでのクライアントの実装。
moduleにしようかとか悩みましたが、既存の実装でクライアント系の処理はservice層にまとめていたので、今回もそれに習う形にしました。
※特に
metadata
周りはサーバ側の実装に依存するので注意。
grufのwikiだと、クライアントの初期化(Gruf::Client.new
)時のoptions
引数のキーでusername
を入れていたりとこの辺が今回の実装と違っており、若干悩みました。app/services/grpc_client_service.rbclass GrpcClientService def initialize @metadata = { login: ENV["GRPC_CLIENT"], password: ENV["GRPC_PASSWORD"] } end def run(service_klass, method, request) client = Gruf::Client.new( service: service_klass, options: { hostname: ENV["GRPC_HOST"], channel_credentials: :this_channel_is_insecure } ) client.call(method, request.to_h, @metadata) end end導入してみての感想とか
前職でgrufは使っていたので、余裕かと思っていましたが、やはり導入するのと、ただ使うだけでは結構違うなと思いました。
しっかり設定周りのコードを読んでおけば良かったと後悔してます。また、今回gRPCサーバがgoで書かれており、クライアントからのコールがうまく行かないときにコード読むのに苦労して結局諦めたので、goも勉強したいなと思いました。
また少し詰まったのは、クライアントの初期化(
Gruf::Client.new
)時にmetadata
を入れると謎の勘違いしており(本当はcall時の引数に入れる)、ライブラリのWikiに書いてないことはやはり、しっかりコード読まないといけないなと初歩的なことを改めて実感しました。
- 投稿日:2020-07-14T06:23:44+09:00
ridgepole [ERROR] uninitialized constant Rails (erb):16:in `<main>'
忘備録
自分用とはいえだいぶ読みにくいと思います。すみません。intro
いつものように開発環境コマンドプロンプトにて。
bundle exec ridgepole --config ./config/database.yml --file ./db/Schemafile --apply --dry-run
もしくは
bundle exec ridgepole --config ./config/database.yml --file ./db/Schemafile --apply
した時。[ERROR] uninitialized constant Rails (erb):16:in `<main>'???? どこの何がどうエラーなん!?!?
と困惑。エラーメッセージは「uninitialized constant Rails (erb):16:in `'」
だけでNameErrorとかも何も教えてくれなかったので。調べた感じ、とりあえず初期化時にクラスやファイルの読み込みが上手く行ってなかったりなんだな~
と思ったので、前に上手く行った段階と今回でファイルをいじったなって所をコメントアウトやら色々試していった。+元々こちらの記事を読んだことがあったので→ ridgepole 導入はまりどころ
自分で試しているうちに
「そういえばdatabase.ymlもいじって、database.ymldefault: &default adapter: mysql2 encoding: utf8mb4 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: <%= Rails.application.credentials.mysql[:username] %> ?ココ password: <%= Rails.application.credentials.mysql[:password] %> ?ココ host: localhostとかで展開してたな......」と。
dotenvじゃないですが、開発環境でcredentials使っているので(本来は本番環境だけで使うもの)
これだなと、--apply時にうまく展開されてないんだなと。
16行目でエラー出てたけど、まさにこの場所でした。試しに
database.ymlusername: testuser password: testpass1111と展開なしで直接書いたらバッチリ動きました。
ついでにdotenvを使うことにした
dotenv自体の使い方については
Railsで使える環境変数を管理できるgem(dotenv-rails)や.envの導入方法
を参考にしつつ。ついでに気になったので下記の記事も読みました。
ENV[]とENV.fetch()の違い【Rails/Ruby】
gitにdatabase.ymlはあげて管理したいので、fetchの方を使ってデフォルト値も入力してしまうと
結局見えちゃって意味ないな~と思い
出力するときはENV.fetchじゃなく普通にENV['']使いました。development: <<: *default database: skuemy_system_development username: <%= ENV['DATABASE_USERNAME'] %> password: <%= ENV['DATABASE_PASSWORD'] %>として読み込むようにしました。
しかし
ymlファイルのコメントアウトがどうも上手く働いてくれないままなんですが。
上記のように変えて、
下の方にdatabase.yml# username: <%= Rails.application.credentials.mysql[:username] %> # password: <%= Rails.application.credentials.mysql[:password] %>と下の方にメモ書きとして残しておいたのです。ですが、
コメントアウトしているにもかかわらず読み込もうとするんです。(未解決)なので
bundle exec ridgepole --config ./config/database.yml --file ./db/Schemafile --apply --dry-run
とかしても[ERROR] uninitialized constant Rails (erb):57:in `<main>'ってさっきコメントアウトしていた行、57行目でダメーって言われました。
なのでメモは別でとっておくとして、消しました。そうすると無事に動......きません。
[ERROR] Access denied for user 'ユーザー名'@'localhost' (using password: NO)とでます。つまるところデータベースへのアクセス。usernameとpasswordが間違ってるか無いよって言われます。
dotenvで展開してるのにナンデ!?なのですが。
下記の記事を読んでいたので。(2回目)
ridgepole 導入はまりどころ ==> 案3 コマンド実行時に即時展開案3にあるコマンド実行時に即時展開というのを少し編集して使うことにします。
bundle exec dotenv -f ".env" ridgepole -c config/database.yml -E development -f db/Schemafile --apply --dry-runファイルの場所は任意なので変えるのは当然として
splitしたくなかったり、
そもそもファイルは既に作ってあるので--export とか--outputは使いませんでした。「案4 rake task内から間接的に」
も動きました。実際に使っているコード一応貼っておきます。
ファイル名は適当です。
編集したのはファイルの参照部分だけです。task_ridgepole_apply.rakenamespace :db do desc 'apply Schemafile and update schema.rb' task apply: :environment do ENV['ALLOW_DROP_TABLE'] ||= '0' ENV['ALLOW_REMOVE_COLUMN'] ||= '0' ENV['RAILS_ENV'] ||= 'development' task_return = `ridgepole -E #{ENV['RAILS_ENV']} --diff config/database.yml db/Schemafile` column_condition = task_return.include?('remove_column') && ENV['ALLOW_REMOVE_COLUMN'] == '0' table_condition = task_return.include?('drop_table') && ENV['ALLOW_DROP_TABLE'] == '0' if column_condition || table_condition puts '[Warning]this task contains some risks: "remove_column" or "drop_table"' else sh "ridgepole -E #{ENV['RAILS_ENV']} -c config/database.yml --apply -f db/Schemafile" sh 'rake db:schema:dump' end end endこれで誤ってテーブルが削除されたりするリスクがだいぶ減りそうです。
Rakeタスク内でリスクを担保する
感謝です。ほんとに。
まとめ(れてない)
ymlの一部コメントアウトが効かないのは今の所1mmも意味が分かりませんが、
そのコメント部分を消して。
「bundle exec dotenv -f ".env" ridgepole -c config/database.yml -E development -f db/Schemafile --apply --dry-run」
(dry-runを付ける場合の話)
で動いているのでヨシとします。(現場猫)
余談ですが、database.yml# username: <%= ENV['DATABASE_USERNAME'] %> # password: <%= ENV['DATABASE_PASSWORD'] %>と、dotenvで展開していた方をコメントアウトしたらちゃんと(passとかが無いよって)エラーでてくれたので。
そっちはコメントアウト効いているようです。
もうよくわかりません。
Rails.application.credentialsに何かあるとしか......。