20200123のRailsに関する記事は20件です。

Rspecと Factoryの使い方

Rspecとは?

Rspecとは自動テストをしてくれる言語。になります。
UIテストをいちいちせずとも、
「ページの遷移」、「モデルバリデーション」、「if分の条件分岐による処理の確認」...
などなど、人間がやるには面倒なことを自動でやってくれるのがRspecになります!

準備

今回は比較的簡単な、
「コントローラーによる画面遷移確認テスト」
のコードを書いていこうと思います

また、ログイン後の処理定義をするコードを書きますが、そのログイン処理をDeviseを使ってやるものとします

導入するGemインストール

今回使うのはRspecFactory_botというGemを使います
内容については後ほど解説しますので、まずはインストールしましょう!!

Gemfile
group :development, :test do
  gem "rspec-rails"
  gem "factory_bot_rails"
end

rspecを設定・書くためのファイルを作成

$ bundle exec rails generate rspec:install

deviceログインの簡略化

controller_macros

spec/support/controller_macros.rbににログイン処理を簡略化させるためのコードを記載します
ファイルがない場合のファイルを作成してください

spec/support/controller_macros.rb
module ControllerMacros
  def login_admin(admin)
    @request.env["devise.mapping"] = Devise.mappings[:admin]
    sign_in admin
  end

  def login_user(user)
    controller.stub(:authenticate_user!).and_return true
    @request.env["devise.mapping"] = Devise.mappings[:user]
    sign_in user
  end
end

rails_helperで読み込み設定

先ほどのコードを読み込ませるために、spec/rails_helper.rbに以下文を追記します

spec/rails_helper.rb
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }

実装

ここからファイルに必要なコード書いていきます

テストするためのモデルを作成

spec/fatoriesこのディレクトリに作成していモデルデータを記載していきます。
今回はユーザによる画面遷移テストを作りたいので、まずユーザーファイルを作成。

spec/fatories/users.rb

その後中身を書いていきます

**spec/fatories/users.rb**
FactoryBot.define do
  factory :user do
    name { "test" }
    email { "user@email.com" }
    password { "password" }
    password_confirmation { "password" }
  end
end

今回はユーザー必要最低限な情報だけにしておきます
ここはかくじ必要なカラム等追加していただければと思います!

テストコードの記載

ここから肝心なテスト内容を記載するところです

テストコードを書く場所は
spec/controllers/users_controller_spec.rb
ここになります!
コントローラー内での画面遷移テストしかできないので、他のモデルが絡むときは
随時それ対応するコントローラーでかきましょう!

spec/cotrollers/users_controller_spec.rb
require 'rails_helper'

describe UsersController, type: :controller do
  before do
    @user = FactoryBot.create(:user)
  end
  context 'ログイン後' do
    before do 
      login(@user)
    end

    it 'トップ画面にいく' do 
      get :index
      expect(response).to have_http_status "200"
    end
  end

  context '未ログインの場合' do
    it 'トップに行くと302(エラー)が返される' do 
      get :index
      expect(response).to have_http_status "302"
    end
    it 'トップに行くとuserログインページにリダイレクトされる' do 
      get :index
      expect(response).to redirect_to new_user_session_path
    end
  end
end

・describe
 →コントローラーしていし、typeでもコントローラーと指定する
 →モデルの場合typeはmodelになる

・before do
 →テスト実行前の準備する場所
 →今回で言うとここでユーザーの情報を作成して、@userに代入してuserのデータを扱えるようにしている

・contxt
 →実行する前提条件の定義を日本語わかりやすく、みやすくする
 →実行範囲内を定義する意味もある

・it
 →実際にテストする内容

・get: :index
 →ルーティングで定義てしてるgetメソッドindexアクションに遷移すると定義

・expect(response).to have_http_status "200"
 →要するに成功したら200って返ってくきてるよね??という確認
 →ここがtrueかfalseかでテスト結果を判定している

実行

$ rspec

以上で実行完了

まとめ

以上でコントローラーテストの簡易的な説明になります
まずは簡単なところからはじめて徐々にできる範囲を伸ばしていきましょう!!

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

Rails 真偽値を返すメソッド 一覧

Railsで実行できる真偽判定メソッドをまとめました。

any?

文字列や配列が空でない場合にtrueを返す。

>> a = ["a"]
=> ["a"]
>> a.any?
=> true

empty?

文字列や配列が空の場合にtrueを返す。
nilに対してempty?メソッドを呼び出すと、NoMethodErrorが発生する。

