20200514のRailsに関する記事は25件です。

2020年最新【EC2】 postgresql9.6、10、11をインストールする方法

$  yum update
$  yum install postgresql

noを選択する。すると下記の文字が現れる。

postgresql is available in Amazon Linux Extra topics "postgresql9.6" and "postgresql10" and "postgresql11"

To use, run
# sudo amazon-linux-extras install :topic:

Learn more at
https://aws.amazon.com/amazon-linux-2/faqs/#Amazon_Linux_Extras

で、下記を入力すればインストールできる。
$  sudo amazon-linux-extras install postgresql9.6

$  yum install postgresql-server

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RailsにBULMAでビューを作成するまでの手順

どうも!!
Railsで、オリジナルアプリ開発を始めた、プログラミング学習歴3ヶ月の初心者です。

BULMAを導入するまでの経緯

CSSフレームワークのBULMAをRailsで使いたい!!!

「今のところJSを使わないので、BootstrapではなくてBULMAを使いたいな。」と思い立ち、
ビューで使用するまでの手順を模索していました。

BULMAの概要は以下を参照ください。
BULMA

具体的な手順

gemをGemfileに記載する

Gemfile
gem "bulma-rails"

bundle install

ターミナル
$ bundle install

application.scssでimport

application.scss
@import "bluma";

app/assets/stylesheets/application.scss(application.cssから変更してます。)
にBULMAをインポートする。

application.html.erbを編集する

application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>ShitsumonWa</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <#以下の行を忘れずに追加する >
    <%= stylesheet_pack_tag 'application' %>

  </head>

  <body>
    <%= yield %>
  </body>
</html>

app/views/layouts/application.html.erbに
<%= stylesheet_pack_tag 'application' %>を追加

Booystarapからの乗換の際に注意事項

application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>ShitsumonWa</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>

  </head>

  <body>
    <%= yield %>
  </body>
</html>

app/views/layouts/application.html.erb

<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>

ここの2行のままだと、
Sprockets::FileNotFound in コントーラー名#アクション名
というエラーが出て怒られるので、修正するのを忘れずに!!!!
原因が何かわからず、3時間ぐらいググり続けました...(本来ならこの時間でviewを作成できていた

まとめ

BULMAをRailsで利用するまでの手順は、単純です。

Rails6以降、Bootstrapが少しめんどくさくなりました。
それでも、RailsにBootstrapを導入したい人もたくさんいると思います。
以下の記事が大変参考になりました。感謝です。
Rails 6にjQueryとBootstrapを入れる

Rails6以降の変更点はこちらの記事が参考になりました。ありがたやです。
Ruby on Rails 6の主要な新機能・機能追加・変更点

個人的には、
①フロントで時間をかけるよりもしっかりバックエンドの機能実装に時間を使いたい
②実際に現役のバックエンドエンジニアの方が、現場で使用している

上記の理由でCSSフレームワークにBULMAを導入することにしました。(あと使ってて純粋に楽しい。)
アプリ開発の際に、BULMAを導入している方の参考になれば、嬉しいです。

公式: >BULMA start

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsのポリモーフィック関連付けのテストを考えてみた。

なぜ今回の記事を書いたか。

Railsの場合、ポリモーフィック関連付けを行なった際に紐づくモデルに対して実装しなければいけないメソッドの強制が行われません。そのため、メソッドの定義を忘れた場合エラーが発生します。
ただ、実装する際に忘れるメソッドのテストを書くのは難しいのでは? と個人的に思いました。
そのため、良し悪しは別として僕が実装したテストを記載します。

他に良い方法があれば是非教えてください!

実装したモデル

今回の記述に関係ない箇所に関しては省略しています。

app/models/notification.rb
class Notification < ApplicationRecord
  belongs_to :event, polymorphic: true
  
  
  
end
app/models/comment.rb
class Comment < ApplicationRecord
  has_many :notifications, as: :event, dependent: :destroy
  
  
  
  def notification_message()
    "#{self.user.name}によりコメントされました。"
  end
end

実装したテスト

ポリモーフィック関連付けに紐づくテストなので、Notificationのモデルテストに記載いたしました。

spec/models/notification_spec.rb
require 'rails_helper'

RSpec.describe Notification, type: :model do
  it 'Notificationと関連づいているクラスに対して必要なメソッドが定義されているか' do
    # 定数オブジェクトの一覧を取得。
    constants = Object.constants.map do |name|
      Object.const_get(name)
    end
    # Notificationモデルと紐づいているモデルを取得する
    models = constants.select do |c|
      # モデルクラスを絞り込む
      next unless c.class == Class && c < ActiveRecord::Base && !c.abstract_class? 
      # Notificationモデルが紐づいているか絞り込む
      c.reflect_on_all_associations.map(&:name).include?(:notifications)
    end
    models.each do |model|
      # 定義されたメソッドがあるかを確認する
      expect(model.method_defined?(:notification_message)).to be true
    end
  end
end

この実装を行う事で、自動的にポリモーフィック関連付けされたモデルに必要なメソッドが存在しているかを確認してくれる。これでポリモーフィック関連付けを行なった際にメソッド忘れは無くなります!

懸念点

  • モデルの数が多くなった時にテスト時間が増えそう
  • モデルテストで、他のモデルにメソッドがあるか確認するのは良いのか?
  • テストでここまで処理を書くのは良いのか?
  • ポリモーフィック関連付けで関連づいたモデルに対してメソッドの強制を行う方法が他にあるのではないか?
  • 継承やmoduleを使用した方が良いのではないか?

参考にしたサイト

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ActiveRecordでnew => build => save! するとどうなる

関連付けのあるモデルにおいて、親レコードをnew => 子レコードをbuild => 親レコードをsave! したときの挙動が複雑な気がしたのでメモ。

(理解力が不足しているだけかもしれない。)

検証環境: ActiveRecord 6.0.2.1

結論を言葉で

  • has_oneでは親レコードと子レコードのvalidityは独立している。
  • has_manyでは子レコードが1つでもinvalidなら親レコードもinvalidになる。

親レコードをsave!したとき、

  • 親レコードがinvalidならraiseする
  • 親レコードも子レコードもvalidな場合、insertがtransactionで囲まれて実行される
  • 親レコードがvalidで子レコードがinvalidな場合(上記の性質によりhas_oneでのみありうる)、親レコードのinsert処理のみが実行される

実験

サンプルは以下。

migrations:

class CreateTables < ActiveRecord::Migration[6.0]
  def change
    create_table :customers do |t|
    end

    create_table :banks do |t|
    end

    create_table :merchants do |t|
    end

    create_table :bank_accounts do |t|
      t.references :customer, foreign_key: true, null: false
      t.references :bank, foreign_key: true, null: false
      t.string :account_number, null: false
    end

    create_table :orders do |t|
      t.references :customer, foreign_key: true, null: false
      t.references :merchant, foreign_key: true, null: false
      t.integer :price, null: false
    end
  end
end

models:

class Customer < ApplicationRecord
  has_one :bank_account
  has_many :orders
end

class Bank < ApplicationRecord
end

class Merchant < ApplicationRecord
end

class BankAccount < ApplicationRecord
  belongs_to :customer
  validates :account_number, presence: true
end

class Order < ApplicationRecord
  belongs_to :customer
  validates :price, presence: true
end

実験するケースは以下。

  • 1 has_one
    • 1-1 invalid
    • 1-2 valid & insertable
    • 1-3 valid & uninsertable
  • 2 has_many
    • 2-1 all invalid
    • 2-2 all valid & insertable
    • 2-3 all valid & uninsertable
    • 2-4 valid&savable + valid & uninsertable
    • 2-5 invalid + valid & uninsertable
    • 2-6 invalid + valid & insertable

1-1 invalid

irb(main):118:0> customer = Customer.new
irb(main):119:0> customer.build_bank_account
=> #<BankAccount id: nil, customer_id: nil, bank_id: nil, account_number: nil, created_at: nil, updated_at: nil>
irb(main):120:0> customer.valid?
=> true
irb(main):121:0> customer.bank_account.valid?
=> false
irb(main):122:0> customer.bank_account.errors.full_messages
=> ["Account number can't be blank"]
irb(main):123:0> customer.save!
=> true
irb(main):125:0> customer.persisted?
=> true
irb(main):126:0> customer.bank_account.persisted?
=> false

1-2 valid & insertable

irb(main):156:0> customer = Customer.new
irb(main):157:0> customer.build_bank_account(account_number: "1234", bank_id: 1)
=> #<BankAccount id: nil, customer_id: nil, bank_id: 1, account_number: "1234", created_at: nil, updated_at: nil>
irb(main):158:0> customer.valid?
=> true
irb(main):159:0> customer.bank_account.valid?
=> true
irb(main):160:0> customer.save!
=> true
irb(main):161:0> customer.persisted?
=> true
irb(main):162:0> customer.bank_account.persisted?
=> true

1-3 valid & uninsertable

irb(main):138:0> customer = Customer.new
irb(main):139:0> customer.build_bank_account(account_number: "1234")
=> #<BankAccount id: nil, customer_id: nil, bank_id: nil, account_number: "1234", created_at: nil, updated_at: nil>
irb(main):140:0> customer.valid?
=> true
irb(main):141:0> customer.bank_account.valid?
=> true
irb(main):142:0> customer.save!
ActiveRecord::NotNullViolation (Mysql2::Error: Field 'bank_id' doesn't have a default value)
irb(main):143:0> customer.persisted?
=> false
irb(main):144:0> customer.bank_account.persisted?
=> false

2-1 all invalid

irb(main):176:0> customer = Customer.new
irb(main):177:0> customer.orders.build
=> #<Order id: nil, customer_id: nil, bank_id: nil, price: nil, created_at: nil, updated_at: nil>
irb(main):178:0> customer.orders.build
=> #<Order id: nil, customer_id: nil, bank_id: nil, price: nil, created_at: nil, updated_at: nil>
irb(main):187:0> customer.valid?
=> false
irb(main):188:0> customer.errors.full_messages
=> ["Orders is invalid"]
irb(main):191:0> customer.orders.map {|order| order.valid? }
=> [false, false]
irb(main):193:0> customer.orders.map {|order| order.errors.full_messages }
=> [["Price can't be blank"], ["Price can't be blank"]]
irb(main):196:0> customer.save!
ActiveRecord::RecordInvalid (Validation failed: Orders is invalid)

2-2 all valid & insertable

irb(main):001:0> customer = Customer.new
irb(main):002:0> customer.orders.build(price: 100, merchant_id: 1)
=> #<Order id: nil, customer_id: nil, merchant_id: 1, price: 100, created_at: nil, updated_at: nil>
irb(main):004:0> customer.orders.build(price: 200, merchant_id: 2)
=> #<Order id: nil, customer_id: nil, merchant_id: 2, price: 200, created_at: nil, updated_at: nil>
irb(main):005:0> customer.valid?
=> true
irb(main):006:0> customer.orders.map {|order| order.valid? }
=> [true, true]
irb(main):007:0> customer.save!
=> true
irb(main):008:0> customer.persisted?
=> true
irb(main):009:0> customer.orders.map {|order| order.persisted? }
=> [true, true]

2-3 all valid & uninsertable

irb(main):016:0> customer = Customer.new
irb(main):017:0> customer.orders.build(price: 100)
=> #<Order id: nil, customer_id: nil, merchant_id: nil, price: 100, created_at: nil, updated_at: nil>
irb(main):018:0> customer.orders.build(price: 200)
=> #<Order id: nil, customer_id: nil, merchant_id: nil, price: 200, created_at: nil, updated_at: nil>
irb(main):019:0> customer.valid?
=> true
irb(main):020:0> customer.orders.map {|order| order.valid? }
=> [true, true]
irb(main):021:0> customer.save!
ActiveRecord::NotNullViolation (Mysql2::Error: Field 'merchant_id' doesn't have a default value)
irb(main):024:0> customer.persisted?
=> false
irb(main):025:0> customer.orders.map {|order| order.persisted? }
=> [false, false]

2-4 valid&savable + valid & uninsertable

irb(main):035:0> customer = Customer.new
irb(main):036:0> customer.orders.build(price: 100, merchant_id: 1)
=> #<Order id: nil, customer_id: nil, merchant_id: 1, price: 100, created_at: nil, updated_at: nil>
irb(main):037:0> customer.orders.build(price: 200)
=> #<Order id: nil, customer_id: nil, merchant_id: nil, price: 200, created_at: nil, updated_at: nil>
irb(main):038:0> customer.valid?
=> true
irb(main):039:0> customer.orders.map {|order| order.valid? }
=> [true, true]
irb(main):040:0> customer.save!
ActiveRecord::NotNullViolation (Mysql2::Error: Field 'merchant_id' doesn't have a default value)
irb(main):041:0> customer.persisted?
=> false
irb(main):042:0> customer.orders.map {|order| order.persisted? }
=> [false, false]

2-5 invalid + valid & uninsertable

irb(main):044:0> customer = Customer.new
irb(main):045:0> customer.orders.build
=> #<Order id: nil, customer_id: nil, merchant_id: nil, price: nil, created_at: nil, updated_at: nil>
irb(main):046:0> customer.orders.build(price: 100)
=> #<Order id: nil, customer_id: nil, merchant_id: nil, price: 100, created_at: nil, updated_at: nil>
irb(main):047:0> customer.valid?
=> false
irb(main):048:0> customer.errors.full_messages
=> ["Orders is invalid"]
irb(main):049:0> customer.orders.map {|order| order.valid? }
=> [false, true]
irb(main):050:0> customer.orders.map {|order| order.errors.full_messages }
=> [["Price can't be blank"], []]
irb(main):051:0> customer.save!
ActiveRecord::RecordInvalid (Validation failed: Orders is invalid)

2-6 invalid + valid & insertable

irb(main):059:0> customer = Customer.new
irb(main):060:0> customer.orders.build
=> #<Order id: nil, customer_id: nil, merchant_id: nil, price: nil, created_at: nil, updated_at: nil>
irb(main):061:0> customer.orders.build(price: 100, merchant_id: 1)
=> #<Order id: nil, customer_id: nil, merchant_id: 1, price: 100, created_at: nil, updated_at: nil>
irb(main):062:0> customer.valid?
=> false
irb(main):063:0> customer.errors.full_messages
=> ["Orders is invalid"]
irb(main):064:0> customer.orders.map {|order| order.valid? }
=> [false, true]
irb(main):065:0> customer.orders.map {|order| order.errors.full_messages }
=> [["Price can't be blank"], []]
irb(main):066:0> customer.save!
ActiveRecord::RecordInvalid (Validation failed: Orders is invalid)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【FactoryBot】外部参照キーのカラムデータの作成方法

【FactoryBot】外部参照キーのカラムデータの作成方法

FactoryBotで外部参照キーとなるカラムデータを作成する方法を調べたので内容をまとめます.
例えばPostモデルでファクトリデータを作成する際に一緒に投稿者となるUserのファクトリデータを作成するような状況です。

目次

  1. 状況
  2. 動作環境
  3. 手順
  4. 結果
  5. 当初つまづいた点
  6. おわりに

状況

Messageモデルのテストを行うためにConversationモデルとUserモデルのデータが必要です。
下記が今回のER図です。

Messageモデルはbelongs_to :user, belongs_to :conversationというアソシエーションを持ちます。

すなわちMessageモデルのFactoryBotを実行した際にConversationモデルとUserモデルのデータを生成する必要があります。

image.png

message.rb
class Message < ApplicationRecord
  belongs_to :conversation
  belongs_to :user
end
user.rb
class User < ApplicationRecord
  has_many :senders, class_name: 'Conversation', foreign_key: :sender_id
  has_many :recipients, class_name: 'Conversation', foreign_key: :recipient_id
  has_many :messages, dependent: :destroy
end
conversation.rb
class Conversation < ApplicationRecord
  belongs_to :sender, class_name: 'User', foreign_key: :sender_id
  belongs_to :recipient, class_name: 'User', foreign_key: :recipient_id
  has_many :messages, dependent: :destroy
end

動作環境

OS : macOS Mojave 10.14.6
ruby : 2.6.3p62
rails : 5.2.4
factory_bot_rails : 5.2.0

手順

イメージとして以下の流れを実現するようにします。
STEP1. UserのFactoryBot呼び出し
STEP2. ConversationのFactoryBot呼び出し
STEP3. MessageのFactoryBot呼び出し

STEP 1. UserモデルのFactoryBotを定義

users.rb
FactoryBot.define do
  #名前が重複しないようにシーケンスを利用
  factory :user do
    sequence :name do |n|
      "user_#{n}"
    end
  end
end

STEP 2. ConversationモデルのFactoryBotを定義

associationを記載することでUserのFactoryBotが呼び出されsenderに代入されます.
ポイント association(senderやrecipient)userは異なる名前のためfactory: :userでどのfactoryを利用するのか明示しています

conversations.rb
FactoryBot.define do
  factory :conversation do

  association :sender, factory: :user
  association :recipient, factory: :user

  end
end

実際にConversaionモデルのFactoryBotを動かしてみました。
conversation.senderとconversation.recipientにFactoryBot :userで作成されたデータが入っています。

image.png

STEP 3. MessageモデルのFactoryBotを定義

messages.rb
FactoryBot.define do
  factory :message do
    sequence :body do |n|
      "message_#{n}"
    end
    #アソシエーション名とファクトリー名が異なる場合はconv factory: :conversationのように
    #記述.今回はアソシエーション名とファクトリ名が同じためconversationのみで問題ない.
    conversation
    #conversationファクトリで作成したuserデータをmessage.userに代入
    user { conversation.sender }

  end
end

結果

message_spec.rb
require 'rails_helper'

RSpec.describe Message, type: :model do
  it 'バリデーションが通る' do
    message = build(:message, body:'test message' )
    binding.pry
    expect(message).to be_valid
  end
end

message内を確認するとuserとconversationにデータが入っており、
テストをPassしている。

image.png

当初つまづいたところ

当初はSTEP 3. FactoryBot:messageのコードでblankエラーが発生していた

image.png

原因

そこでMessagemモデルのファイルを確認するとuser_idconversation_idにnullチェックがあったためBlankエラーになっていた。

image.png

つまりこのようなバリデーションがある場合はcreateメソッドを利用する必要があります。

messages.rb
FactoryBot.define do
  factory :message do
    sequence :body do |n|
      "message_#{n}"
    end

    after(:build) do |message|
      message.conversation = create(:conversation)
      message.user = message.conversation.sender
    end

  end
end

インスタンスが保存されるためidが表示される. その結果,nullチェックのバリデーションもPassできる.

image.png

おわりに

今回の件で以下のことを学びました。
1. associationメソッドを利用することで外部参照キーのデータを作成できる
2. associationメソッドはbuildのため、idのnullチェックがある場合はcreateを利用する

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails モデル作成時 ファイルアップロードエラー

問題

Sample.create({name: "hoge", icon: File::open('db/dummy-001.png')})

rails aborted!
Errno::EXDEV: Invalid cross-device link @ rb_file_s_link - (db/dummy-001.png, /tmp/7408f7ac8f26bae8efad1f94360df7e420200514-12545-30ex00.png)
/work/vendor/bundle/ruby/2.4.0/gems/paperclip-6.1.0/lib/paperclip/io_adapters/abstract_adapter.rb:62:in `link_or_copy_file'
/work/vendor/bundle/ruby/2.4.0/gems/paperclip-6.1.0/lib/paperclip/io_adapters/abstract_adapter.rb:55:in `copy_to_tempfile'
/work/vendor/bundle/ruby/2.4.0/gems/paperclip-6.1.0/lib/paperclip/io_adapters/file_adapter.rb:21:in `cache_current_values'
/work/vendor/bundle/ruby/2.4.0/gems/paperclip-6.1.0/lib/paperclip/io_adapters/file_adapter.rb:11:in `initialize'
/work/vendor/bundle/ruby/2.4.0/gems/paperclip-6.1.0/lib/paperclip/io_adapters/registry.rb:33:in `new'
/work/vendor/bundle/ruby/2.4.0/gems/paperclip-6.1.0/lib/paperclip/io_adapters/registry.rb:33:in `for'
/work/vendor/bundle/ruby/2.4.0/gems/paperclip-6.1.0/lib/paperclip/attachment.rb:100:in `assign'
/work/vendor/bundle/ruby/2.4.0/gems/paperclip-6.1.0/lib/paperclip/has_attached_file.rb:66:in `block in define_setter'
/work/vendor/bundle/ruby/2.4.0/gems/activemodel-5.2.1/lib/active_model/attribute_assignment.rb:51:in `public_send'
/work/vendor/bundle/ruby/2.4.0/gems/activemodel-5.2.1/lib/active_model/attribute_assignment.rb:51:in `_assign_attribute'
/work/vendor/bundle/ruby/2.4.0/gems/activemodel-5.2.1/lib/active_model/attribute_assignment.rb:44:in `block in _assign_attributes'
/work/vendor/bundle/ruby/2.4.0/gems/activemodel-5.2.1/lib/active_model/attribute_assignment.rb:43:in `each'
/work/vendor/bundle/ruby/2.4.0/gems/activemodel-5.2.1/lib/active_model/attribute_assignment.rb:43:in `_assign_attributes'
/work/vendor/bundle/ruby/2.4.0/gems/activerecord-5.2.1/lib/active_record/attribute_assignment.rb:23:in `_assign_attributes'
/work/vendor/bundle/ruby/2.4.0/gems/activemodel-5.2.1/lib/active_model/attribute_assignment.rb:35:in `assign_attributes'
/work/vendor/bundle/ruby/2.4.0/gems/activerecord-5.2.1/lib/active_record/core.rb:314:in `initialize'
/work/vendor/bundle/ruby/2.4.0/gems/activerecord-5.2.1/lib/active_record/inheritance.rb:66:in `new'
/work/vendor/bundle/ruby/2.4.0/gems/activerecord-5.2.1/lib/active_record/inheritance.rb:66:in `new'
/work/vendor/bundle/ruby/2.4.0/gems/activerecord-5.2.1/lib/active_record/persistence.rb:35:in `create'
/work/db/seeds.rb:41:in `block in <main>'
/work/db/seeds.rb:41:in `map'
/work/db/seeds.rb:41:in `<main>'
/work/vendor/bundle/ruby/2.4.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:50:in `load'
/work/vendor/bundle/ruby/2.4.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:50:in `load'
/work/vendor/bundle/ruby/2.4.0/gems/activesupport-5.2.1/lib/active_support/dependencies.rb:281:in `block in load'
/work/vendor/bundle/ruby/2.4.0/gems/activesupport-5.2.1/lib/active_support/dependencies.rb:253:in `load_dependency'
/work/vendor/bundle/ruby/2.4.0/gems/activesupport-5.2.1/lib/active_support/dependencies.rb:281:in `load'
/work/vendor/bundle/ruby/2.4.0/gems/railties-5.2.1/lib/rails/engine.rb:551:in `load_seed'
/work/vendor/bundle/ruby/2.4.0/gems/activerecord-5.2.1/lib/active_record/tasks/database_tasks.rb:281:in `load_seed'
/work/vendor/bundle/ruby/2.4.0/gems/activerecord-5.2.1/lib/active_record/railties/databases.rake:194:in `block (2 levels) in <main>'
/work/vendor/bundle/ruby/2.4.0/gems/railties-5.2.1/lib/rails/commands/rake/rake_command.rb:23:in `block in perform'
/work/vendor/bundle/ruby/2.4.0/gems/railties-5.2.1/lib/rails/commands/rake/rake_command.rb:20:in `perform'
/work/vendor/bundle/ruby/2.4.0/gems/railties-5.2.1/lib/rails/command.rb:48:in `invoke'
/work/vendor/bundle/ruby/2.4.0/gems/railties-5.2.1/lib/rails/commands.rb:18:in `<main>'
/work/vendor/bundle/ruby/2.4.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `require'
/work/vendor/bundle/ruby/2.4.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `block in require_with_bootsnap_lfi'
/work/vendor/bundle/ruby/2.4.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/loaded_features_index.rb:65:in `register'
/work/vendor/bundle/ruby/2.4.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:20:in `require_with_bootsnap_lfi'
/work/vendor/bundle/ruby/2.4.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:29:in `require'
/work/vendor/bundle/ruby/2.4.0/gems/activesupport-5.2.1/lib/active_support/dependencies.rb:287:in `block in require'
/work/vendor/bundle/ruby/2.4.0/gems/activesupport-5.2.1/lib/active_support/dependencies.rb:253:in `load_dependency'
/work/vendor/bundle/ruby/2.4.0/gems/activesupport-5.2.1/lib/active_support/dependencies.rb:287:in `require'
bin/rails:4:in `<top (required)>'
/usr/local/bundle/bin/bundle:23:in `load'
/usr/local/bundle/bin/bundle:23:in `<main>'
Tasks: TOP => db:seed
(See full trace by running task with --trace)
make: *** [seed] Error 1

解決

vendor 配下が何らかの理由で壊れていたようです...。

$ rm -rf vendor/
$ bundle install --path vendor/bundle
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Ruby]GraphQLで認証を実装する

はじめに

最近、graphql-rubyを勉強中です。認証周りを実装してみたので忘備録です。
ただ、この方法が正解かどうかは模索中です。

やりたいこと

あるmutationなりqueryはログインユーザしか発行できないけど、ユーザに関係ないquery等はログインしてなくても発行できるようにしたい。でも全てのqueryとかでログインしてるかどうかのコードを追加するのはめんどくさい。

まずは通常の認証

graphql_controller.rbのexcute内でuserの認証を行い、contextにcurrent_userを保持させます。

graphql_controller.rb
def excute
  current_user = User.auth_user(params)
  context: { current_user: current_user }
# その後の処理↓
...
end

上の例だと、auth_userメソッドで認証して、userオブジェクトをもらっています。
contextに引き渡すと、resolver内等でcontext[:current_user]で取得できます。

ひと工夫

さて、この場合だと、resolver内でいちいちcontext[:current_user]が存在するかどうかを確認しないといけません。かといって、excute内で弾くと全てのqueryがログインしていないと発行できません。
そこで、base_query.rbに以下のような処理を書いてやることにしました。

base_query.rb
  class BaseQuery < GraphQL::Schema::Resolver
    def current_user
      context[:current_user].presence || raise(GraphQL::ExecutionError, 'Permissions deny, Please login again')
    end
  end

queryはbase_query.rbを継承していますので、current_userを使うときにこのメソッドを呼び出してやれば良いですね。

もう少し細かくやる場合

今回はuserと紐づくかどうかで、判断しましたがもう少し細かく設定する場合には返すObjectタイプごとに指定できるようです。こっちも試してみたら記事を書こうと思います。
https://graphql-ruby.org/authorization/authorization.html
他にもっと良い方法あるよって方は教えてもらえると幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【簡潔】Brakeman導入方法-Rails-

Brakeman導入方法

Brakemanとは

Brakemanとはアプリケーションを開発した際にセキュリティ検査ができるgemの一つです。
Railsでは元からセキュリティについては比較的維持できるものですが、私が開発した際に補助的にこのようなツールを使ってみました。ちなみにBrakemanは静的なツールです。

導入方法

作成中のプロジェクトのGemfile内のgroup :development doendの間にgem 'brakeman', require: falseを追記します。

group :development do
  gem 'brakeman', require: false
end

次にターミナルの作成中アプリのディレクトリでbrakemanと叩けばBrakemanでのセキュリティチェックが始まります。

 % brakeman

出力はこんな感じです。

== Brakeman Report ==

Application Path: /Users/UserName/FolderName/AppName
Rails Version: 5.2.4.2
Brakeman Version: 4.8.2
Scan Date: 0000-00-00 00:00:0 +0000
Duration: 0.290167 seconds
Checks Run: BasicAuth, BasicAuthTimingAttack, ContentTag, CookieSerialization, CreateWith, CrossSiteScripting, DefaultRoutes, Deserialize, DetailedExceptions, DigestDoS, DynamicFinders, EscapeFunction, Evaluation, Execute, FileAccess, FileDisclosure, FilterSkipping, ForgerySetting, HeaderDoS, I18nXSS, JRubyXML, JSONEncoding, JSONEntityEscape, JSONParsing, LinkTo, LinkToHref, MailTo, MassAssignment, MimeTypeDoS, ModelAttrAccessible, ModelAttributes, ModelSerialize, NestedAttributes, NestedAttributesBypass, NumberToCurrency, PageCachingCVE, PermitAttributes, QuoteTableName, Redirect, RegexDoS, Render, RenderDoS, RenderInline, ResponseSplitting, RouteDoS, SQL, SQLCVEs, SSLVerify, SafeBufferManipulation, SanitizeMethods, SelectTag, SelectVulnerability, Send, SendFile, SessionManipulation, SessionSettings, SimpleFormat, SingleQuotes, SkipBeforeFilter, SprocketsPathTraversal, StripTags, SymbolDoSCVE, TranslateBug, UnsafeReflection, ValidationRegex, WithoutProtection, XMLDoS, YAMLParsing

== Overview ==

Controllers: 2
Models: 3
Templates: 34
Errors: 0
Security Warnings: 0

== Warning Types ==


No warnings found


セキュリティチェック完了です。

この記事を読んでいただきありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【rails】active_hash セレクトボックス作り方

目的

active_hashを使うと
セレクトボックスの選択肢を作る上で…
DBを使わずに、使うことができる。
e45d5968e89b3a236c6a55a3f25b2777.gif

準備

1.Gemfileに以下を記述する。

gem 'active_hash'

下記と合わせてサーバの再起動も行う

$ bundle install

準備はこれだけ

実装例

itemsテーブルにregion_idカラムを用意して、itemの登録の時にregion(発送元の地域)をアクティブハッシュから選択する。(下のgifのようなイメージ)
e45d5968e89b3a236c6a55a3f25b2777.gif

1.早速下記コマンドでitemsテーブルにregion_idカラムを用意

コンソール
rails g model Item region_id:integer name:string
rake db:migrate

2.Active_Hash::Baseを継承しているモデルを作成する。(app/modelsの中にファイル作成)

app/models/region.rb
class Region < ActiveHash::Base
 include ActiveHash::Associations
  has_many :items
  self.data = [
    {id: 1, name: '北海道'}, {id: 2, name: '青森県'}, {id: 3, name: '岩手県'},
    {id: 4, name: '宮城県'}, {id: 5, name: '秋田県'}, {id: 6, name: '山形県'},
    {id: 7, name: '福島県'}, {id: 8, name: '茨城県'}, {id: 9, name: '栃木県'},
    {id: 10, name: '群馬県'}, {id: 11, name: '埼玉県'}, {id: 12, name: '千葉県'},
    {id: 13, name: '東京都'}, {id: 14, name: '神奈川県'}, {id: 15, name: '新潟県'},
    {id: 16, name: '富山県'}, {id: 17, name: '石川県'}, {id: 18, name: '福井県'},
    {id: 19, name: '山梨県'}, {id: 20, name: '長野県'}, {id: 21, name: '岐阜県'},
    {id: 22, name: '静岡県'}, {id: 23, name: '愛知県'}, {id: 24, name: '三重県'},
    {id: 25, name: '滋賀県'}, {id: 26, name: '京都府'}, {id: 27, name: '大阪府'},
    {id: 28, name: '兵庫県'}, {id: 29, name: '奈良県'}, {id: 30, name: '和歌山県'},
    {id: 31, name: '鳥取県'}, {id: 32, name: '島根県'}, {id: 33, name: '岡山県'},
    {id: 34, name: '広島県'}, {id: 35, name: '山口県'}, {id: 36, name: '徳島県'},
    {id: 37, name: '香川県'}, {id: 38, name: '愛媛県'}, {id: 39, name: '高知県'},
    {id: 40, name: '福岡県'}, {id: 41, name: '佐賀県'}, {id: 42, name: '長崎県'},
    {id: 43, name: '熊本県'}, {id: 44, name: '大分県'}, {id: 45, name: '宮崎県'},
    {id: 46, name: '鹿児島県'}, {id: 47, name: '沖縄県'}
]
end

3.Itemモデルに記述

item.rb
class Item < ApplicationRecord
  # アクティブハッシュ
  extend ActiveHash::Associations::ActiveRecordExtensions
  belongs_to_active_hash :region
end

4.フォーム例(ビュー作成)

_form.html.haml
= form_for @item do |f|
  .new-wrapper__main__title.spacing
    発送元の地域
    %span.require 必須
   = f.collection_select :region_id, Region.all, :id, :name,{prompt: "選択してください"}, {class: "new-wrapper__main__input-select"}

参考文献

https://qiita.com/YJ2222/items/c2eaada06c99c051e151
https://qiita.com/Toman1223/items/8633142312bfa886d50b

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

railsでsidekiq-schedulerを動かす

公式のUsageはrailsで動かす前提じゃなかったので、railsで動かす場合のUsage的な何か。

  • 0. ばーじょん
Gemfile.lock
    rails (6.0.1) 
    sidekiq (6.0.7)
    sidekiq-scheduler (3.0.1)
  • 1. Gemfile書く
Gemfile
gem 'sidekiq-scheduler'
gem 'sinatra', require: false # ダッシュボードいらない場合はいらない
  • 2. 初期設定を書く

herokuで動かす場合は環境変数に設定しておくこともできるので、その場合はなくても構わない、多分。

config/initializers/sidekiq.rb
Sidekiq.configure_server do |config|
  config.redis = { url: ENV.fetch('REDIS_URL') { 'redis://localhost:6379' } }
end

Sidekiq.configure_client do |config|
  config.redis = { url: ENV.fetch('REDIS_URL') { 'redis://localhost:6379' } }
end
  • 3. ジョブを作る

app/jobs/内に作る。ApplicationJobを継承していれば自動的にsidekiq-schedulerがジョブとして認識してくれる。ここ参照

app/jobs/hello_world_job.rb
class HelloWorldJob < ApplicationJob
  def perform
    puts 'test'
  end
end
  • 4. ジョブを登録する

こっちの拡張子はymlですよ。

config/sidekiq.yml
:schedule:
  hello_world:
    every: '1m'
    class: HelloWorldJob
  • 5. ダッシュボード見れるようにする
config/routes.rb
require 'sidekiq/web'
require 'sidekiq-scheduler/web'

Rails.application.routes.draw do
  mount Sidekiq::Web, at: "/sidekiq"
end

これで http://localhost:3000/sidekiq のURLでsidekiqのダッシュボードが見れるようになる。

  • 6. sidekiq動かす

rails sとは別プロセスで動かす必要がある。

$ bundle exec sidekiq

こんな感じで見れたら成功。

_DEVELOPMENT__Sidekiq.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】後からカラムを追加して外部キーを張る際に、add_referenceを使う場合の注意点。

始めに

忘れがちになるため、備忘録。

環境

Ruby 2.6.5
Rails 5.2.4.2

アソシエーション対象

投稿用のpostsテーブルと、ユーザーのusersテーブルを関連付け。
postsテーブルに外部キー制約を張ったuser_idカラムを追加する

カラム追加用のマイグレーションを作成

1.マイグレーション作成

$ rails g migration AddUserIdToPosts 

2.マイグレーションファイルへの記述。foreign_key: trueを忘れずに記述すること。

db/migrate/[timestamps]_add_user_id_to_posts.rb
class AddUserIdToPosts < ActiveRecord::Migration[5.2]
  def change
    add_reference :posts, :user, foreign_key: true
  end
end

※上手く外部キー制約が追加されない記述。

db/migrate/[timestamps]_add_user_id_to_posts.rb
class AddUserIdToPosts < ActiveRecord::Migration[5.2]
  def change
    add_reference :posts, :user, index: true
  end
end

3.マイグレーションファイルの保存

$ rails db:migrate


何故、foreign_keyが必要なのか

Ruby on Rails APIより、

add_reference(table_name, ref_name, **options)

:foreign_key
Add an appropriate foreign key constraint. Defaults to false.

Create a supplier_id column and appropriate foreign key
add_reference(:products, :supplier, foreign_key: true)

外部キー制約を張るためのforeign_keyはデフォルトではfalseになるため、ただ単にadd_referenceカラムを追加しただけでは外部キー制約は張られることがないとのこと。

つまり、「user.posts」のようにアソシエーションを利用してオブジェクトを取得しようとしても、外部キーが存在しないためエラーになるので注意が必要。

参考

Railsで外部キー制約のついたカラムを作る時のmigrationの書き方
後からreferencesカラムを追加しようとするとdb:migrateできない

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】add_referenceで、カラムへ後から外部キー制約を追加する場合の注意点。

始めに

忘れがちになるため、備忘録。

アソシエーション対象

投稿用のpostsテーブルと、ユーザーのusersテーブルを関連付け。
postsテーブルに外部キー制約を張ったuser_idカラムを追加する

カラム追加用のマイグレーションを作成

1.マイグレーション作成

$ rails g migration AddUserIdToPosts 

2.マイグレーションファイルへの記述

db/migrate/[timestamps]_add_user_id_to_posts.rb
class AddUserIdToPosts < ActiveRecord::Migration[5.2]
  def change
    add_reference :posts, :user, foreign_key: true
  end
end

※上手く外部キー制約が追加されない記述。

db/migrate/[timestamps]_add_user_id_to_posts.rb
class AddUserIdToPosts < ActiveRecord::Migration[5.2]
  def change
    add_reference :posts, :user, index: true
  end
end

3.マイグレーションファイルの保存

$ rails db:migrate


何故、foreign_keyが必要なのか

Ruby on Rails APIより、

add_reference(table_name, ref_name, **options)

:foreign_key
Add an appropriate foreign key constraint. Defaults to false.

Create a supplier_id column and appropriate foreign key
add_reference(:products, :supplier, foreign_key: true)

外部キー制約を張るためのforeign_keyはデフォルトではfalseになるため、ただ単にadd_referenceカラムを追加しただけでは外部キー制約は張られることがないとのこと。

つまり、「user.posts」のようにアソシエーションを利用してオブジェクトを取得しようとしても、外部キーが存在しないためエラーになるので注意が必要。

参考

Railsで外部キー制約のついたカラムを作る時のmigrationの書き方
後からreferencesカラムを追加しようとするとdb:migrateできない

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

サジェスト機能を実装

サジェスト機能を実装

機能を追加したので学んだことをoutputしておく。
理解が深まればもう少しわかりやすく改善していく予定。

実装内容

以下の写真のように文字を入力すると候補が表示されるようにしたい。
スクリーンショット 2020-05-14 15.06.16.png

まず
gem 'jquery-ui-rails'をGemfileに記載し、
bundle installを実行
jQuery UIは、jQueryを拡張するライブラリ(プラグイン)の一種
これにより「autocomplete」が使えるようになり、候補がリストで表示される。

早速、search.rbを作成し、以下のように配置する。(今回は新たに作成したが既存のものに書いても良い。)
スクリーンショット 2020-05-14 15.11.33.png

そして以下のように書く。
スクリーンショット 2020-05-14 15.15.43.png

軽く説明
1行目はjQuery記載のためのテンプレ
9行目で以下のようなhtmlファイルに記載されたid="suggest"のiuputにユーザーが入力した時に「autocomplete」が反応する。

<input type="text" id="suggest">

source:dataによりデータの元を定義する
今回であればl2~l9で定義されたdataを呼び出しユーザーが入力した値に対して該当する値が検出される。
autoFocus:trueこのページの最初の写真のように、「a」を入力するとリストが表示されて、「apple」の部分背景がgrayになっている。trueをfalseに変えると背景はgrayにならない。
delay:300入力してから0.3秒後に表示される。
minLength:11文字以上入力された時に実行される。

参考サイト

jQuery UI

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsプロジェクトの *_spec.rb の中で xmpfilter を使う方法

要点

  1. rspec/autorun を読み込むことで rspec コマンドで実行したのと同じ状況にする
  2. require "rails_helper" などが失敗しないように -I spec を指定
  3. カレントディレクトリを Rails.root に固定するため --cd に .git のあるディレクトリを指定

(defun rubyxmp ()
  (interactive)
  (let ((s "xmpfilter --dev --fork --no-warnings --poetry")
        (rspec-p (string-match "_spec" (buffer-file-name)))
        (git-root (locate-dominating-file default-directory ".git"))
        xmpfilter-command-name)
    (when rspec-p
      (setq s (concat s " -r rspec/autorun -I spec"))
      (when git-root
        (setq s (concat s " --cd " git-root))))
    (setq xmpfilter-command-name s)
    (call-interactively 'xmp)))
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails]form_for/form_withの書き方(haml含む)

はじめに

自分が初めてform_with/form_forの文法を学んだ時に「???」となったので、
初学者にも分かるような説明記載と自分の備忘録として記載します。

まず、はじめに「form_for」はRails5.1で非推奨となっており、将来的にform_withに置き換えられる予定です。
「Rails5.1以上」と「form_with」の使用が推奨とされております。(2020/5/14現在)

「form_for」と「form_with」

1.form_for

  • Railsでフォームタグを簡単に作成するためのメソッド
  • モデルに基づくformに使用される

form_forは以下のように書きます。

<%= form_for モデル do |form| %>
  <%# フォームの部品 %>
<% end %>

具体的なコードで書くと、
※モデルはUserとする

<%= form_for @user do |form| %>
  <%= form.text_field :email %>
  <%= form.submit %>
<% end %>

Rails特有のhamlで書くと、

= form_for @user do |f|
  = f.text_field :email
  = f.submit

2.form_with

  • 自動でパスを選択してくれ、HTTPメソッド(getやpostなど)を指定する必要が無い
  • コントローラから渡された、ActiveRecordを継承するモデルのインスタンスが利用できる
  • モデルに基づかないフォームも生成できる

form_withは以下のように書きます。

<%= form_with model:'モデル名' local: true do |form| %>
  <%# フォームの部品 %>
<% end %>

上記の説明で取り上げた通り、form_withは、モデルに基づかないファームも生成できます。
具体的なコードで書くと、
※モデルはUserとする

 #関連するモデルがない場合 → urlの指定のみで、modelの記述がない
<%= form_with url: users_path do |form| %>
  <%= form.text_field :email %>
  <%= form.submit %>
<% end %>
 #関連するモデルがある場合 → modelの記述のみで、urlの指定は不要
<%= form_with model: @user do |form| %>
  <%= form.text_field :email %>
  <%= form.submit %>
<% end %>

Rails特有のhamlで書くと、

 #関連するモデルがない場合 → urlの指定のみで、modelの記述がない
= form_with url: users_path do |f|
  = f.text_field :email
  = form.submit
 #関連するモデルがある場合 → modelの記述のみで、urlの指定は不要
= form_with model: @user do |f|
  = form.text_field :email
  = form.submit

「form_for」と「form_with」の違い

1.localは、記載の必要あり。
2.remote(Ajax)は、基本的にtrue設定で記載の必要なし。
3.form_withではinputタグは用いない
上記の1と2に関しましては、以下の記事が参考になりますので、興味がございましたらご参照下さい。
https://qiita.com/Tatsu88/items/8ea9b944681a48cce589#comment-85443e109252b344989b

最後に

これからは form_with を使うことを強くお勧めします。
もしこの記事がみなさんの役に立てば幸いです。

以上となります。最後までご覧いただき、ありがとうございました!
記述に何か誤りなどございましたら、お手数ですが、ご連絡いただけますと幸いです。

参考記事

【Rails 5】(新) form_with と (旧) form_tag, form_for の違い
form_for/form_tag/form_withについて【概要+使い方】
【Rails入門説明書】form_withについて解説

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【jQuery】【rails】使えない!? 動かない

解消したいこと

jQueryが上手く動かない
しかしリロードをすれば、動くようになる
なぜ??

原因

結論、Turbolinksが邪魔している
デフォルトgemであるturbolinks(ajaxとajaxとhistoryAPIを使ってページ遷移しやすくするもの)が動作しているため、そもそも$(document).ready()が動かない
つまりページが変わっているように見えて、上記のことが起きておりjqueryは起動しない。

Turbolinksとは

ページ上のリンクをクリックした時に、ページ全体をリロードさせるのではなく、bodyタグの中身とheadの中のtitleを同一ページ上で書き換える方法。
.jsとか.cssとか処理し直さないので、ページの読み込みがかなり早くなる。

使い方

gemfileに以下を記述

gem 'jquery-turbolinks'

bundel install と サーバ再起動を行う

解消例

該当のjqueryをこの記述に入れることで解消された。

$(document).on('turbolinks:load',(function(){ }))

参考文献

https://workabroad.jp/posts/1133
https://qiita.com/hiroyayamamo/items/b258acbaa089d9482c8a

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsをやって、最近まなんだ事をまとめてみる

はじめに

自分が最近Railsで、制作していて忘れそうなことをメモ感覚で残したものなので、
頭おかしいこと書いてあるかもしれないのでご了承ください。

STI(単一テーブル継承)

1つのテーブルを継承したクラスを作成することができる。
継承することによって、子クラスは親クラスと同じカラムを使うことができるので、似たようなクラスを作って増やしたいときに、カラムの定義を1つずつ行わなくてよくなるので、手間も少なくなるので便利です。カラムを追加することもできます。

※ 親クラスには、string型typeをカラムとして追加することを忘れないように、気を付けましょう!

使い方の参考記事

モデルの関連付けについて

1対1とか、1対多などの関係定義などを行うときに、いろんなファイル内に設定を書き込まないといけないので、結構大変です。
簡単に、こんな時にこう書く的なのをまとめます。

  • 1対1のつながり(従属)
    • belongs_to を従属するモデルに書く
  • 1対1のつながり(含む、所有など)
    • has_one を所有するモデルに書く
  • 1対多のつながり
    • has_manyを所有するモデルに書く
  • 従属する(マイグレーションファイル)
    • references :従属されるモデル(単数形) を従属する側のモデルのマイグレーションファイルに書く

大体こんな感じだと思います。
Railsガイド- Active Recordの関連付け

バリデーションなど

よく使うやつと個人的に思うものをまとめます。
バリデーションで、データを確認することにより、保存したいデータのみを保存することができる。

  • presence
    • 指定した値が空かどうかを確認
  • format
    • 正規表現などを利用して、データの内容(文字など)を検証する
  • length
    • 長さを検証
      • maximum : 最大値を指定
      • minimum : 最小値を指定
      • in : 値の範囲を指定できる(以上、以下の指定ができる)
      • is : 等しくないといけないように指定できる

最後に

Railsで開発するときに、すごい悩まされたのがデータベース設計でした。今までは、本に書いてあることをそのまま作ればよいだけでしたが、自分で開発する際には、データベースのカラムに何が必要か、どんな制約を付けるか。などとても悩む部分が多かったです。でも、いろんなことにチャレンジすることはいつか自分のためになると思うので、皆さんも頑張ってください!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsでgoogleapisを使い、gmailでメール送信

事前準備

https://developers.google.com/gmail/api/quickstart/ruby
とりあえずこの通りにして、ラベルが表示されるまでやってみる

スコープの変更

サンプルコードだとreadonlyだけなので、メール送信出来るようにスコープを変える

SCOPE = Google::Apis::GmailV1::AUTH_GMAIL_READONLY
SCOPE = [
  Google::Apis::GmailV1::AUTH_GMAIL_READONLY,
  Google::Apis::GmailV1::AUTH_GMAIL_SEND
]

* ただメールを送るだけならAUTH_GMAIL_READONLYは要らないが、後に説明するプロファイルの参照時に必要

既に出来上がっているtoken.ymlはスコープが最初のままなので、このファイルを消す

メールの作成

メールは素のテキストで書くか、何らかのライブラリを使って組み立てるかする。
APIとしてはBase64でエンコードしたものを渡す必要があるが、google/apis/gmail_v1勝手にやってくれるので、素のテキストのままで渡せば問題ない。

# Railsに標準で入っている[mail gem][5]
mail = Mail.new do
  from    "from@domain.local" # credentialsを取ったgmailのアカウントから送るなら、多分必要ない get_user_profile("me") で確定するっぽい
  to      "to@domain.local"
  subject "test"
  body    "body"
end

message_object = Google::Apis::GmailV1::Message.new
message_object.raw = mail.to_s

GoogleApi.gmail_service.send_user_message("me", message_object)

自分のメールアドレスを動的に取る

fromアドレスをハードコードしたくない場合

response = GoogleApi.gmail_service.get_user_profile("me")
data = { "from" => response.email_address }
Rails.root.join("tmp/gmail_profile.yml").write YAML.dump(data)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Chartkickで入力していない日付の表示をしたくない

今回実装したいこと

Chartkickで入力していない日付の表示をしたくない。
以下のように入力していない日付のX軸の表示を消したい。

スクリーンショット 2020-05-14 10.53.03.png

結論

discrete: trueを追加する。

↓変更前

<%= line_chart @data, xtitle: "Day", ytitle: "Number", curve: false %>

↓変更後

<%= line_chart @data, xtitle: "Day", ytitle: "Number", curve: false %>

↓解決

スクリーンショット 2020-05-14 10.58.32.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【brew】bundle installでmysql2エラーが出てしまった時の解決策

homebrewを使ってrailsをAPIモードで使用する、 bundle install で以下のエラーが出てしまいました。

console.
user-no-MacBook-Pro:app user$ bundle install
An error occurred while installing mysql2 (0.5.3), and Bundler cannot continue.
Make sure that `gem install mysql2 -v '0.5.3' --source 'https://rubygems.org/'` succeeds before bundling.

解決策

そういえば、brewでmysqlを使うのが初めてだったので、まずbrewでmysqlをインストールが必要でした。

console.
user-no-MacBook-Pro:app user$ brew install mysql
...
To have launchd start mysql now and restart at login:
  brew services start mysql
Or, if you don't want/need a background service you can just run:
  mysql.server start

mysql.server.startが出力されたらmysqlインストールに成功しています。

console.
user-no-MacBook-Pro:app user$ brew info mysql

mysql: stable 8.0.19 (bottled)

ここで、bundle install 再実行

console.
user-no-MacBook-Pro:app user$ bundle install

Installing mysql2 0.5.3 with native extensions
Using puma 4.3.3
Fetching rack-cors 1.1.1
Installing rack-cors 1.1.1
Using thor 1.0.1
Using railties 6.0.3
Using sprockets 4.0.0
Using sprockets-rails 3.2.1
Using rails 6.0.3
Using spring 2.1.0
Using spring-watcher-listen 2.0.1
Bundle complete! 10 Gemfile dependencies, 55 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.

先ほどまで、installing mysql2 で引っかかっていたのが、今度はスムーズにいけました。
railsをAPIモードで使うことを前提としているので、コレで作業が進みそうです。

おまけ

Ruby on Rails+ReactでCRUDを実装してみた
https://qiita.com/yoshimo123/items/9aa8dae1d40d523d7e5d#%E3%83%87%E3%82%A3%E3%83%AC%E3%82%AF%E3%83%88%E3%83%AA%E6%A7%8B%E6%88%90

こちらを進めているのですが、

console.
user-no-MacBook-Pro:app user$ rake db:create
Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2)
Couldn't create 'app_development' database. Please check your configuration.
rake aborted!
Mysql2::Error::ConnectionError: Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2)

と出てしまいました。
rake aborted!
cant connect to local MySQL serverとあるため、mysql側にエラーがあるようです。

今回の場合、mysqlサーバーが動いていないよ!ということだったので、

console.
user-no-MacBook-Pro:app user$ mysql.server start
Starting MySQL
.. SUCCESS! 
user-no-MacBook-Pro:app user$ rake db:create
Created database 'app_development'
Created database 'app_test'

mysqlを起動することで、無事に解決しました。
今後もエラーログを見て、何がエラーになっているのかを理解してスキルを高めていきたいですね。

参考

【Rails/MySQL】RailsにMySQLを導入する方法【プログラミング学習149日目】
https://qiita.com/fuku_tech/items/a380ebb1fd156c14c25b

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

daw

dwa

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails]ネストしたコメントの削除機能の作成

コメントの削除機能の作成

Railsで投稿サイトのコメント削除機能を作成した際に、ネストのルーティング関連で少し詰まってしまったのでログを残しておきます。

作りたいもの

本の内容を投稿(book)して、その投稿1つ1つに対してコメント(book_comment)できる。
そのコメントを消すことができるようにする。

routes.rb
Rails.application.routes.draw do
  devise_for :users
  resources :users,only: [:show,:index,:edit,:update]
  resources :books do
    resources :book_comments, only: [:create, :destroy]
  end

  root 'home#top'
  get 'home/about'
end

destroyアクションにlink_toする書き方について

ここで、viewファイルのlink_toの書き方が分からず、詰まってしまいました。

commentbookに紐づいているので、commentを削除(destroy)するときは以下のように引数を2つ指定してあげるといいそうです。

book_book_comment_path(@book, book_comment)の部分。

book_comment となっているのは、@book.book_commentseachで1つ1つ回しているためです。

show.html
<% @book.book_comments.each do |book_comment| %>
    <%= link_to  "#{book_comment.user.name}", user_path(book_comment.user) %>
        <span><%= book_comment.created_at.strftime('%Y/%m/%d') %></span>
    <%= link_to "Destroy", book_book_comment_path(@book, book_comment), method: :delete, data:{confirm: "削除しますか?"}, class: "btn-sm btn-danger"%>
    <div class="comment-entry"><%= book_comment.comment %></div>
<% end %>

コントローラーファイルについて

コントローラーファイルは以下の通りです。

URLにはidが2つある状態(/books/:book_id/book_comments/:id(.:format))なので、book_idとコメントのidが2つ分かっている状態にすると、きちんと動きました。

book_comments_controller.rb
class BookCommentsController < ApplicationController
    before_action :authenticate_user!
    def create
        @book = Book.find(params[:book_id])
        @book_new = Book.new
        @book_comment = @book.book_comments.new(book_comment_params)
        @book_comment.user_id = current_user.id
        @book_comment.save
        redirect_to book_path(@book)
    end
    def destroy
        @book = Book.find(params[:book_id])
        book_comment = @book.book_comments.find(params[:id])
        if book_comment.user != current_user
            redirect_to request.referer
        end
        book_comment.destroy
        redirect_to request.referer
    end

    private
    def book_comment_params
        params.require(:book_comment).permit(:comment)
    end
end

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【DBエラー解決】Error: Duplicate column name ''"

はじめに

$ rails db:migrate した際にDBには反映されているがエラーが起こっている解決策について調べたのでまとめました。

今回の状況

$ rails db:migrate
⬇︎
エラー発生
⬇︎
エラーなのにDBにはカラムが反映されている

エラー内容

andardError: An error has occurred, all later migrations canceled:

Mysql2::Error: Duplicate column name '' ・・・・・
・
・
・
・

これはカラムが重複していますと言うエラーだそうです。

解決策

command + f でmigrationファイルの中身を空にする。

class AddCategoryToItems < ActiveRecord::Migration[5.2]
  def change

   ここを空にする

  end
end

空の状態で $ rails db:migrate すると通る。

command + f で切り取ったコードをペーストしてもとに戻しておく。

以降 $ rails db:migrate してもエラーになりません!

まとめ

Mysql2::Error: Duplicate column name ''

このエラーはカラムが重複していますよと言うエラー内容でした

migrationファイルを空にしてから $ rails db:migrate

その後migrationファイルの中身はもとに戻しておく。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【開発ログ⑩】Railsで法定付与日と勤続年数を計算したよ

前提について

はじめまして、
プログラミングスクールに通ういりふねと申します。この記事は、スクールの課題である個人アプリの開発の記録を書くことで、自身のアウトプットに利用しています。もし、読んでいただけた方がいましたら、フィードバックをしていただけたら嬉しいです。

開発するのは「有給休暇管理ツール」です。仕様は過去記事をどうぞ。

アプリはデプロイまで行いますが、サービスとして提供するものではありません。あくまでも自学習の一環ですので、ご理解下さい。では本題へどうぞ。

今回の実施内容

前回までで、社員登録の実装が完了しました。社員登録時に「入社日(カラム名は、hire_date)」を入力してもらいます。そこで、この入社日を使用して、法定付与日と勤続年数を計算させます。具体的には以下の手順で進めます。今回は書くこと少なめ。

  • 法定付与日の計算方法を確認
  • 法定付与日の実装
  • 勤続年数の計算方法を確認
  • 勤続年数の実装

法定付与日の「とは?」と「計算方法」

大見出しを使用するほどではないのですが、法定付与日について確認しておきます。法定付与日は、毎年有給休暇が付与される日のことです。これは、入社日から6ヵ月後となります。詳細は厚生労働省のページ等で確認できますが、あまりここでは関係ないので省きます。

2019年04月01日に入社した人は、毎年10月01日が法定付与日。
2019年01月30日に入社した人は、毎年07月30日が法定付与日。
つまり、入社日に6ヵ月を足し算すれば法定付与日は求められるわけです。

もちろん、会社が作成する就業規則によって法定付与日は、変更されている場合があります。一律、4月1日と定めているところもあるようです。今回は、基本的にこの考え方で進めます。

法定付与日の実装

日付計算に際しては、こちらの記事を参考にさせていただきました。今回は、ルーティングとコントローラーの編集は行わないので、モデルとビューを掲載します。
モデルには、入社日(hire_date)に「 >> 6 」を記述して、月数に6を足す計算メソッドcalculate_grant_dateを定義しました。

models/employee.rb
class Employee < ApplicationRecord
〜中略〜
  def calculate_grant_date
    grant_day = hire_date >> 6
    grant_day.strftime("%m/%d")
  end
end

モデルファイルに定義したcalculate_grant_dateをeach文の変数employeeに対して実行しています。これでOKでした。

branches/_main.html.haml
.main
  =render 'branches/mainheader'
〜中略〜
        - @employees.each do |employee|
          %tr 
〜中略〜
            %th{id: "short"}
              = employee.calculate_grant_date
〜以下省略〜

勤続年数の計算方法

今回、勤続年数は「何年何ヵ月」という表示をさせたいと考えています。大まかな考え方としては、現在の日付(rubyでは、Date.todayで表示)から、入社日(hire_date)を引き算すれば求められます。

仮に今日の日付が「2020年05月」で、入社日が「2019年3月」とするならば、「1年2ヵ月」と表示させることができればよいということになります。年と月をバラバラにとりだして、それぞれ引き算すれば良さそうです。具体的には以下のとおりです。

●勤務年数の計算●
 今日の年「2020」 - 入社の年「2019」 = 1年
 今日の月「05」 - 入社の月「03」 = 2ヵ月
 計算結果を合体させて「1年2ヵ月」となります。これならイケそう!!

計算がヘンテコになるパターンもありました。今日の日付が「2020年05月」で、入社日が「2015年08月」であれば、「4年9ヵ月」にならなければいけませんが、先程の計算を適用するとこうなります。

●ヘンテコになる勤務年数の計算●
 今日の年「2020」 - 入社の年「2015」 = 5年
 今日の月「05」 - 入社の月「08」 = -3ヵ月
 計算結果を合体させて「5年-3ヵ月」となります。っておい!!!!

つまり、月の計算結果がマイナスになるときは、下のように年の計算結果に「-1」を月の計算結果に「+12」を足せば正しい勤続年数が算出されるというわけです。

●勤務年数の計算●
 今日の年「2020」 - 入社の年「2015」 -1 = 4年
 今日の月「05」 - 入社の月「08」 +12 = 9ヵ月
 計算結果を合体させて「4年9ヵ月」となります。できました!!

ちなみに月の計算結果が、0のときはどうでしょうか?今日の日付が「2020年05月」で、入社日が「2015年05月」とであれば、「5年0ヵ月」になります。

●勤務年数の計算●
 今日の年「2020」 - 入社の年「2015」 = 5年
 今日の月「05」 - 入社の月「05」 = 0ヵ月
 計算結果を合体させて「5年0ヵ月」となります。こっちは問題なさそう。

勤続年数の実装

前置きが長くなりましたが、実装します。今回もモデルとビューのみの編集になります。
同じくモデルに勤続年数を計算させる「calculate_year_of_service」メソッドを定義します。
先程の計算結果から、月の計算結果が「0以上」か「0未満」かで条件分岐をさせています。「0以上」ならそのまま年と月の引き算を行い、それ以外(月の計算結果がマイナスになった場合)は、計算結果の最後に年に「-1」を月に「+12」を付け加えればOK!!

model/employee.rb
class Employee < ApplicationRecord
〜中略〜
  def calculate_year_of_service
    if Date.today.month - hire_date.month > 0
      year = Date.today.year - hire_date.year
      month = Date.today.month - hire_date.month
    else
      year = Date.today.year - hire_date.year - 1
      month = Date.today.month - hire_date.month + 12
    end
    "#{year}#{month}ヵ月"
  end
end
branches/_main.html.haml
.main
  =render 'branches/mainheader'
〜中略〜
        - @employees.each do |employee|
〜中略〜
            %th{id: "short"}
              = employee.calculate_year_of_service
〜以下省略〜

ビューの確認

無事に実装できました。ビュー確認時点は、2020年05月です。
スクリーンショット 2020-05-13 23.55.36.png

今日の積み上げ

計算系のメソッドを作るときは、紙に手書きで計算を書いてから論理を組み立てることが大切だと感じました。この記事も計算式と説明している内容が合っているかドキドキしながら書きました〜。
あと、数字を際立たせるために文中に「カギカッコ」を使いすぎ。。。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】 5分でExcel出力を実装する方法

はじめに

社内アプリケーションを作成したときに管理部へ提出するようの資料をExcel出力するとき調べたことをまとめました。
Axlsx というライブラリを使用すると、Rails で簡単にExcel出力ができるようになります。
利点なのかわかりませんが、複数のシートを作成することができて便利だなーと感じました。

コマンド

// Rails アプリケーション作成
$ rails _5.2.4.2_ new five_min_axlsx_rails
$ cd five_min_axlsx_rails

// scaffold で Excel モデルを作成
$ rails g scaffold excel title:string body:string
$ rails db:migrate RAILS_ENV=development

Gemfile

Gemfile
gem 'axlsx_rails'
$ bundle install

サンプルデータ作成

サンプルデータを作成するために、数も少ないのでコンソールrails cを開いてコマンドを実行します。

$ rails c
> Excel.create(title: "sample_1", body: "body_sample_1")
> Excel.create(title: "sample_2", body: "body_sample_2")
> Excel.create(title: "sample_3", body: "body_sample_3")

Controller

index(一覧)のデータをExcelに出力したいケースを想定します。
controllerに追記します。

app/controllers/excels_controller.rb
  def index
    # Excelに出力したいデータをインスタンス変数に格納する
    @excels = Excel.all

    # 以下、追記
    respond_to do |format|
      format.html
      format.xlsx do
        # ファイル名をここで指定する(動的にファイル名を変更できる)
        response.headers['Content-Disposition'] = "attachment; filename=#{Date.today}.xlsx"
      end
    end
  end

axlsx

viewのexcelフォルダにindex.xlsx.axlsxを作成します。
rubyの拡張子ではないため注意!!

  • ポイント
    • 複数のシート(excel用語)に分けてデータを出力するためにadd_worksheetメソッドを繰り返ししている
      • add_worksheet とは、新しいシート(excel用語)を作成します。
    • add_worksheetメソッドの引数(name)に出力するファイル名を指定することができる
    • add_rowメソッドで1行目に追加する内容を指定することができる
app/views/excels/index.xlsx.axlsx
wb = xlsx_package.workbook

@excels.each_with_index do |excel, index|
  wb.add_worksheet(name: excel.title) do |sheet|
    sheet.add_row [ "No.", "title", "body" ]
    sheet.add_row [ index, excel.title, excel.body ]
  end
end

view

以下の1行を追記します。

app/views/excels/index.html.erb
<h1>Excels</h1>

<!-- 以下の1行を追記 -->
<%= link_to "Excel出力", excels_path(@excels, format: :xlsx) %>

<table>
  <thead>
    <tr>
      <th>Title</th>
      <th>Body</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @excels.each do |excel| %>
      <tr>
        <td><%= excel.title %></td>
        <td><%= excel.body %></td>
        <td><%= link_to 'Show', excel %></td>
        <td><%= link_to 'Edit', edit_excel_path(excel) %></td>
        <td><%= link_to 'Destroy', excel, method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>

<br>

<%= link_to 'New Excel', new_excel_path %>

確認

ここまでできたら、実際にExcel出力ができるかどうかを確認します。

$ rails s

リンクhttp://localhost:3000/excelsへアクセス。

Excel出力のリンク先をクリックするとダウンロードが開始されます。

ダウンロードしたExcelファイルを開くと以下のように出力されていることが確認できます。

まとめ

いかがでしたでしょうか。
最近、自分が書いた記事を参考にしてくださっているのを見て、時間をかけて書いた甲斐があるなぁとしみじみ感じております。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む