>> a = ""
=> ""
>> a.empty?
=> true
>> b = nil
=> nil
>> b.empty?
NoMethodError (undefined method `empty?' for nil:NilClass)

nil?

nilの場合のみtrueを返す。それ以外はfalseを返す。

>> a = nil
=> nil
>> a.nil?
=> true

blank?

empty?とnil?が合わさったメソッド。

>> a = ""
=> ""
>> b = nil
=> nil
>> a.blank?
=> true
>> b.blank?
=> true

present?

!blank?を実行するメソッド。

>> a = 1
=> 1
>> a.present?
=> true

参考
https://techracho.bpsinc.jp/baba/2011_11_26/4724

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

Active Storageにも、1対1対応のモデルを作ったほうがいいかも

Rails 5.2から、Active Storageという、ファイルアップロードの機能が標準装備されましたが、そのまま使おうとしたところ、微妙に気になる点がありました。

Active Storageの便利な点

今までPaperclipなどのサードパーティーのGemを使っていたのですが、以下のような点を自前で実装する必要がありました。

  • S3でprivate保管していると、画像表示用に生成したpresigned URLが期限切れするという問題があるので、いったんRailsを経由させてS3のURLにリダイレクトさせる必要がある
  • オンデマンドに適切なサイズのサムネイルを生成するのが難しい

Active Storageであれば、こういった引っかかりやすい箇所も標準で備えていて、しかも「複数オリジンの並行運用」なんてことまでこなせてしまいます。

使い方について

Active Storageは専用のデータベーステーブルを持って、ポリモーフィック関連で別なモデルと結びつきますので、画像を入れたいモデル側でのマイグレーションは不要です。has_one_attached :itemhas_many_attached :itemsのように宣言するだけで画像を使えるようになります。

そのまま使おうとして気になったこと

もちろんそのままでも基本的なファイル保存などは問題ないのですが、使いづらい点がいくつかありました。

  • モデルがActiveStorage::Attachmentで固定なので、
    • 自分でメソッドを生やそうにもオープンクラスになってしまい、抵抗がある
    • 画像ファイルに付随するタイトルなどのデータを保存できない
  • has_many_attachedの場合、accepts_nested_attributes_forでファイルの追加はできるけど削除が利かない

画像用モデルを立てる

ということで、画像と1対1で結びつくモデルを立てて、そこにタイトルを入れたり、画像処理用のメソッドを追加したりすることでより便利に使えて、そしてaccepts_nested_attributes_forからの_destroyもできるようになるので、この方法が良さそうだと感じました。

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

【Rails】外部キー追加におけるreference型の使用、未使用の違い。外部キー制約によるエラーをコンソールで確認してみた。

概要

アソシエーション設定時にテーブルへ外部キーを追加していく中で、
reference型を使用して外部キーを追加した場合と
integer型で外部キーを追加した場合について違いを確認していきます。

外部キー追加時のreference型について

例えば、UserモデルとBlogモデルで
Blogモデルにuser_idを外部キーとして設定する場合、
モデル作成と同時に設定するなら、以下のように記述して外部キーを追加することができます。

reference型を使用する場合

$ rails g model Blog title:string content:text user:references 



マイグレーションファイルはこのようになります。

class CreateBlogs < ActiveRecord::Migration[5.2]
  def change
    create_table :blogs do |t|
      t.string :title
      t.text :content
      t.references :user, foreign_key: true

      t.timestamps
    end
  end
end



マイグレーションすると、

schema.rb
  enable_extension "plpgsql"

  create_table "blogs", force: :cascade do |t|
    t.string "title"
    t.text "content"
    t.bigint "user_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["user_id"], name: "index_blogs_on_user_id"
  end

  create_table "users", force: :cascade do |t|
    t.string "name"
    t.integer "age"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

  add_foreign_key "blogs", "users"
end


reference型を使わず、カラム名をuser_idにしてinteger型で記述する場合

$ rails g model Blog title:string content:text user_id:integer 
class CreateBlogs < ActiveRecord::Migration[5.2]
  def change
    create_table :blogs do |t|
      t.string :title
      t.text :content
      t.integer :user_id

      t.timestamps
    end
  end
end


これをマイグレーションすると、

schema.rb
  enable_extension "plpgsql"

  create_table "blogs", force: :cascade do |t|
    t.string "title"
    t.text "content"
    t.integer "user_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

  create_table "users", force: :cascade do |t|
    t.string "name"
    t.integer "age"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

end


どちらも外部キーとしてuser_idが追加されました。
しかし、reference型にはuser_id以外にも追加されているコードがあります。
これがこの2つの方法の違いで、reference型を使うと外部キー制約、インデックスが自動で追加されます。
reference型を使わない場合はどちらも付与されません。

外部キー制約とは

外部キー制約が付与された場合、以下2点の成約が付きます。

1,存在しない値の外部キーは登録できない(参照整合性)
2,親テーブル(user)の外部キーが子テーブル(blog)に登録されていると親テーブルは削除できない。

1を簡単に言い直すと
主キーとして存在しないuser_idをblogsテーブルには登録できないようになるということです。

コンソール上で試してみる

実際にコンソール上で試してみましょう。

user_id(1)のUserを作成します。
その後、Blogを作成しますが、外部キーにuser_id(1)とuser_id(3)(存在していないuser_id)のどちらもBlogの作成に成功しています。
外部キー制約が付与されていれば、存在していないuser_idであるuser_id(3)が含まれたBlogはエラーが発生して作成することはできません。


irb(main):001:0> User.create(name: "test1", age: 10)
   (0.1ms)  BEGIN
  User Create (1.2ms)  INSERT INTO "users" ("name", "age", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"  [["name", "test1"], ["age", 10], ["created_at", "2020-01-23 03:47:53.740490"], ["updated_at", "2020-01-23 03:47:53.740490"]]
   (23.2ms)  COMMIT
=> #<User id: 1, name: "test1", age: 10, created_at: "2020-01-23 03:47:53", updated_at: "2020-01-23 03:47:53">

irb(main):003:0> Blog.create(title: "test1", content: "test1", user_id: 1)
   (0.1ms)  BEGIN
  Blog Create (1.2ms)  INSERT INTO "blogs" ("title", "content", "user_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id"  [["title", "test1"], ["content", "test1"], ["user_id", 1], ["created_at", "2020-01-23 03:48:48.383505"], ["updated_at", "2020-01-23 03:48:48.383505"]]
   (4.9ms)  COMMIT
=> #<Blog id: 1, title: "test1", content: "test1", user_id: 1, created_at: "2020-01-23 03:48:48", updated_at: "2020-01-23 03:48:48">
irb(main):004:0> Blog.create(title: "test1", content: "test1", user_id: 3)
   (0.2ms)  BEGIN
  Blog Create (0.5ms)  INSERT INTO "blogs" ("title", "content", "user_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id"  [["title", "test1"], ["content", "test1"], ["user_id", 3], ["created_at", "2020-01-23 03:48:56.834930"], ["updated_at", "2020-01-23 03:48:56.834930"]]
   (1.7ms)  COMMIT
=> #<Blog id: 2, title: "test1", content: "test1", user_id: 3, created_at: "2020-01-23 03:48:56", updated_at: "2020-01-23 03:48:56">


次に、2の制約についてですが、
blog_id(1)のBlogにはuser_id(1)が外部キーとして登録されているので、user_id(1)のユーザーを削除しようとしてもエラーが発生するということです。

1,2の制約のどちらもエラーを発生させるために、外部キー制約をマイグレーション実行後に追加してみます。

後から追加する場合はadd_foreign_key :blogs, :users
( add_foreign_key :対象のテーブル, :指定先のテーブル)
をロールバックしたマイグレーションファイルに追記します。

  def change
    create_table :blogs do |t|
      t.string :title
      t.text :content
      t.integer :user_id

      t.timestamps
    end
    add_foreign_key :blogs, :users
  end
end

rails db:rollbackして追記した後に再度rails db:migrateを実行します。

そして、先程と同じように存在しないuser_id(3)を外部キーとして、
Blogを作成しようとするもエラーが発生します。


irb(main):001:0> Blog.create(title: "test2", content: "test2", user_id: 3)
   (0.1ms)  BEGIN
  Blog Create (5.7ms)  INSERT INTO "blogs" ("title", "content", "user_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id"  [["title", "test2"], ["content", "test2"], ["user_id", 3], ["created_at", "2020-01-23 04:03:15.314345"], ["updated_at", "2020-01-23 04:03:15.314345"]]
   (0.2ms)  ROLLBACK
Traceback (most recent call last):
        1: from (irb):1
ActiveRecord::InvalidForeignKey (PG::ForeignKeyViolation: ERROR:  insert or update on table "blogs" violates foreign key constraint "fk_rails_40ebb3948d")
DETAIL:  Key (user_id)=(3) is not present in table "users".
: INSERT INTO "blogs" ("title", "content", "user_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id"

続いて2の制約についてエラーを発生させるために試してみます。

user_id(2)に紐付いたBlogを作成します。


irb(main):005:0> Blog.create(title: "test1", content: "test1", user_id: 2)
   (0.2ms)  BEGIN
  Blog Create (0.7ms)  INSERT INTO "blogs" ("title", "content", "user_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id"  [["title", "test1"], ["content", "test1"], ["user_id", 2], ["created_at", "2020-01-23 04:08:42.845532"], ["updated_at", "2020-01-23 04:08:42.845532"]]
   (4.0ms)  COMMIT
=> #<Blog id: 2, title: "test1", content: "test1", user_id: 2, created_at: "2020-01-23 04:08:42", updated_at: "2020-01-23 04:08:42">


親レコードのuser_id(2)を削除してみると、エラーが発生します。


irb(main):008:0> User.find(2).destroy
  User Load (0.5ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2  [["id", 2], ["LIMIT", 1]]
   (0.2ms)  BEGIN
  User Destroy (2.5ms)  DELETE FROM "users" WHERE "users"."id" = $1  [["id", 2]]
   (0.1ms)  ROLLBACK
Traceback (most recent call last):
        1: from (irb):8
ActiveRecord::InvalidForeignKey (PG::ForeignKeyViolation: ERROR:  update or delete on table "users" violates foreign key constraint "fk_rails_40ebb3948d" on table "blogs")
DETAIL:  Key (id)=(2) is still referenced from table "blogs".
: DELETE FROM "users" WHERE "users"."id" = $1

この通り、外部キー制約がついた状態では子レコードを削除してからでないと親レコードを削除できなくなっています。

親レコードを削除する時にいちいち子レコードを全て削除するのは手間がかかりますが、
Userモデルにhas_many :blogs, dependent: :destroyを記述すれば、解決されます。
この記述でUserを削除した際に紐付いている子レコードのblogも一緒に削除してくれますね。

user.rb
class User < ApplicationRecord
  has_many :blogs, dependent: :destroy
end



irb(main):002:0> User.find(2).destroy
  User Load (0.3ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2  [["id", 2], ["LIMIT", 1]]
   (0.1ms)  BEGIN
  Blog Load (0.4ms)  SELECT "blogs".* FROM "blogs" WHERE "blogs"."user_id" = $1  [["user_id", 2]]
  Blog Destroy (0.4ms)  DELETE FROM "blogs" WHERE "blogs"."id" = $1  [["id", 2]]
  User Destroy (0.5ms)  DELETE FROM "users" WHERE "users"."id" = $1  [["id", 2]]
   (0.5ms)  COMMIT
=> #<User id: 2, name: "test2", age: 20, created_at: "2020-01-23 03:48:02", updated_at: "2020-01-23 03:48:02">


親レコードと子レコードを一緒に削除できました!

インデックスについてはエラーを試す場面がないので、以下参照。
https://qiita.com/seiya1121/items/fb074d727c6f40a55f22

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

【Ruby】mapメソッドのつかいかた(+別メソッドとの組み合わせの例など)

自己整理&備忘録です。

mapメソッドとは

mapメソッドとは、「配列の要素それぞれに対して一定の処理を行って、新しい配列をつくるメソッド」とされる。

 
基本的な型

配列.map { |変数| 実行させたい処理 }

名称は異なるがcollectメソッドもmapと同じ動きをする。

つかいかた(例)

1. 数式

sample_1.rb
x = [0, 100, 500]
y = x.map { |x| x * 2 }
p y

#=> [0, 200, 1000]

2.mapの要素にメソッドを与える(よく見る形)

基本の型

オブジェクト名.map(&:メソッド名)

sports = ["BASEBALL", "SOCCER"]
p sports.map(&:downcase) 

#=> ["baseball", "soccer"]

mapとmap!の違い

map  → 元の値に対して影響を与えない
map! → 元の値を書き換える

fruits = ["apple", "banana", "orange"]
p fruits
p fruits.map(&:upcase)
p fruits
p fruits.map!(&:capitalize)
p fruits

# 出力結果(4つ目のmap!後に元のfruitsを出力すると上書きされている)
["apple", "banana", "orange"]
["APPLE", "BANANA", "ORANGE"]  
["apple", "banana", "orange"]
["Apple", "Banana", "Orange"]
["Apple", "Banana", "Orange"] 

eachメソッドとの違い

新しい配列を作るので、空の配列array = []を作らずに書くことができる。

例.配列の頭文字の大文字にしたいときのeachmapの違い

rei.rb
fruits = ["apple", "banana", "orange"]

出力結果(理想形)

["Apple", "Banana", "Orange"]

 
eachのとき

each_ver.rb
fruits = ["apple", "banana", "orange"]
fruits_ini = []
fruits.each do |frt|
  fruits_ini << frt.capitalize
end
p fruits_ini

#=> ["Apple", "Banana", "Orange"]

 
mapのとき

map_ver.rb
fruits = ["apple", "banana", "orange"]
p fruits.map(&:capitalize)
#=> ["Apple", "Banana", "Orange"]

参考

Rubyのmap, map!メソッドの使い方
Ruby mapメソッドについて
【Rails入門】mapメソッドを完全攻略!配列操作の基礎を学ぼう

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

Railsチュートリアル 第4章

Railsコンソールの設定

Railsコンソールはirb(IRB:Interactive RuBy)を拡張して作られているため、Rubyの機能を全て使うことができる。

$ nano ~/.irbrc

下記の設定を書くと、irbのプロンプトが簡潔な表示に置き換わる。

IRB.conf[:PROMPT_MODE] = :SIMPLE
IRB.conf[:AUTO_INDENT_MODE] = false

後続ifとunless

if:条件式が"真"のときに実行される

puts "x is not empty" if !x.empty?

unless:条件式が"偽"のときに実行される

>> string = "foobar"
>> puts "The string '#{string}' is nonempty." unless string.empty?
The string 'foobar' is nonempty.
=> nil

メソッド

mapメソッド

配列の要素の数だけブロック内の処理を繰り返し、結果として作成された配列を返す。
map!は元の値を書き換える。

オブジェクト.map { |変数|
  # 実行したい処理
}
>> (1..5).map { |i| i**2 }
=> [1, 4, 9, 16, 25]
>> %w[a b c]      # %w で文字列の配列を作成
=> ["a", "b", "c"]
>> %w[a b c].map { |char| char.upcase }
=> ["A", "B", "C"]
>> %w[A B C].map { |char| char.downcase }
=> ["a", "b", "c"]

mapのブロック内で宣言した引数(char)に対してメソッドを呼び出している場合は、省略記法が一般的。

>> %w[A B C].map { |char| char.downcase }
=> ["a", "b", "c"]
>> %w[A B C].map(&:downcase)
=> ["a", "b", "c"]

to_a

配列に変換する

>> (0..9).to_a
=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

%w

文字列の配列に変換

>> a = %w[foo bar baz quux]         # %wを使って文字列の配列に変換
=> ["foo", "bar", "baz", "quux"]
>> a[0..2]
=> ["foo", "bar", "baz"]

ブロック

下記では、範囲オブジェクトである(1..5)に対して、eachメソッドを呼び出している。
メソッドに渡されている{ |i| puts 2 * i }が、ブロックと呼ばれる部分。

>> (1..5).each { |i| puts 2 * i }

また、ブロックである事を示すには波カッコで囲むが、下記のようにdoとendで囲んで示すこともできる。
短い1行のブロックには波カッコを使い、長い1行や複数行のブロックにはdo..endを使う。

>> (1..5).each do |i|
?>   puts 2 * i
>> end

そう考えると、単体テストもブロックであることがわかる。
このtestメソッドは、文字列(説明文)とブロックを引数にとり、テストが実行されるときにブロック内の文が実行されている。

test "should get home" do
  get static_pages_home_url
  assert_response :success
  assert_select "title", "Ruby on Rails Tutorial Sample App"
end

ハッシュ

ハッシュは本質的には配列と同じだが、インデックスとして整数値意外のものも使える点が配列とは異なる。(そのため、他の言語ではハッシュを連想配列と呼ぶこともある)
ハッシュは、キーと値のペアを波カッコで囲んで表記する。
配列と似ているが、ハッシュでは要素の並び順が保証されないため、要素の順序が重量である場合は、配列を使う必要がある。

>> user = { "first_name" => "Michael", "last_name" => "Hartl" }
=> {"last_name"=>"Hartl", "first_name"=>"Michael"}

上記では、ハッシュのキーとして文字列を使っていたが、Railsでは文字列よりもシンボルを使うのが一般的。:nameのように表す。

>> user = { :name => "Michael Hartl", :email => "michael@example.com" }
=> {:name=>"Michael Hartl", :email=>"michael@example.com"}
>> user[:name]              # :name に対応する値にアクセスする
=> "Michael Hartl"
>> user[:password]          # 未定義のキーに対応する値にアクセスする
=> nil

また、シンボルとハッシュロケットの組み合わせを、下記のようにキーの名前の後にコロンを置く記法も同じように使える。

{ name: "Michael Hartl", email: "michael@example.com" }

CSSを追加する

ハッシュを学んだので、下記が理解できるようになっている。

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

まずは、丸カッコがないが、Rubyでは丸カッコは使用しなくてもいいので、下記の2つの行は等価となる。

stylesheet_link_tag('application', media: 'all',
                    'data-turbolinks-track': 'reload')
stylesheet_link_tag 'application', media: 'all',
                    'data-turbolinks-track': 'reload'

次にmedia引数はハッシュだが、波カッコがない点が不思議。
ハッシュはメソッド呼び出しの最後の引数である場合は、波カッコを省略できるためであり、下記の2つの行は等価となる。

stylesheet_link_tag 'application', { media: 'all',
                    'data-turbolinks-track': 'reload' }
stylesheet_link_tag 'application', media: 'all',
                    'data-turbolinks-track': 'reload'

Rubyでは改行と空白を区別していないため、上記のように途中に改行が含まれていても問題ない。

以上の事から、stylesheet_link_tagメソッドは、2つの引数で呼ばれており、最初の引数はである文字列は、スタイルシートへのパスを示す。
次の引数であるハッシュには2つの要素があり、最初の要素はメディアタイプを示し、次の要素はturbolinksという機能をオンにしている。
最後に上記を読み込んで生成されたHTMLソースは下記となる。

<link data-turbolinks-track="true" href="/assets/application.css"
media="all" rel="stylesheet" />

クラス

クラスの継承

String ⇨ Object ⇨ BasicObject ⇨ nil(スーパークラスを持たないという事)
Array、Hashなどのクラスも上記と同様である。これが"Rubyではあらゆる物がオブジェクトである"という事。

>> s = String.new("foobar")
=> "foobar"
>> s.class
=> String
>> s.class.superclass
=> Object
>> s.class.superclass.superclass
=> BasicObject
>> s.class.s

クラス継承の例

>> class Word < String             # WordクラスはStringクラスを継承する
>>   # 文字列が回文であればtrueを返す
>>   def palindrome?
>>     self == self.reverse        # selfは文字列自身を表します
>>   end
>> end
=> :palindrome?

上記の様に、WordクラスはStringクラスを継承しているため、palindrome?メソッドだけでなく、Stringクラスが扱える全てのメソッドがWordクラスでも扱える様になる。

>> s = Word.new("level")    # 新しいWordを作成し、"level" で初期化する
=> "level"
>> s.palindrome?            # Wordが回文かどうかを調べるメソッド
=> true
>> s.length                 # WordはStringで扱える全てのメソッドを継承している
=> 5

selfキーワード

selfとは、オブジェクトそのものを指している。

attr_accesssorメソッド

クラスにインスタンス変数を読み書きするためのアクセサメソッドを定義するメソッド。
アクセサメソッドは、外部インスタンスのインスタンス変数を参照したり変更するために定義する。

initialize

User.newを実行すると自動的に呼び出されるメソッド。

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

【rails】ユーザー 登録時にnameカラムに自動的に@を付ける方法

はじめに

初学者がポートフォリオ作成でハマったことをメモします。
TiwtterのアカウントIDのように特定のカラムの最初に@を付ける方法です。ホートフォリオ作成時にユーザーの名前やアカウントIDの最初に@を付けたいという方は多いと思います。
初歩的な内容すぎて調べてもそのような記事がすぐ見つからなかったので残しておきます。

結論

before_saveをmodelに設定する。

コードと説明

formから送られてきた値を保存する前に加工する。
before_saveは、paramsをDBに保存する直前に指定したプログラム(今回の例では、:change_account_name)を実施するコールバック関数です。
before_saveを設定すると、createとupdateのどちらも場合にも、適用されます。

コールバック処理について
http://www.techscore.com/blog/2012/12/25/rails%E3%81%AE%E3%82%B3%E3%83%BC%E3%83%AB%E3%83%90%E3%83%83%E3%82%AF%E3%81%BE%E3%81%A8%E3%82%81/

models/user.rb
before_save :change_account_name
  def change_account_name
    self.account_name = "@" + account_name
  end
views/users/new
#slim,bootstrap使用
= form_with model: @user, local: true do |f|
(中略)
  = f.label :account_name, 'アカウントID'
    .input-group
      .input-group-prepend
        .input-group-text @
          = f.text_field :account_name, class: 'form-control' , placeholder: "9文字まで" 

スクリーンショット 2020-01-23 18.52.24.png

終わりに

誰かのお役に立てれば幸いです。
間違えている部分があればコメントお願い致します。

参考にした記事
https://qiita.com/KeisukeYoshida0220/items/e1ce152ac8d89845aab3

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

【Ruby】ヒアドキュメントの初歩的なつかいかた

簡単に備忘録。

1. ヒアドキュメントとは

ヒアドキュメントは「文字列をシェルスクリプトやプログラミングに埋め込むためのもの」
""無しや改行&空白なども書いた通りに適用できる(長い文などに有効)。

2. 使い方

通常

puts "りんご"
puts "バナナ"
puts "メロン"
puts "いちご"

#=> 
りんご
バナナ
メロン
いちご

ヒアドキュメントの場合

puts <<~EOS
りんご
バナナ
メロン
いちご
EOS

#=> 
りんご
バナナ
メロン
いちご

参考

【3分でわかる】Rubyでヒアドキュメント
ヒアドキュメントとは・基本の使い方(ruby)

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

therubyracer のエラー

bundle install

次のエラーがでました?

エラー内容
Building native extensions.  This could take a while...
ERROR:  Error installing therubyracer:
    ERROR: Failed to build gem native extension.
    current directory: /Users/koichi/.rbenv/versions/2.3.4/lib/ruby/gems/2.3.0/gems/therubyracer-0.12.3/ext/v8
/Users/koichi/.rbenv/versions/2.3.4/bin/ruby -r ./siteconf20200120-28144-771md0.rb extconf.rb
checking for main() in -lpthread... yes
checking for main() in -lobjc... yes
checking for v8.h... no
*** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of necessary
libraries and/or headers.  Check the mkmf.log file for more details.  You may
need configuration options.
Provided configuration options:
    --with-opt-dir
    --without-opt-dir
    --with-opt-include
    --without-opt-include=${opt-dir}/include
    --with-opt-lib
    --without-opt-lib=${opt-dir}/lib
    --with-make-prog
    --without-make-prog
    --srcdir=.
    --curdir
    --ruby=/Users/koichi/.rbenv/versions/2.3.4/bin/$(RUBY_BASE_NAME)
    --with-pthreadlib
    --without-pthreadlib
    --with-objclib
    --without-objclib
    --enable-debug
    --disable-debug
    --with-v8-dir
    --without-v8-dir
    --with-v8-include
    --without-v8-include=${v8-dir}/include
    --with-v8-lib
    --without-v8-lib=${v8-dir}/lib
/Users/koichi/.rbenv/versions/2.3.4/lib/ruby/gems/2.3.0/gems/libv8-3.16.14.19/ext/libv8/location.rb:50:in `configure': By using --with-system-v8, you have chosen to use the version  (Libv8::Location::System::NotFoundError)
of V8 found on your system and *not* the one that is bundled with 
the libv8 rubygem. 
However, your system version of v8 could not be located. 
Please make sure your system version of v8 that is compatible 
with 3.16.14.19 installed. You may need to use the 
--with-v8-dir option if it is installed in a non-standard location
    from /Users/koichi/.rbenv/versions/2.3.4/lib/ruby/gems/2.3.0/gems/libv8-3.16.14.19/lib/libv8.rb:7:in `configure_makefile'
    from extconf.rb:32:in `<main>'
To see why this extension failed to compile, please check the mkmf.log which can be found here:
  /Users/koichi/.rbenv/versions/2.3.4/lib/ruby/gems/2.3.0/extensions/x86_64-darwin-19/2.3.0-static/therubyracer-0.12.3/mkmf.log
extconf failed, exit code 1
Gem files will remain installed in /Users/koichi/.rbenv/versions/2.3.4/lib/ruby/gems/2.3.0/gems/therubyracer-0.12.3 for inspection.
Results logged to /Users/koichi/.rbenv/versions/2.3.4/lib/ruby/gems/2.3.0/extensions/x86_64-darwin-19/2.3.0-static/therubyracer-0.12.3/gem_make.out

このエラーを読んで、

  • therubyracer でエラー
  • libv8 も絡んでる
  • option をつけろ

ということかと?

3つの手順で解決

1 - V8 の 3.15 を入手

brew install v8@3.15

2 - ローカルの設定ファイルへ書き込み

bundle config --local build.libv8 --with-system-v8
bundle config --local build.therubyracer --with-v8-dir=/usr/local/opt/v8@3.15

3 - インストール

bundle install

これでうまくいきました??

解決方法にたどり着くまでに調べたこと

以下、まとまってないです. メモとして残してます

therubyracer

Call JavaScript code and manipulate JavaScript objects from Ruby. Call Ruby code and manipulate Ruby objects from JavaScript.

  • 2017年のver0.12.3で止まっている
  • 依存関係: libv8 ~> 3.16.14.0 つまり 3.16.14.0 から 3.16.14.19 まで
  • mini racer??

libv8

Distributes the V8 JavaScript engine in binary and source forms in order to support fast builds of The Ruby Racer

バージョン履歴

  • 7.3.492.27.1 - July 22, 2019 universal-darwin19 (6.99MB)
  • 7.3.492.27.1 - July 22, 2019 x86_64-darwin-19 (6.99MB)
  • 3.16.14.19 - January 11, 2018 universal-darwin-17 (2.36MB)

mini racer

Minimal embedded v8 engine for Ruby

github/readme.md参照

依存性

libv8 >= 6.9.411

余談
"~>1.0.1"という記法について

Darwinって?

Wikipedia

Darwin(ダーウィン)はアップルが開発するUnix系のPOSIX準拠オペレーティングシステム (OS) である。技術的にはNEXTSTEPからOPENSTEPに続く流れを汲み、Mach 3.0+BSDをベースとし、一部の機能は他のBSD系OSからも取り入れている。DarwinはmacOSやiOS、さらにはwatchOSとtvOSの基礎となる部分でもある。

自分のMac環境(2020-01-21現在)

  • Darwin Kernel Version 19.2.0

V8 javascript engine

V8とは

V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++. It is used in Chrome and in Node.js, among others. It implements ECMAScript and WebAssembly, and runs on Windows 7 or later, macOS 10.12+, and Linux systems that use x64, IA-32, ARM, or MIPS processors. V8 can run standalone, or can be embedded into any C++ application.

brew install v8 の参考ページ

疑問

v8 engine に ”3.15系統” と ”最新の7系統” がある?

自分のGoogle Chrome のバージョンをしらべるには?

chromeバージョン確認ページ
自分のChromeのバージョンを確認したところ↓
JavaScript: V8 7.9.317.32

もう一度、brew install v8 の参考ページをよく見ると
Screenshot from Gyazo
バージョン3.15open sourceとなってる
対して、7.9にはついていない
3系統だけopen source?

libv8のreadme
ここに次のような記載があります

Versioning
Versions of the libv8 gem track the version of v8 itself, adding its own point release after the main v8 version. So libv8 3.11.8.5 and 3.11.8.14 both correspond to v8 version 3.11.8. Another way to think about it would be that 3.11.8.14 is the 14th release of the libv8 rubygem based on v8 version 3.11.8

つまり、V8のバージョン番号がほぼそのままlibv8のバージョン番号になっているということ

% brew info v8
v8: stable 7.9.317.31 (bottled)
Google's JavaScript engine
https://github.com/v8/v8/wiki
/usr/local/Cellar/v8/7.9.317.31 (38 files, 38.9MB) *
  Poured from bottle on 2020-01-22 at 11:43:33
From: https://github.com/Homebrew/homebrew-core/blob/master/Formula/v8.rb
==> Dependencies
Build: ninja ✘
==> Requirements
Build: xcode ✔
==> Analytics
install: 2,444 (30 days), 10,549 (90 days), 38,848 (365 days)
install-on-request: 2,138 (30 days), 8,718 (90 days), 28,839 (365 days)
build-error: 0 (30 days)

その他参考にしたサイト

https://github.com/rubyjs/therubyracer/issues/455

https://bundler.io/v1.16/bundle_config.html

https://docs.brew.sh/Bottles

https://formulae.brew.sh/api/formula/v8@3.15.json

https://github.com/rubyjs/libv8/issues/282

http://blog.cyclogy.com/2014/02/08/homebrew-versions/

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

Railsチュートリアル 第3章

この章では、テスト駆動開発について学習。

テスト

Railsのテストには、コントローラーテスト、モデルテスト、統合テストの3種類ある。
ここでは、コントローラーテストについてメモ。

テストの例を2つほど

test "should get home" do
 get static_pages_home_url  ⇨GETリクエストをhomeアクションに対して発行
 assert_response :success   ⇨リクエストに対するレスポンスは"成功"になるはず
end
test "should get home" do
 get static_pages_home_url
 assert_response :success
 assert_select "title", "Home | Ruby on Rails Tutorial Sample App" ⇨titleタグ内に「Home | Ruby on Rails Tutorial Sample App」と言う文字列があるはず
end

setupメソッド

書くテストが実行される直前で実行されるメソッド。

provideメソッド

provideメソッドでパラメータを引き渡す

<% provide(:title, "Home") %>

yieldメソッドで受け取る

<title><%= yield(:title) %></title>

テストの便利な設定

テスト用の設定として、minitest reportersとGuardについてメモ。

minitest reporters

テストの結果をプログレスバーでパーセント表示したり、REDやGREENで表示してくれる設定。
minitest-reporters gemを利用。

Gemfile.rb
source 'https://rubygems.org'
.
.
group :test do
  gem 'rails-controller-testing', '1.0.2'
  gem 'minitest',                 '5.10.3'
  gem 'minitest-reporters',       '1.1.14'  ⇦これを追加
  gem 'guard',                    '2.13.0'
  gem 'guard-minitest',           '2.4.4'
end
.
.
test/test_helper.rb
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
require "minitest/reporters"  ⇦これを追加
Minitest::Reporters.use!    ⇦これを追加

class ActiveSupport::TestCase
  # Setup all fixtures in test/fixtures/*.yml for all tests
  # in alphabetical order.
  fixtures :all

  # Add more helper methods to be used by all tests here...
end

Guardによるテストの自動化

rails testコマンドを手動で打ち込まなくても、static_pages_test.rbファイルなどを変更すると自動的にテストを実行してくれるツール。

Gemfile.rb
source 'https://rubygems.org'
.
.
group :test do
  gem 'rails-controller-testing', '1.0.2'
  gem 'minitest',                 '5.10.3'
  gem 'minitest-reporters',       '1.1.14'
  gem 'guard',                    '2.13.0'  ⇦これを追加
  gem 'guard-minitest',           '2.4.4'   ⇦これを追加
end
.
.

初期化

$ bundle exec guard init

Cloud9を使っている場合は、tmuxをインストールする必要がある。

$ sudo yum install -y tmux

Guardfile編集

Guardfile.rb
# Guardのマッチング規則を定義
guard :minitest, spring: "bin/rails test", all_on_start: false do
  watch(%r{^test/(.*)/?(.*)_test\.rb$})
  watch('test/test_helper.rb') { 'test' }
  watch('config/routes.rb')    { integration_tests }
  watch(%r{^app/models/(.*?)\.rb$}) do |matches|
    "test/models/#{matches[1]}_test.rb"
  end
  watch(%r{^app/controllers/(.*?)_controller\.rb$}) do |matches|
    resource_tests(matches[1])
  end
  watch(%r{^app/views/([^/]*?)/.*\.html\.erb$}) do |matches|
    ["test/controllers/#{matches[1]}_controller_test.rb"] +
    integration_tests(matches[1])
  end
  watch(%r{^app/helpers/(.*?)_helper\.rb$}) do |matches|
    integration_tests(matches[1])
  end
  watch('app/views/layouts/application.html.erb') do
    'test/integration/site_layout_test.rb'
  end
  watch('app/helpers/sessions_helper.rb') do
    integration_tests << 'test/helpers/sessions_helper_test.rb'
  end
  watch('app/controllers/sessions_controller.rb') do
    ['test/controllers/sessions_controller_test.rb',
     'test/integration/users_login_test.rb']
  end
  watch('app/controllers/account_activations_controller.rb') do
    'test/integration/users_signup_test.rb'
  end
  watch(%r{app/views/users/*}) do
    resource_tests('users') +
    ['test/integration/microposts_interface_test.rb']
  end
end

# 与えられたリソースに対応する統合テストを返す
def integration_tests(resource = :all)
  if resource == :all
    Dir["test/integration/*"]  else
    Dir["test/integration/#{resource}_*.rb"]
  end
end

# 与えられたリソースに対応するコントローラのテストを返す
def controller_test(resource)
  "test/controllers/#{resource}_controller_test.rb"
end

# 与えられたリソースに対応するすべてのテストを返す
def resource_tests(resource)
  integration_tests(resource) << controller_test(resource)
end

Guard使用時のSpringとGitの競合を防ぐには、.gitignoreファイルにspring/ディレクトリを追加する。
そうすることで、指定したファイルはGitリポジトリに追加されなくなる。

.gitignore
# See https://help.github.com/articles/ignoring-files for more about
# ignoring files.
#
# If you find yourself ignoring temporary files generated by your
# text editor or operating system, you probably want to add
# a global ignore instead:
#   git config --global core.excludesfile '~/.gitignore_global'

# Ignore bundler config.
/.bundle

# Ignore the default SQLite database.
/db/*.sqlite3
/db/*.sqlite3-journal

# Ignore all logfiles and tempfiles.
/log/*
/tmp/*
!/log/.keep
!/tmp/.keep

# Ignore Byebug command history file.
.byebug_history

# Ignore Spring files.
/spring/*.pid        ⇦これを追加

設定が完了したので実行

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

【Railsでスクレイピング】Mechanize記事まとめ

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

【Rails】 Heroku, Aws で詰まったときに見る記事まとめ

Heroku

db

heroku上のDBをリセットする

heroku pg:reset DATABASE_URL
heroku run rake db:migrate
or
heroku run rake db:seed

【Rails】Heroku上でApplication Error(H10 App crashed)【PostgreSQL】

group :production do
  gem 'pg', '0.20.0'
end
メモ

heroku run rails cでheroku上のdbの中身を確認できる。
heroku run rails db:migrate:statusでdbの状態を見れる。
heroku logs -tで詳しいエラー内容を見れる。

assets

Rails4 asset pipeline関連設定まとめ(Heroku対応込)

RAILS_ENV=production bundle exec rake assets:precompile assets:clean

git add .

git commit -m "hoge"

git push origin hoge

git push heroku

【rails】【heroku】【bootstrap】herokuでCSS、font、JavaScriptが反映されない。

credentials.yml.enc, master.key

【Rails5.2】credentials.yml.encとmaster.keyでのデプロイによる今までとの変更点

EDITOR=vim bin/rails credentials:edit

railsのエラー「Errno::EACCES: Permission denied」の解決

production環境でsecret_tokenをセットする(rails)

AWS

【Railsチュートリアル】S3に画像をアップロードする設定【13章課題】

Rails+carrierwave+heroku+AWS S3で画像アップロードさせるときにハマったこと

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

あっさり読むrails④(非同期通信)

はじめに

JSを使った非同期通信を簡単に書いてみます。

前提

使用するのは、
Ruby on rails
Haml
jQuery
です。
CSSは特に指定しません。

実行

次のファイルを用意します。

sample.haml.html
= form_with model:@sample, local: true do |f|
  = f.text_area :name, placeholder: "サンプル", class: "sample-form"

.add-text

これがビューデータとなります。

sample.js
$(function(){
 $(".sample-form").on("change" functon(){
   var sampletext = $(this).val;
   $(".add-text").text(sampletext):
 })
});

これが非同期処理の中身になります。

これで、テキストエリアに文章を入力すれば、同じ文章が.add-textの部分に表示されます。

※随時更新します

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

あっさり読むrails④(非同期処理)

はじめに

JSを使った非同期処理を簡単に書いてみます。
関連事項を次の記事に書いています。

前提

使用するのは、
Ruby on rails
Haml
jQuery
です。
CSSは特に指定しません。

実行

次のファイルを用意します。

Gemfile
gem 'jquery-rails'

これを記述して、bundle installします。
(ディレクトリが、アプリのディレクトリになっている事を確認。pwdというコマンドで確認可能)

application.js
//= require jquery

この記述を忘れるとエラーになります($とは何ですか?という感じのエラー)

sample.haml.html
= form_with model:@sample, local: true do |f|
  = f.text_area :name, placeholder: "サンプル", class: "sample-form"

.add-text

これがビューデータとなります。

sample.js
$(function(){
 $(".sample-form").on("change" functon(){
   var sampletext = $(this).val;
   $(".add-text").text(sampletext):
 })
});

これが非同期処理の中身になります。

これで、テキストエリアに文章を入力すれば、同じ文章が.add-textの部分に表示されます。

※随時更新します

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

複数のレコードを一括処理

一括でレコードをアレコレするやり方。なお割と力技。
それぞれやりたい事は以下。

①カートを空にする =Userに紐づくCartレコードを全削除
②カートに入ってる商品を注文する =複数のCartレコードを使って複数のOrderProductレコードを作成する。
③注文情報と注文商品情報をまとめて変更する =親要素と子要素を一括でupdateをかける

前提ーー
Cartモデル、OrderProductモデルには以下のカラムがある
・product_name
・quantity
・price
・order_id(OrderProductのみ)
・status(OrderProductのみ)
Orderモデル
・user_id
・status

User<Cart,Order
Order<OrderProduct が1対他の関係。

①カートを空にする

delete_allメソッドを使う。これを使うとUserに紐づいたCartレコードを全て削除できる。
似たようなものでDestroy_allがあるけどこちらはUserに紐づいた全てのレコードが削除されるらしい。つまりこの前提だとOrderレコードまで消えるっぽい。
actionはdestroyを個別削除に使っていたので新しく作成する。

config/routes.rb
delete 'public/carts' => "public/carts#delete_all"
carts_controller.rb
def delete_all
 @user = current_user
 @user.carts.delete_all
end
Carts/index.html.erb
<%= link_to "カートを空にする", public_carts_path, method: :delete %>

②カートに入ってる商品を注文する

ここでやりたい挙動は
1、Orderレコードを新しく作成
2、そのIDを使ってOrderProductを作成

controller
def create
 @user = current_user

 @order = Order.new
 @order.user_id = current_user.id
 @order.save!

 @user.carts.each do |cart|
  order_product = OrderProduct.new
  order_product.order_id = @order.id
  order_product.quantity = cart.quantity
  order_product.product_name = cart.product_name
  order_product.total = cart.product.price
  order_product.save
 end
end

もっと大量のデータを扱う場合は別のやり方があると思う。
.eachで回すのはナンセンスだけどやり方調べる時間がなかった。

③注文情報と注文商品情報をまとめて変更する

これが一番難しかった。何なら今でもいまいち理解できてない。

order_controller
def update
 @order = Order.find(params[:id])
 @order.update(order_params)
end

private
def order_params
 params.require(:order).permit(:user_id,:status, order_products_attributes: [:id, :status, :_destroy])
end
models/order.rb
accepts_nested_attributes_for :order_products
<%= form_for [@order] do |f| %>
 <%= f.select :status, ['入金待ち', '入金確認', '製作中', '発送準備中', '発送済み'], {}, class: "dropdown" %>
 <%= f.fields_for :order_products do |order_product| %>            
   <%= order_product.select  :status, ['着手不可', '製作待ち', '製作中', '製作完了'] %>   
  <% end %>
  <%= f.submit "更新" %>
<% end %>

order_products_attributes: [:id, :status, :_destroy]
まず一番にcontrollerのこいつ。order_paramsに入りこんでるこいつ。attributesを使うならこれとモデルへの記述が必要でidと_destroyは形式として突っ込むらしい。何故かはよくわからない。

それからfields_for。これをform_forの中にとりあえず入れると、そこで子要素を編集できる。fields_forをかけると子要素のレコードを繰り返し持ってこれるので.eachはいらない。ただし

タグで:product_nameなんかを表示させる為に.eachも併用すると途端にテーブルが崩壊する。そういう場合はもうテーブルを分ける。入力のテーブルと商品名とかを表示させるテーブルをいい感じにくっつける。

submitはform_forの中且つfields_forの外。

これでいける。

え、同じ値を全レコードのカラムにぶち込みたい?
.eachで .カラム = "入れたい値" を回せばいいんじゃないかな。
createならsaveする前に入れてさ。

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

【Rails】scope、ActiveSupport::Concernについて

環境

Rails 5.2.3
mysql 5.7.28

リテラルについて

例文

contoroller.rb
def index
  @boards = Board.includes(:user).order('created_at DESC')
end

def bookmarks
  @bookmarks = current_user.bookmarks.board.includes(:user).order('created_at DESC')
end

改善ポイント
・orderはリテラルではなくscopeで定義すべき

理由
・共通化できるクエリはscopeでメソッドのように定義すべき
・クエリはできるだけリテラルで使わないほうが良い
リテラル=変数やメソッド化していないただのベタ書きした文字や数字

メリット
・任意のメソッド名でどのような効果を持つかわかりやすくなる
・探す場所が1箇所に絞られ、リファクタリング容易(保守性)、データベースパフォーマンスの最適化(読み込みスピードUP)が図れる
・IDEの構文チェックがうまく走らないことがあり、ミスに気づけなくなることがある。

scope

共通的に使うクエリーをメソッドのように定義することができる便利な機能。

board.rb
scope :new_order, -> { order(created_at: :desc) }
contoroller.rb
def index
  @boards = Board.includes(:user).new_order
end

def bookmarks
  @bookmarks = current_user.bookmarks.board.includes(:user).new_order
end

このようにscopeを使用することで先ほどあげたメリットがある。
しかし、システムが大きくなるほどscopeの数が増えていき見通しが悪くなる。
そんな時にActiveSupport::Conernを使ってmoduleに機能を切り出す。

ActiveSupport::Concern

共通処理をモジュールとして切り出し、インクルードすることでモジュールを使用。

例文

board.rb
scope :new_order, -> { order(created_at: :desc) }
comment.rb
scope :new_order, -> { order(created_at: :desc) }

複数のモデルで同じscopeが定義されている時、ActiveSupport::Concernでまとめる。

引用
1. app/models/concerns/以下のモジュールファイルを作成する
2. ActiveSupport::Concernモジュールをextendで取り込む
3. includedブロック内でscopeを宣言する
4. モデル側でモジュールをincludeする

app/models/concerns/hoge.rb
require 'active_support'

module Hoge
  extend ActiveSupport::Concern

  included do
    scope :new_order, -> { order(created_at: :desc) }
  end
end
board.rb,comment共通
include Hoge

まとめ

参考資料
Rails: モデルの外では名前付きスコープだけを使おう(翻訳)
scopeをActiveSupport::Concernに分割する
[Rails] ActiveSupport::Concern の存在理由

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

rakeタスクに引数を渡したいとき

rakeタスクで引数を渡したい!
意外とまとまった記事がなかったので備忘。

タスク名とともに引数を書く

task :task_name, ['filter'] => :environment do |_task, args|
  p args
  p args[:filter]
  ~

filter の部分は任意の文字でOK

タスク実行方法

command: rake "task_name[arg]"

arg 部分に渡したい引数を書く。この場合だと "arg" が渡される。
タスク全体を "" で囲まないと、引数までタスクとして認識されない。

実行結果

=> #<Rake::TaskArguments filter: arg>
"arg"
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】kaminari&ransackでページネーションと検索機能を実装

【ページネーション】
1ページあたり20件に設定。
スタイルはBootstrap4で実装します。

【検索機能】
投稿一覧から検索とブックマーク済み投稿一覧から検索の2パターン実装していきます。
検索条件はタイトルか本文の部分一致とします。

【事前準備】
・ブックマーク機能を実装しておいてください。
参考資料:【Rails】ブックマーク(お気に入り)機能
・bootstrap4を使用できるように設定しておいてください。
参考資料:BootstrapをRailsに導入してみよう!徹底解説!

環境

Rails 5.2.3
mysql 5.7.28
gem kaminari
gem ransack

実装

それでは実装していきます。

gemの追加

gem kaminari
gem ransack

Gemfileに追加し、bundle install

Gemfile
gem 'kaminari'
gem 'ransack'
ターミナル
$ bundle install

ページネーション実装

まずはページネーションから実装していきます。

ターミナルでkaminariの設定ファイルを自動生成するコマンドを入力します。

ターミナル
$ rails g kaminari:config

これでkaminariの動作を変えるために必要な設定ファイルを生成できます。

次にページネーション用のビューファイルをを自動生成するコマンドを入力します。

ターミナル
$ rails g kaminari:views bootstrap4

これでbootstrap4のページネーションのデザインに合わせたビューファイルが自動で生成されます。

次に1ページあたりのデータ取得数を書き換えます。
デフォルトでは25件のデータを取得する設定になっています。

config/initializers/kaminari_config.rb
# frozen_string_literal: true
Kaminari.configure do |config|
  config.default_per_page = 20 # 25から20に書き換え
  # config.max_per_page = nil
  # config.window = 4
  # config.outer_window = 0
  # config.left = 0
  # config.right = 0
  # config.page_method_name = :page
  # config.param_name = :page
  # config.params_on_first_page = false
end

次はコントローラーにpageメソッドを追加していきます。

board_controller.rb
def index
  @boards = Board.includes(:user).page(params[:page])
end

def bookmarks
  @boards = current_user.bookmark_boards.includes(:user).page(params[:page])                  
end

kaminariをインストールしたことでモデルクラスに対してpageメソッドが使用できるようになります。
先ほど設定した1ページあたりのデータ取得件数を引数のparams[:page]に格納し、ビューで表示するようにします。

この時点で1ページ目には20件のデータが表示されているはずです。
2ページ目以降も見れるようにリンクを貼ります。
リンクを貼りたい箇所に<%= paginate @boards %>を記述すればOKです。
Image from Gyazo
サーバーを再起動して、このようなリンクが表示されていれば実装完了です。

日本語化

実装は完了ですが英語の部分を日本語化しておきます。

デフォルトのロケール(言語)を日本語にします。

config/application.rb
module アプリ名
  class Application < Rails::Application
  # 追記↓
    config.i18n.default_locale = :ja

次に日本語訳を設定するファイルを作成・記述していきます。
config/locales/ja.ymlを作成し任意の日本語を記述していきます。

config/locales/ja.yml
ja:
  views:
    pagination:
      first: 最初
      last: 最後
      previous: 
      next: 
      truncate: ...

これで設定が完了したのでサーバーを再起動したら反映されているはずです。

検索機能実装

controllerを編集

boards_controller.rb
def index
    @q = Board.ransack(params[:q]) # 検索オブジェクト作成
    @boards = @q.result.includes(:user).page(params[:page]) # 検索結果(検索しなければ全件取得)
end

def bookmarks
    @q = current_user.bookmark_boards.ransack(params[:q]) # 検索オブジェクト作成
    @boards = @q.result.includes(:user).page(params[:page]) # 検索結果(検索しなければ全件取得
end

viewを編集(検索フォーム)

boards/index.html.erb
<!-- 検索フォーム --> 
<%= render 'search_form', url: boards_path, q: @q %>
boards/bookmarks.html.erb
<!-- 検索フォーム -->
<%= render 'search_form', url: bookmarks_boards_path, q: @q %>
_search_form.html.erb
<%= search_form_for q, url: url do |f| %>
  <div class="input-group mb-3">
    <%= f.search_field :title_or_body_cont, class: "form-control", placeholder: '検索ワード' %>
    <div class="input-group-append">
      <%= f.submit "検索", class: "btn btn-primary" %>
    </div>
  </div>
<% end %>

<%= search_form_for q, url: url do |f| %>のurlを一覧とブックマーク一覧のurlで指定することでその範囲内から検索をすることができます。

<%= f.search_field :title_or_body_cont,...でtitleカラムとbodyカラムのどちらからでも検索が部分的に一致すれば表示されます。
条件はこちらのサイトを参考にしました。
Ransackで簡単に検索フォームを作る73のレシピ

viewを編集(検索結果一覧)

index.html.erb,bookmarks.html.erb共通
<!-- 掲示板一覧 -->
<% if @boards.any? %>
  <div class="row d-flex">
    <%= render @boards %>
  </div>
<% else %>
  <h1><%= '検索結果がありません。' %></h1>
<% end %>
<%= paginate @boards %>

これでインスタンスにデータが存在すれば一覧表示され、なければ検索結果がありませんと表示されます。

まとめ

わりとすんなり実装できたかなと思います。
詰まったとことしては検索範囲を全boardとbookmark_boardで範囲分けする部分でしたが、urlを検索範囲に指定することで解決しました。

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

enumを導入すると起きるエラー【rails】

概要

数字を文字列に変換してくれる最強助っ人「enum」ちゃんを導入するとエラー発生。

エラー文
'x'ArgumentError in xxx
'x' is not a valid xxx

スクリーンショット 2020-01-09 18.22.23.png

解決方法

Shift_user.model(enumを書いているモデル)での定義ミス。

詳細

Before↓

model.rb
class ShiftUser < ApplicationRecord

enum work_type:{
"午前": 1,
"午後": 2,
"一日": 3,
}
end

After↓

model.rb
class ShiftUser < ApplicationRecord
  enum work_type: {
    am: 1,
    pm: 2,
    all_day: 3
  }
  # 英語から日本語
  WORK_TYPE = {
    am: '午前',
    pm: '午後',
    all_day: '1日'
  }

そもそもenumで日本語でも指定はできるが、
よく使われるのが「アルファベット or _」を使って命名するらしい。

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

【Rails】 DataTables のテーブルに関連付けされたモデルのデータを表示する方法

はじめに

DataTableへデータを渡すときに json 形式に変換する必要があります。
関連付けされたモデルのデータを簡単に json 形式に変換することができます。

関連リンク

関連リンクを下記に載せておくので、必要であれば参考にしてください。。

関連付けされたモデルのデータを表示する方法

as_json の include をすることで user に関連付けされた post のデータを一緒に json のデータとして作成してくれます。

app/datatables/users_datatable.rb
class UsersDatatable
  # *** 省略 ***

  def as_json(options = {})
    {
        recordsTotal: User.count, # 取得件数
        recordsFiltered: users.total_count, # フィルター前の全件数

        # user モデルに関連付けされた post モデルを include して json ファイルを作成する。
        data: users.as_json(include: :post), # 表データ
    }
  end

  # *** 省略 ***
end

まとめ

こちらは突き詰めると、 DataTable 特有の使い方ではなく、 json ファイルの作り方の記事になってしまっています。
使い方が全くわからないという方には少しは参考になるかなーと思い投稿させていただきました。

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