20200514のRubyに関する記事は30件です。

Ruby:インスタンス変数(attr_accessorとself)に関して

本記事はまだ更新の可能性があります。
※2020/5/14時点


・attr_accessorメソッド
・インスタンス変数(メソッド内で定義するself)


これらの関連性と整理を追求する記事です。

出発点はメソッド内で
@変数名やself.変数名などの使い方がややこしすぎて
わけがわからなくなってしまったので始めています。

各種の初心者用説明サイトなどでは個別には説明されているものの
両方を混ぜ込んだコードでの説明はあまり見つけられませんでしたので
ここで自分の備忘録としても記載したく思います。

本記事は、なんとなく整理ができた成果物としてのコードを載せております。

それぞれの考察に関しては

attr_accessorメソッドについて

メソッド定義の中におけるselfについて

上記の2つの記事を参照ください。

混ぜ込んだコードとコンソールでの吐き出しを順に記載します。

class Alcohl
 attr_accessor :name
 def initialize(hobby)
  @hobby=hobby
 end 
 def my_hobby
  puts "私の好きなのは#{@hobby}"
  puts "でも大切なのは#{self.name}"
 end 
 def wisky
  "飲みづらいのは#{@hobby}"
 end 
end

alc=Alcohl.new("酒")
alc.name="オリオンビール"
alc.my_hobby
alc2=Alcohl.new("ウイスキー")
puts alc2.wisky

ec2-user:~/environment/ruby_lessons $ ruby hello.rb
私の好きなのは酒
でも大切なのはオリオンビール
飲みづらいのはウイスキー

以上です。

コードとしては汚いのかもしれませんが(それすらわからないのですが)、
とりあえず網羅性を意識しました。

いったん、こちらで整理とします。

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

Kinx ライブラリ - Database (SQLite3)

Database / SQLite3

はじめに

「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」 でお届けしているスクリプト言語 Kinx。言語はライブラリが命。ということでライブラリの使い方編。

今回は SQLite3 です。実際には後述するように Database オブジェクトを使用したほうが良いです。

SQLite ライブラリ自体は開発初期の段階からあったのですが、イマイチ使いづらかったので改善を考えてました。今回、Database クラスを用意したのでようやく公開です。

SQLite3

SQLite3 はポータブルで使いやすい組み込み RDB。よくお世話になっています。ファイル 1 つで完結すること、そのファイルがプラットホーム非依存なことにより、色々な用途に使える。

SQLite

コア・ライブラリとして SQLite オブジェクトがあるが、実際には後述する Database オブジェクトを使うのが良い。Database クラスは SQLite クラスをラップしてより使いやすいインターフェースを提供するクラス。

DB 接続

SQLite オブジェクトを new SQLite(filename[, timeout]) で作成してファイルに接続。

var db = new SQLite("sample.db", 30000);

Exec/Query

基本的に SQLite オブジェクトには基本メソッドとして execpreparetransaction の 3 つのメソッドがある。ただし、後述する Database クラスで、より使いやすいインターフェースを用意している。

SELECT 以外の単発コマンドで exec を使い、それ以外では prepare で SQL オブジェクトを作成して実行する。

exec

db.exec("CREATE TABLE IF NOT EXISTS mytable "
    "("
        "id INTEGER PRIMARY KEY AUTOINCREMENT, "
        "name TEXT NOT NULL"
    ")"
);

prepare

prepare で返された SQL オブジェクトは bindexecquerynext というメソッドが存在する。全てのメソッドは処理後に自分自身のオブジェクトを返す。

また、next メソッドは query メソッド呼び出し後に有効。next の結果は抽出した行オブジェクトであり、以下の形式となっている。

{
    "columnName": [ "col1", "col2", "col3" ],
    "value": [ val1, val2, val3 ]
}

例えば以下のように使う。

var c, r = db.prepare("SELECT * FROM mytable WHERE (id > ? AND id < ?) OR id = ?")
            .bind(2)
            .bind(8, 10)    // bind() は複数回に分けて実行できる。
            .query();       // これまで設定した値を実際にバインディングして next() 実行可能にする。

while (c = r.next()) {
    var cols = c.columnName;
    var vals = c.value;
    System.println("Record[%d]" % vals[0]);
    for (var i = 0, len = cols.length(); i < len; ++i) {
        System.println("    %-8s = %s" % cols[i] % vals[i]);
    }
}

尚、query は SELECT でのみ有効。SELECT 以外で exec を使用した場合、バインディングして実行した後、バインディングされていた値はその都度リセットされて次の exec に備える。

transaction

transacxtion は一連の操作をロックして行う。例えば、複数の値を一気にインサートしたいときなどは以下のようにする。ロックするので 1 つずつ実施するより高速に実施できる。

var ins = db.prepare("INSERT INTO mytable (name) VALUES (?)");
db.transaction(&{
    for (var i = 0; i < 20; ++i) {
        ins.bind("name(%{i})").exec();
    }
});

Database

クラス Database は上記 SQLite のラッパークラス。以下のメソッドがある。

  • transaction ... トランザクション処理を行う。
  • createTable ... テーブルを作成
  • dropTable ... テーブルを削除
  • insert ... データを挿入
  • delete ... データを削除
  • query ... データを抽出するための cursor オブジェクトを返す
  • queryAll ... データを全て抽出

インスタンス化

普通に new する。

var db = new Database(dbfile[, options]);

インスタンス化する際のオプションは以下のとおり。

  • force ... true の場合、ファイルが存在なければファイルを新規に作成する。false の場合はエラーとして例外を送出する。デフォルトは false。
  • timeout ... データベースの各種操作のタイムアウト値をミリ秒で指定。デフォルトは 10 秒。

以下のように指定する。

var db = new Database("dbfile.db", { force: true });

さらに、データベース名を複数のファイル名の配列として渡すと、最初の db をマスターとしてそれ以降の DB を自動的に ATTACH DATABASE する。その際、エイリアスとして自動的にファイル名の拡張子を除いた部分(いわゆる stem 部分、例えば abc.db であれば abc)を登録する。マスター DB は main で指定し、それ以外はエイリアスで指定する。

transaction

SQLite クラスの transaction より細かく指定できる。通常は同じように利用する。

db.transaction(&{
    ...
});

トランザクション処理が成功した場合は自動的にコミットする。途中で例外が発生した場合はロールバックを試みる。

また、Database クラスの transaction メソッドでは第一引数にモードを指定できる。モードは文字列で指定し、以下を指定可能。

  • 'EXCLUSIVE' ... BEGIN EXCLUSIVE でトランザクションを開始する。
  • 'IMMEDIATE' ... BEGIN IMMEDIATE でトランザクションを開始する。

未指定、または上記以外が指定された場合、BEGINBEGIN DEFERRED)である。サンプルは以下の通り。

db.transaction('EXCLUSIVE', &{
    ...
});

createTable

createTable にはテーブル名とスキーマを渡す。スキーマは直接文字列((id INTEGER, name TEXT) のような形)で渡すか、配列で渡す(単に , で結合されて () で括られる)。

db.createTable("mytable", [
    "id INTEGER PRIMARY KEY AUTOINCREMENT",
    "name TEXT NOT NULL"
]);

尚、常に IF NOT EXISTS で実行される。

dropTable

dropTable には単にテーブル名を渡す。

db.dropTable("mytable");

尚、常に IF EXISTS で実行される。

注意

ステートメントが残っていると database is locked の RuntimeException 例外が発生するので、ステートメントへの参照が残っていない状態で実施すること。

insert

insert には以下のパラメータを指定する。

パラメータ 内容
into 必須、テーブル名
replace true or false, 省略時 false
column 必須、カラム名(文字列)、またはカラム名の配列
bind 必須、column に対するバインディング・データを配列で指定、column で指定したカラムの数だけ必要

複数 insert する場合は transaction で囲んだほうが高速に実施される。

db.transaction(&{
    for (var i = 0; i < 20; ++i) {
        db.insert({
            into: "mytable",
            column: ["name"],
            bind: ["name(%{i})"],
        });
    }
});

delete

fromwherebind で指定。全て必須(必ず bind も必要)。

db.delete({
    from: "mytable",
    where: "id = ?",
    bind: [4]
});

query

条件に合うデータを DatabaseCursor で返す。初回の DatabaseCursor オブジェクトはデータをフェッチしていない状態で返される。

query() に指定できるパラメータは以下の通り。

パラメータ 内容
distinct true or false, 省略時 false
select 省略時は *、もしくはカラム名(文字列) or カラム名の配列
from 必須、テーブル名
innerJoin INNER JOIN 句 (innerJoinouterJoin は同時指定不可)
outerJoin OUTER JOIN 句
on innerJoin または outerJoin を指定したときだけ有効
where WHERE 句、文字列、または配列(AND で結合)
bind WHERE 句に対するバインディング・データを配列で指定、? の数だけ必要
groupby GROUP BY 句
having HAVING 句
orderby ORDER BY 句 (orderbyorderbyAscorderbyDesc は同時指定不可)
orderbyAsc orderby と同様
orderbyDesc ORDER BY 句、DESC 指定
limit LIMIT 句
offset OFFSET 句

以下がサンプル。

var c = db.query({
    select: "*",
    from: "mytable",
    where: "(id > ? AND id < ?) OR id = ?",
    bind: [2, 8, 10]
});

上記復帰値 c は DatabaseCursor オブジェクトで next()columns()values() メソッドを持つ。columns() はカラム名の配列を返し、values() は値の配列を返す。

DatabaseCursor オブジェクトは以下のように c.next() が null を返すまでフェッチすることで全抽出データを取得できる。

while (c.next()) {
    var cols = c.columns();
    var vals = c.values();
    System.println("Record[%d]" % vals[0]);
    for (var i = 0, len = cols.length(); i < len; ++i) {
        System.println("    %-8s = %s" % cols[i] % vals[i]);
    }
}

queryAll

queryAll は、コールバックを指定する方法と指定しない方法の 2 通りの使い方がある。コールバックを指定しなければ、全ての抽出データを一括で配列形式で取得できるが、データ量が多い場合メモリを消費してしまう可能性がある。それを回避するため、コールバック方式では 1 つずつイテレートしてコールバックするように動作する。

一括で取得する場合

一括で取得した場合は以下のようにコールバックを記載しない。

var res = db.queryAll({
    select: "*",
    from: "mytable",
    where: "(id > ? AND id < ?) OR id = ?",
    bind: [2, 8, 10]
});

以下のようなデータ構造で返る。

{
    "columns": ["id", "name"],
    "values": [
        [3, "name(2)"],
        [5, "name(4)"],
        [6, "name(5)"],
        [7, "name(6)"],
        [10, "name(9)"]
    ]
}

コールバックさせる場合

コールバックさせる場合は、以下のように関数を渡す。

db.queryAll({
    select: "*",
    from: "mytable",
    where: "(id > ? AND id < ?) OR id = ?",
    bind: [2, 8, 10]
}, &(c, i) => {
    System.println("%2d => " % i, c.toJsonString());
});

以下のようにそれぞれの行のオブジェクトが返される。この場合は DatabaseCursor オブジェクト ではない ので注意。

 0 => {"columns":["id","name"],"values":[3,"name(2)"]}
 1 => {"columns":["id","name"],"values":[5,"name(4)"]}
 2 => {"columns":["id","name"],"values":[6,"name(5)"]}
 3 => {"columns":["id","name"],"values":[7,"name(6)"]}
 4 => {"columns":["id","name"],"values":[10,"name(9)"]}

尚、コールバック関数で明示的に false を返すとイテレーションを中止して制御を戻す。

db.queryAll({
    select: "*",
    from: "mytable",
    where: "(id > ? AND id < ?) OR id = ?",
    bind: [2, 8, 10]
}, &(c, i) => {
    System.println("%2d => " % i, c.toJsonString());
    return false if (i == 2);
});

以下のようになる。

 0 => {"columns":["id","name"],"values":[3,"name(2)"]}
 1 => {"columns":["id","name"],"values":[5,"name(4)"]}
 2 => {"columns":["id","name"],"values":[6,"name(5)"]}

サンプル

サンプル・コードは GitHub にあります。お試しください。

おわりに

SQLite3 使いやすいですね。単純にデータ・ストレージに使ってもいいですし、ロガーのバックエンドに使ったり、ポータブルな設定ファイルとして使ったりと色々使えます。何より RDB や SQL の基本的なことを学習するのにも良いかなー、と思います。

ではでは、また次回。

  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

herokuを使ったデプロイ手順

今回はherokuを使ったデプロイ方法について紹介していきます。

手順

1.herokuにログインする(herokuのアカウントがない人は下記のURLからアカウントを作成しましょう)
https://signup.heroku.com/

2.Gemfileを以下のように編集

*DBでsqliteを使用している場合*

group :development do
  <!-- sqliteを使用している場合下記のgemを追加 -->
  gem 'sqlite3'
  # Access an interactive console on exception pages or by calling 'console' anywhere in the code.
  gem 'web-console', '>= 3.3.0'
  gem 'listen', '>= 3.0.5', '< 3.2'
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'
end

*DBでmysqlを使用している場合*

group :development do
  <!-- mysqlを使用している場合下記のgemを追加 -->
  gem 'mysql2', '>= 0.4.4', '< 0.6.0'
  # Access an interactive console on exception pages or by calling 'console' anywhere in the code.
  gem 'web-console', '>= 3.3.0'
  gem 'listen', '>= 3.0.5', '< 3.2'
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'
end

アプリ立ち上げ時にDBを指定した際は、初期でgemfileにgemの記述があるので削除かコメントアウトしておきましょう!
Image from Gyazo

下記の記述もGemfileに追加し本番環境に適したパッケージに変更します。

group :production do 
  gem 'pg'
end

3.productionの中以外をアップデートする

$ bundle --without production

4.gitの管理下に置く

$ git init

5.heroku上にアプリ作成(アプリ名は被らないような名前)

$ heroku create アプリ名

6.全てのフォルダやファイルを管理対象にすることを明示

$ git add *

7.現在の状態をコメント付きで保存

$ git commit -m "first commit"

8.現在の状態をherokuに送信

$ git push heroku master

9.Heroku上にデータベース作成

$ heroku run rails db:migrate

*seeds.rb記述している場合は下記のコマンドも実行する*

$ heroku run rails db:seed

10.アプリを開く

$ heroku open

デプロイ後にローカルで編集した際には、6の手順から再び実行すると本番環境にも反映されます。

以上

  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

Rubyのsortとsort_byを理解してマルチソートをしよう

Rubyの配列を並び替えるときはArray#sortArray#sort_byを使うことになるかと思います。

data = (1..10).to_a.shuffle
p data.sort # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

こんなシンプルなsortをする場面なんてほとんど無いでしょうから、なんの参考にもなりませんね笑

よくあるものですと、DBやcsvなどのデータを取得したあとに並び替えたいときですね。

サンプルデータ
require 'date'
data = [
  {id: 1, name: '吉田(A)', ruby: 'yoshida', join_date: Date.new(2009, 4, 1)},
  {id: 2, name: '鈴木', ruby: 'suzuki', join_date: Date.new(2015, 4, 1)},
  {id: 3, name: '吉田(B)', ruby: 'yoshida', join_date: Date.new(2009, 4, 1)},
  {id: 4, name: '佐藤', ruby: 'sato', join_date: Date.new(2006, 10, 1)},
  {id: 5, name: '田中', ruby: 'tanaka', join_date: Date.new(2009, 4, 1)},
]

例えばこんな社員マスタみたいなものがあったとします。

数字の降順で並び替える

idを降順に並び替えたい場合はシンプルにsortにブロックを渡してbを先にしてあげればできます。

idの降順
pp (data.sort do |a, b|
  b[:id] <=> a[:id]
end)

#[{:id=>5,
#  :name=>"田中",
#  :ruby=>"tanaka",
#  :join_date=>#<Date: 2009-04-01 ((2454923j,0s,0n),+0s,2299161j)>},
# {:id=>4,
#  :name=>"佐藤",
#  :ruby=>"sato",
#  :join_date=>#<Date: 2006-10-01 ((2454010j,0s,0n),+0s,2299161j)>},
# {:id=>3,
#  :name=>"吉田(B)",
#  :ruby=>"yoshida",
#  :join_date=>#<Date: 2009-04-01 ((2454923j,0s,0n),+0s,2299161j)>},
# {:id=>2,
#  :name=>"鈴木",
#  :ruby=>"suzuki",
#  :join_date=>#<Date: 2015-04-01 ((2457114j,0s,0n),+0s,2299161j)>},
# {:id=>1,
#  :name=>"吉田(A)",
#  :ruby=>"yoshida",
#  :join_date=>#<Date: 2009-04-01 ((2454923j,0s,0n),+0s,2299161j)>}]

文字列の昇順

では、名前のローマ字の昇順にしたい場合はどうでしょうか?
sato, suzuki, tanaka, yoshida, yoshidaの順番にしたい場合ですね。

ローマ字順
pp (data.sort do |a, b|
  a[:ruby] <=> b[:ruby]
end)

#[{:id=>4,
#  :name=>"佐藤",
#  :ruby=>"sato",
#  :join_date=>#<Date: 2006-10-01 ((2454010j,0s,0n),+0s,2299161j)>},
# {:id=>2,
#  :name=>"鈴木",
#  :ruby=>"suzuki",
#  :join_date=>#<Date: 2015-04-01 ((2457114j,0s,0n),+0s,2299161j)>},
# {:id=>5,
#  :name=>"田中",
#  :ruby=>"tanaka",
#  :join_date=>#<Date: 2009-04-01 ((2454923j,0s,0n),+0s,2299161j)>},
# {:id=>1,
#  :name=>"吉田(A)",
#  :ruby=>"yoshida",
#  :join_date=>#<Date: 2009-04-01 ((2454923j,0s,0n),+0s,2299161j)>},
# {:id=>3,
#  :name=>"吉田(B)",
#  :ruby=>"yoshida",
#  :join_date=>#<Date: 2009-04-01 ((2454923j,0s,0n),+0s,2299161j)>}]

ブロック内の比較対象を:rubyにするだけでできました。
これはString#<=>が実装されているために簡単に実現することができています。

sortってなにしてくれてるんだっけ

っていうか、そもそもなんで<=>を指定すると並び替えてくれるんだっけ?
というのを思い返すときにArray#sortのドキュメントを読んでみます。

ブロックとともに呼び出された時には、要素同士の比較をブロックを用いて行います。ブロックに2つの要素を引数として与えて評価し、その結果で比較します。ブロックは <=> 演算子と同様に整数を返すことが期待されています。つまり、ブロックは第1引数が大きいなら正の整数、両者が等しいなら0、そして第1引数の方が小さいなら負の整数を返さなければいけません。両者を比較できない時は nil を返します。

つまり最終的にブロックの最後に評価される値が整数を返してあげれば並び替えてくれるわけです。
<=>メソッドは何らかの比較をして1, -1, 0, nilのいずれかを返すように実装されているので、それを使えば簡単に並び替えできる、というわけです。

Arrayに<=>がおるやん

それを理解した上でArrayのドキュメントを見ると気付くわけです。
Array#<=>が実装されていることに。

自身と other の各要素をそれぞれ順に <=> で比較していき、結果が 0 でなかった場合にその値を返します。各要素が等しく、配列の長さも等しい場合には 0 を返します。各要素が等しいまま一方だけ配列の末尾に達した時、自身の方が短ければ -1 をそうでなければ 1 を返します。 other に配列以外のオブジェクトを指定した場合は nil を返します。

つまり配列同士の比較もできるわけです。
配列の先頭から比較し、同値の場合には次の要素を検証していき、同値じゃなかった場合には1, -1を返してくれます。
ここまで来ればもうマルチソートのやり方もなんとなく理解できましたね。

マルチソートしてみる

ではサンプルのデータで、先程ローマ字順に並び替えましたが、yoshidaがかぶっています。
sortでは元の順番が保持されているためidが低い1のyoshidaが先に来ています。
3のyoshidaを先に持っていきたい(第2ソートキーとしてidの降順にしたい)場合はどうするか?

こうです。

第二ソートキーとしてidを指定
pp (data.sort do |a, b|
  [a[:ruby], -a[:id]] <=> [b[:ruby], -b[:id]]
end)

最初に:rubyで比較をし、そこで優劣がつくようなら:idでの評価はしません。
:idでの比較の際に-をつけ、値を逆転させることで降順を実現しています。

日付(Dateクラス)の降順の並び替えは?

じゃあマルチソートは一旦置いといて、今度は入社年月日の降順(新しい順)にしたい場合はどうでしょうか?
Dateクラスにも<=>が実装されているので並び替えは容易ですね。
しかし降順となるとIntegerのように-をすることはできません。(Date#-は実装されていますが、算術演算子としての-なので引数が必要です)

そうなった場合、最初の:idでの降順の並び替えで提示したようにaとbを入れ替えてあげることで実現できます。

join_dateの降順で並び替え
pp (data.sort do |a, b|
  b[:join_date] <=> a[:join_date]
end)

#[{:id=>2,
#  :name=>"鈴木",
#  :ruby=>"suzuki",
#  :join_date=>#<Date: 2015-04-01 ((2457114j,0s,0n),+0s,2299161j)>},
# {:id=>1,
#  :name=>"吉田(A)",
#  :ruby=>"yoshida",
#  :join_date=>#<Date: 2009-04-01 ((2454923j,0s,0n),+0s,2299161j)>},
# {:id=>3,
#  :name=>"吉田(B)",
#  :ruby=>"yoshida",
#  :join_date=>#<Date: 2009-04-01 ((2454923j,0s,0n),+0s,2299161j)>},
# {:id=>5,
#  :name=>"田中",
#  :ruby=>"tanaka",
#  :join_date=>#<Date: 2009-04-01 ((2454923j,0s,0n),+0s,2299161j)>},
# {:id=>4,
#  :name=>"佐藤",
#  :ruby=>"sato",
#  :join_date=>#<Date: 2006-10-01 ((2454010j,0s,0n),+0s,2299161j)>}]

マルチソートで日付の降順の方法、その2

しかしこれをマルチソートのキーとして採用するとちょっとわかりづらくなる気がします。しませんか?私だけですか?

join_dateの降順で並び替え
pp (data.sort do |a, b|
  [a[:ruby], b[:join_date]] <=> [b[:ruby], a[:join_date]]
  # aとbのキーを間違えてない???って思ってしまう
end)
# サンプルデータだとあんまり意味がないのでppの結果は省略します笑

まぁ-で値を逆転させるのもあまり直感的ではないかも知れないので好みの問題だとは思いますが
個人的にはunixtime化して計算させるほうがわかりやすく思います。

pp (data.sort do |a, b|
  a_time = a[:join_date].to_time.to_i
  b_time = b[:join_date].to_time.to_i
  [a[:ruby], -a_time] <=> [b[:ruby], -b_time]
end)

と、こんな感じで配列同士を比較させればマルチソートでの昇順/降順も自由にコントロールできます。

処理が肥大化しそうならsort_byを使えばいいじゃない

ただ上記のto_time.to_iとかもそうですし、nullrableな値がある場合の処理とかを考えると、a,bそれぞれの変数に対して同じことをやってブロックの中が肥大化していく未来が見えます。

そこでEnumerable#sort_byを使うのが良いのではないかと考えます。

ブロックの評価結果を <=> メソッドで比較することで、self を昇順にソートします

とあるので、ブロックの最後の評価されるものが<=>で比較できればよいので配列を渡せばArray#<=>でマルチソートが簡単にできるやん、という感じです。

つまりこう
pp (data.sort_by do |v|
  [v[:ruby], -v[:join_date].to_time.to_i, v[:id]]
end)

またドキュメントにある通り、複雑な並び替えの場合にはsortよりも処理数が少なくパフォーマンス的にも期待できます。

まとめ

  • sortのブロックには整数を返せばよい
  • 配列を渡せばPHPのarray_multisortみたいなことができる
  • 複雑な条件にはsort_byの方がおすすめ

ブロック内でififするのは複雑度が上がって良くないので配列を渡しちゃいましょう!

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

Rails パンくずリスト gretel の使い方

概要

こんにちは、今回はパンくずリストを作成する際に使ったgretelというgemの使い方を記載します。

gretel

こんな感じです。
スクリーンショット 2020-05-14 19.22.42.png

インストール

gem 'gretel'

bundle installします。

ファイル作成

下記コマンドを打つと専用の設定ファイルが作成されます。

$ rails generate gretel:install

config/breadcrumbs.rbというファイルが作成されたはずです。

パンくずを作成

前項で作成された設定ファイルにパンくずを設定していきます。

config/breadcrumbs.rb
crumb :top do
  link "トップページ", root_path
end

crumb :shops do
  link "カフェ一覧", shops_path
  parent :top
end

crumb :user do |user|
  link user.username, user_path(user)
  parent :shops
end

crumb :edit_user do |user|
  link "編集", edit_user_registration_path
  parent :user, user
end

crumb :following_user do |user|
  link "フォロー", following_user_path
  parent :user, user
end

crumb :followers_user do |user|
  link "フォロワー", followers_user_path
  parent :user, user
end

crumb :shop do |shop|
  link shop.name, shop_path
  parent :shops
end

crumb :edit_shop do |shop|
  link "編集", edit_shop_path
  parent :shop, shop
end

今回は最大4階層までのパンくずを設定しました。

crumbでパンくずを定義し、linkにパスを記述します。
parentには親となるパンくずを設定。

parent :モデル名 と記述したあとに注目してください。

crumb :edit_shop do |shop|
  link "編集", edit_shop_path
  parent :shop, shop #ここに注目!
end

このカンマ以降の記述が無ければ対象のビューで
NoMethodErrorが出てしまいます。

この書き方がなかなか分かりませんでした。

viewに配置

view側はシンプルです。

html.erb
<% breadcrumb :user, @user %>
<%= breadcrumbs separator: " &rsaquo; " %>

これだけです。

separatorでパンくずの階層である > を追加しています。
その他にも色々とオプションがあるのでぜひ公式で確認してみてください。

以上です。
お付き合い頂きありがとうございました。

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

AtCoder Beginners SelectionでRuby学習【PracticeA】標準入力

はじめに

Ruby学習の一環として「競技プログラミング(競プロ)」に挑戦します。
そのための学習の中で学んだことをアウトプットしていきます。
今回は「AtCoder Beginners Selection」の一問目(PracticeA)より、標準入力について。
https://atcoder.jp/contests/abs

問題

整数a,b,cと、文字列sが与えられます。
a+b+cの計算結果と、文字列sを並べて表示しなさい。

制約
1≤a,b,c≤1,000
1≤|s|≤100

入力は以下の形で与えられる。

a
b c
s

# 例
1
2 3
test

a+b+cとsを空白区切りで1行に出力せよ。

出力例
# 上記例の場合
6 test

解答例

a = gets.to_i
b,c = gets.chomp.split("").map(&:to_i) 
s = gets.chomp

print("#{a+b+c} #{s}\n")

ここで使われている標準入力に関わるメソッドを一つ一つ見ていきます。

getsメソッド

rubyにおいて標準入力を受け取るにはgetsメソッドを使います。
ただ、getsメソッドで標準入力を受け取る際には以下の2点に注意します。
①標準入力を文字列で受け取る
②文字列を受け取る場合は末尾に改行コード\nが付いて来る。

#入力 3
a = gets
print a
=> "3\n"  

to_iメソッド

文字列を整数にして返すメソッドです。
getsメソッドのみでは、整数ですら文字列として受け取ってしまうため、
整数を整数として扱いたい場合は、to_iメソッドを使って整数に戻してあげる必要があります。

#入力 3
a = gets.to_i
print a
=> 3  

この場合、改行コードも消してくれます。

chompメソッド

getsメソッドで文字列を受け取る際の改行コード問題。
これを解決してくれるのがchompメソッドです。末尾の改行コードを取り除いてくれます。
この改行コードの存在をそのままにすると、これだけで誤答判定される場合もあるようなので注意が必要です。

#入力 test
s = gets.chomp
print s
=> "test"  

ちなみに
to_iメソッドの部分でも述べましたが、整数に変換した後は使う必要はありません。
必要ない場合にchompメソッドを付けてしまうと処理速度の低下に繋がるので注意。

splitメソッド

文字列を分割して配列にしてくれるメソッドです。
区切り文字を「split("区切り文字")」の形で指定することで、区切り部分を指定できます。
解答例では、bとcの間のスペースを区切り文字として指定しています。

# 段階的に見ていきます。

#入力 3 4
a = gets
print a
=> "3 4\n"

b = a.chomp
print b
=> "3 4"

c = b.split(" ")
print c
=> ["3", "4"]

# まとめて書くと 

#入力 3 4
d = gets.chomp.split(" ")
print d
=> ["3", "4"]

mapメソッド

配列の要素の数だけ繰り返し処理を行ってくれます。
今回の解答で用いている「map(&:to_i)」では、
繰り返し処理の引数として「to_iメソッド」を「&」を使うことで渡せる状態にしています。
このことについては、詳しく書くと長くなる内容なので…
とりあえず、下記の記事が詳しく書いて下さっているので、こちらを覗いてみられるといいかと。

["1", "2", "3"].map(&:to_i) とは何か。

近いうちに自分でも記事にしてみようと思います。

# splitメソッドの続きから

#入力 3 4
d = gets.chomp.split(" ")
print d
=> ["3", "4"]

e = d.map(&:to_i)
print e
=> [3, 4]

最後に

以上、AtCoder Beginners SelectionでRuby学習【PracticeA】で学ぶ標準入力でした。
mapメソッドの部分で課題は残りましたが、
ここもしっかり理解して自分の力にしていきたいと思います。
間違いなどありましたら、ご指摘いただけると嬉しいです。

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

hamlの導入方法

今回はhamlを導入し、erbをhamlに変換する方法を紹介していきます。

手順

1.Gemfileに以下を記述

gem 'haml-rails'

2.bundle install

bundle install

3.ターミナルで以下のコマンドを実行し、拡張子がerbのファイルをhamlに変換する。

$ rails haml:erb2haml

※Would you like to delete the original .erb files? (This is not recommended unless you are under version control.) (y/n)
と聞かれるため、yと入力するとデフォルトで作成されたerbファイルはHamlに変換される。

以上

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

bootstrapの導入方法

bootstrapってなに?

RubyのフレームワークはRuby on Railsで言うところの、
boootstrapとはCSSのフレームワークみたいなものです。

bootstrapを学べば、効率的にビューを実装できるので知っておいて損はないです。。

導入方法

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

gem "sass-rails","~>5.0"
gem "bootstrap-sass","~>3.3.6"
gem "jquery-rails"
gem "jquery-ui-rails"

2.bundle updateする

 $ bundle update

3.appcation.cssを.scssに拡張してから下記を記述する。

 @import "bootstrap-sprockets";
 @import "bootstrap";

4.appcation.jsの中に下記を記述。

//= require jquery //= require~と書いてる部分の一番上に記述
//= require bootstrap-sprockets //= require~と書いてる部分の一番下に記述

以上

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

モデルの単体テストの書き方

はじめに

みなさん単体テストは好きですか??
私は苦手です。。。。

多分苦手な方も多いと思うので、今回はモデルの単体テストの書き方について紹介できればいいかなと思います。
私も未熟なので、参考程度にしてください。おねがいします。。

1.Gemfile

はじめにgemfileにrspecを導入していきます。
今回はfactory_botも使うので、以下のようにgemfileに記述していきましょう。

group :development, :test do
  gem 'rspec-rails', '~> 3.8'
  gem 'factory_bot_rails', '~> 5.0'
end

bundle installも忘れずに!

$ bundle install

2.RSpecの設定

ターミナルで以下のコマンドを記述。
specというディレクトリが生成されます。

$ rails g rspec:install

.rspecに以下を記述

--format documentation

テスト用のDBを最新の構成にする

$ rails db:migrate:reset RAILS_ENV=test

3.モデルを作成し単体テストを書いていこう

まずはテストモデルを生成します。

$ rails generate rspec:model モデル名

それではfactory_botを使ってモデルにテストデータを予め準備します。
spec/factories/モデル名.rbファイルを作成しましょう。
ファイルの中身の模範は以下のようです。

FactoryBot.define do

  factory :モデル名 do
    カラム名     {"仮の値"}
  end

end

実際に書くとこんな感じ。

FactoryBot.define do

  factory :product do
    name            {Faker::Games::Zelda.item}
    infomation      {Faker::Games::Zelda.game}
    category_id     {"1"}
    brand           {Faker::Games::Zelda.location}
    status_id       {"1"}
    delivery_id     {"1"}
    area_id         {"1"}
    day_id          {"1"}
    price           {Faker::Number.number(digits: 4)}
    situation       {"1"}
    user
  end

end

続いて、factory_botの情報を使ってテストコードを書いていきます。
spec/models/モデル名_spec.rbファイルを作成しましょう。
ファイルの中身の模範はこんな感じ。

require 'rails_helper'

describe モデル名 do
  describe '#メソッド名' do
    context 'グループ分けの説明' do
      it 'テストの説明' do
        テスト内容
      end
    end
  end
end

実際はこんな感じ。
factory_botを使用しているので、記述量が減って簡単にかけます!

require 'rails_helper'

describe Product do
  describe '#create' do

    context '出品できる場合' do
      it "productとproduct_imageがある場合は保存できること" do
        expect(build(:product, product_images: [build(:product_image)])).to be_valid
      end
    end

    context '出品できない場合' do
      it "product_imageがない場合は保存できないこと" do
        product = build(:product)
        product.valid?
        expect(product.errors[:product_images]).to include("can't be blank")
      end

      it "nameがない場合は保存できないこと" do
        product = build(:product, name: nil, product_images: [build(:product_image)])
        product.valid?
        expect(product.errors[:name]).to include("can't be blank")
      end

      it "infomationがない場合は保存できないこと" do
        product = build(:product, infomation: nil, product_images: [build(:product_image)])
        product.valid?
        expect(product.errors[:infomation]).to include("can't be blank")
      end

      it "category_idがない場合は保存できないこと" do
        product = build(:product, category_id: nil, product_images: [build(:product_image)])
        product.valid?
        expect(product.errors[:category_id]).to include()
      end

      it "status_idがない場合は保存できないこと" do
        product = build(:product, status_id: nil, product_images: [build(:product_image)])
        product.valid?
        expect(product.errors[:status_id]).to include()
      end

      it "delivery_idがない場合は保存できないこと" do
        product = build(:product, delivery_id: nil, product_images: [build(:product_image)])
        product.valid?
        expect(product.errors[:delivery_id]).to include()

      end

      it "area_idがない場合は保存できないこと" do
        product = build(:product, area_id: nil, product_images: [build(:product_image)])
        product.valid?
        expect(product.errors[:area_id]).to include()
      end

      it "day_idがない場合は保存できないこと" do
        product = build(:product, day_id: nil, product_images: [build(:product_image)])
        product.valid?
        expect(product.errors[:day_id]).to include()
      end

      it "priceがない場合は保存できないこと" do
        product = build(:product, price: nil, product_images: [build(:product_image)])
        product.valid?
        expect(product.errors[:price]).to include()
      end

      it "priceが300以下の場合は保存できないこと" do
        product = build(:product, price: "299", product_images: [build(:product_image)])
        product.valid?
        expect(product.errors[:price]).to include()
      end

      it "priceが9,999,999以上の場合は保存できないこと" do
        product = build(:product, price: "10000000", product_images: [build(:product_image)])
        product.valid?
        expect(product.errors[:price]).to include()
      end

      it "user_idがない場合は保存できないこと" do
        product = build(:product, user_id: nil, product_images: [build(:product_image)])
        product.valid?
        expect(product.errors[:user_id]).to include()
      end
    end
  end
end


最後に以下のコマンドをターミナルで実行し、テストがうまくいっているか確認です!

$ bundle exec rspec spec/models/モデル名_spec.rb

以上です!

  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

CurrentBranchにMasterBranchの情報を反映させたい

背景

チーム開発において自分の作業ブランチにマスターブランチの情報をもってきたい!!
だけどどうすればいいかわからない、、、

そんなときの解決策を今回は紹介します。

以下の4つのコマンド実行するだけです。

コマンドを実行する前の注意点として、編集途中のファイル等がある場合は、一度コミット&プッシュして何もない状態にしておきましょう。

<!-- 開発中ブランチからmasterブランチへ移動 -->
$ git checkout master

<!-- リモートのmasterを反映 -->
$ git pull origin master

<!-- masterブランチから開発中のブランチへ移動 -->
$ git checkout 開発中のブランチ名

<!-- maserの内容を開発中ブランチに取り込む -->
$ git merge origin master

以上

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

devise導入したときのカラムの増やし方

悩み事

devise導入したらデフィルトでemailとpasswordしか入ってないじゃん、、、、
usersテーブルにnameやaddressカラムとかも追加したいのにどうすればいいかわからない!!

ってこと一度はありますよね。

今回はそのお悩み解決いたします!!

1.usersテーブルにカラムを追加

今回はnameカラムを追加してみようと思うので、
以下のようにターミナルでコマンドを実行しましょう。

$ rails g migration AddNameToUsers name:string

$ rails db:migrate

2.application_controller.rbを編集

application_controller.rbを以下のように編集しましょう。
これでユーザー登録時にnameカラムが保存されるようになります。

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :configure_permitted_parameters, if: :devise_controller?

  protected
  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:name])
  end
end

今の状態でuser情報を登録するとnameカラムも追加されてるしいい感じだ!!
と安堵するのはまだ早いのです。。。

今のままでは登録時にしかnameカラムが入っていないので、編集でnameを変更しても変更されません。
不完全な状態です。

3.完全感覚dreamer

じゃあ編集時に追加したカラムの編集もしっかりとできるようにしましょう!
とはいえとても簡単です。

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :configure_permitted_parameters, if: :devise_controller?

  protected
  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:name])
    <!-- 以下のように編集時の対応する記述も追加してあげましょう -->
    devise_parameter_sanitizer.permit(:account_update, keys: [:name])
  end
end

これでバッチリです!!
以上!

  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

active_hashの使い方

active_hashとはなんぞや?

Active_Recordのように読み込み専用情報をまとめたハッシュを扱うことができるもの。

こんな時に使いたい!

テーブルを持つほどでもない・ほとんど変更のないようなちょっとした静的データを保持したい!

1.Gemfile

gemfileに以下の記述する。

gem 'active_hash'

bundle installも忘れずに!

$ bundle install

2.モデルの作成

今回は毎度おなじみのrails g modelでの作成ではなく、
ActiveHash::Baseを継承したモデルを自作する方法で進めていきます。

prefecture(都道府県)のモデルを作成していきます。

app/models/prefecture.rbという感じで作ってみましょう!!

prefecture.rb
class Prefecture < ActiveHash::Base
  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.アソシエーションを組んでいくぅ

今回はフリマアプリだと仮定して、商品の配送先を登録するときに都道府県をactive_hashを使って表示をしたいので、
product.rbにアソシエーションを組んでいきましょう。

product.rb
class Product < ApplicationRecord
  extend ActiveHash::Associations::ActiveRecordExtensions
  belongs_to_active_hash :prefecture
end

4.表示していくぅ

商品を登録するフォームなどでcollection_selectを使う際は以下のように表示できます。

Image from Gyazo

= f.collection_select :prefecture_id, Prefecture.all, :id, :name, {prompt:"選択してください"}, {class:""}

詳細画面などで登録した情報として表示したいときは以下のように記述すれば表示がうまくいくと思います。

= @product.prefecture.name

以上

  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

Gemを利用しないでAPIを扱ってみよう

楽天商品検索APIを使ってみよう

まず企業や団体が提供しているAPIを利用するためには、大抵の場合そのサービスへの利用申請が必要になります。これはAPIを利用する時にパスワードとなるアプリケーションIDを発行してもらうためです。

アプリケーションIDを取得したら、それを含めたリクエストを送るコードを書き、実行します。

すると返り値に、各APIが処理した後の値が帰ってきます。この値のデータ型は楽天商品検索APIではJSONというものになっています。なのでこれをRubyで扱えるようにするために、ハッシュへと変換します。

その後、ハッシュの値を出力するなどのコードを書きます。

大まかな流れは以下のようになります
1. 楽天APIを使うためのアプリ登録をし,アプリケーションIDを取得する
2. RubyでWeb APIへリクエストするコードを書く
3. JSONというデータ型で返って来た値をRubyのハッシュに変換するコードを書く

楽天APIを使うためのアプリ登録をし,アプリケーションIDを取得する

以下のページからアプリケーションIDを取得することができます。
Rakuten Web Service

ここでは

  • アプリ名
  • アプリURL

を記入する必要がありますが、今は存在しないもので大丈夫です。

これでアプリIDを取得できたと思います。

RubyでWeb APIへリクエストするコードを書く

まずAPIを使いたいアプリのあるディレクトリに、Rubyファイルを作成しましょう。

$ touch rakuten_api.rb

続いて、先ほど作成したアプリIDを.bash_profileに環境変数として書き込みます

環境変数とは

環境変数は、サーバーに直接登録して参照できる変数のことです。外部に漏らしたくないパスワードなどの情報(今回でいうアプリID)を、直接環境下に置くことで、外部から不正に利用されることを防ぎます。

1. bash_profileに環境変数を書き込む

ターミナル
$ sudo vi ~/.bash_profile

2. 「i」を入力して、入力モードにする

3. アプリケーションIDを記述する

.bash_profile
export RAKUTEN_APP_ID='取得したアプリIDを記述'

4. 記述が終わったら、「esc」キーを押してから「:wq」「エンター」で保存します。

5. 先ほどの記述を読み込ませます。

ターミナル
$ source ~/.bash_profile

次にAPIを利用するためのコードを書きます。

以下のコードは先ほど作成した、rakuten_api.rbに記述します。

全体のコードは以下のようです。

rakuten_api.rb
require 'net/http'
require 'uri'
require 'json'

puts "検索したい文言を入力してください!"
search_word = gets.chomp

appid = ENV["RAKUTEN_APP_ID"]

url = URI.parse(URI.escape("https://app.rakuten.co.jp/services/api/IchibaItem/Search/20140222?applicationId=#{appid}&keyword=#{search_word}"))
res = Net::HTTP.start(url.host, url.port, use_ssl: true){|http|
    http.get(url.path + "?" + url.query);
}
obj = JSON.parse(res.body)
puts obj

細かい操作についてみていきます。

まず標準ライブラリを呼び出す

rubyには組み込みライブラリとは別に、標準ライブラリというのがあります。組み込みライブラリはファイルに特別な記述をしなくても最初から利用できます。ところが、組み込みライブラリには含まれていない機能を使う際には標準ライブラリを使います。http通信を扱ったり、CSVファイルの操作をしたりするライブラリが存在します。

(今回はGemを使わないでAPIを利用するので、この操作が必要となります。もしGemを使うのであれば、必要な標準ライブラリも自動で呼び出されるので必要ないです。)

標準ライブラリは、Gemを利用するのと同様にrequire ◯◯という記述によって呼び出します。
以下のコードは net/http と uri という標準ライブラリを設定しています。

rakuten_api.rb
require 'net/http'
require 'uri'
require 'json'

これらは、必要な標準ライブラリを呼び出すための記述です。

net/http はhttp通信をRubyで扱うための標準ライブラリです。。これによって、今回使うNet::httpクラスとstartメソッドが利用できるようになります。そして、Webサーバーからドキュメントを得たりフォームの情報を送信したりすることがRubyのプログラムでできます。

uri はURLを扱うための標準ライブラリです。URIとはURLの広義的なものです。これによって、簡単にURIのドメインなどを取り出すことができます。

3行目で、JSONファイルを読み込めるようにします。

次に具体的な操作をみていきます。

楽天市場から商品を検索する

rakuten_api.rb
appid = ENV["RAKUTEN_APP_ID"]

url = URI.parse(URI.escape("https://app.rakuten.co.jp/services/api/IchibaItem/Search/20140222?applicationId=#{appid}&keyword=#{search_word}"))
res = Net::HTTP.start(url.host, url.port, use_ssl: true){|http|
    http.get(url.path + "?" + url.query);
}

まず環境変数として.bash_profileに書き込んだものを一度変数に代入しています。

次にuri環境ライブラリを読み込んだことによって利用できるようになったURIクラスの
parseメソッドを使って、URIのドメインを取り出します。ここで大切なのはhtttps://app.‥
の先にある、?以降の部分です。これはクエリパラメータと言います。
クエリパラメータとはさまざまな情報をWebサーバーに伝えるためにURLに付け加える情報のことです。

クエリパラメータの基本構造は以下のようです。

?パラメータ名=パラメータの値

パラメータは1つとは限らず、複数ある場合は&で繋いでいきます。

今回だと applicationId と keyword という2つにそれぞれアプリIDや検索ワードを代入しています。

またparseメソッドの使い方は以下のようです。

require 'uri'
uri = URI.parse("https://www.google.co.jp/")

例えば、上記のように実行すると以下のようにhttpの構成要素を分解してくれます。

uri.scheme => "https"
uri.host => "www.google.co.jp"
uri.port => "443"
uri.path => "/"

この操作によって元のコードの次の行では
NET::HTTPクラスのstartメソッドを利用し、
引数にhostやportを指定して簡単にドメイン取得できています。

ここまでで楽天市場から商品を検索することができました。

しかしまだ検索結果は、11行目の変数resにJSONというデータ形式で代入されています。

JSONをハッシュに変換する

rakuten_api.rb
obj = JSON.parse(res.body)
puts JSON.pretty_generate(obj)

15行目では、JSONクラスのparseメソッドを利用してJSON形式のデータをRubyのハッシュクラスのインスタンスに変換しています。つまり、変数objはハッシュになっている、ということです。あとは、このハッシュから必要な情報を取り出して利用するだけです。

  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

数学クイズの傑作「破れたページの少女」を、Rubyで解いてみた

問題「破れたページの少女」
  1枚だけページが破れた本がある。
  破れていないページ番号を合計すると15000になる。
  破れたページは何ページ目だろうか?

ある時SNSで見かけて知った数学の問題なのですが、なかなかスマートな問題ですよね。良い具合に引っ掛け要素があります。以下リンクでも詳しく解説されているので、こちらも御覧になってみてください。数学が好きな方は最初に是非解いてみてください^^

この問題についての詳しい解説はコチラ

「これ、合計が15000以外だとどんな解が得られるのかな」…とふと思いたち、この問題をrubyで解けるようにしてみました。
(バージョン:ruby 2.5.1)

仕様

破れた本の、ページ番号の合計を入力(上記の問題通り実行する場合は15000を入力)すると
                 ↓
      この本の元々のページ数と、破られたページを求め、出力する

入力例
破かれた本の、ページ数を全て足した合計を入力してください。
合計は? :  ◯◯◯ ←ここに入力
出力例
この本のページ数は、△△△ページです。
破かれたのは、□□ページ目です(同時に、◇◇ページ目も失われています)。
例外処理(解が得られなかった場合)
入力した合計値の条件では、解が存在しませんでした。

という結果が得られるように、コードを書いてみたいと思います。

考え方

前提として与えられているのは、破られた本の、ページ数を全て足した合計値のみ。元々の本の総ページ数も当然分からない状態です。なのでシンプルに、元々の本のページ数が1ページだとしたら → 2ページだとしたら → … という具合に、順に検証してみようかと思います。

以下が、コード内で用意した主な変数です。

  • final_sum … 最初に入力する合計値(問題文では15000)を入れる変数。
  • book … 本の本体。配列形式で、ページを1枚ずつ追加していきます。例)[1, 2, 3, 4, … ]
  • pages_sum … 破れていない場合の、bookのページ番号の合計値を入れる変数。
  • torn_page_list … 破くことが可能なページ全てのパターンを配列形式で追加していきます。 例)[[1, 2],[3, 4],[5, 6], … ]

例えば3ページ目を破ると、その裏の4ページ目も同時に失うことになります。そのためtorn_page_listには[x-1, x]というふうに格納していきます。且つ、[奇数, 偶数]の形でなければいけません。左右逆の[偶数, 奇数]だと、見開きページってことになってしまう(1枚だけ破くという前提が崩れてしまいNGとなる)為です。

bookにページを1枚ずつ追加していきながら…
本のページ番号合計 - 破いて無くなるページ合計 == 最初に入力した数値(問題文では15000)
つまり、
pages_sum - torn_page_list[??].sum == final_sum
を満たすものを探し当てれば、ゴールです!

作成したコード(※まだ未完成)

sample.rb(コメント付き)
puts "破かれた本の、ページ数を全て足した合計を入力してください。"
print "合計は? :"
final_sum = gets.to_i

# ↓ bookには、1ページずつ配列で格納していきます。 例) [1, 2, 3, .. ]
book = []

# ↓ pages_sumには、本のページ数を足していきます。
pages_sum = 0

# ↓ torn_page_listには、破ることが可能な全てのページを、パターンとして配列で格納していきます。 例) [[1, 2], [3,4], .. ]
torn_page_list = []

# ↓ インデックス値を用意
i = 1
# ↓ 以下のwhile文によって、条件を満たすケースが無いかを探り、無い場合はページを一枚ずつ追加し再検証する。
# ↓ なぜ final_sum + 1 なのかというと、「これ以上やっても、もう条件を満たすケースは無いよ!」というページ上限が、 final_sum + 1 の為。
while i <= final_sum + 1 do

  # ↓ 破るページを全ケース試して、解の条件を満たすケースが無いかを検証。
  lost_page_is = torn_page_list.find { |lost| pages_sum - lost.sum == final_sum }

  # ↓ 条件を満たすケースが見つかったら、while文をbreak(強制終了)する!
  break if lost_page_is != nil

  # ↓ 満たさなかった(上記でbreakしなかった)場合は、ページを増やし再検証する。
  book << i
  pages_sum += i
  if book.last.even?
    torn_page_list << [i - 1, i]
  end
  i += 1
end

# 結果を出力
if lost_page_is != nil
  puts "この本のページ数は、#{book.length}ページです。"
  puts "破かれたのは、#{lost_page_is[0]}ページ目です(同時に、#{lost_page_is[1]}ページ目も失われています)。"
else
  puts "入力した合計値の条件では、解が存在しませんでした。"
end

↓ コメントアウト無しバージョン ↓

sample.rb(コメントアウト無しバージョン)
puts "破かれた本の、ページ数を全て足した合計を入力してください。"
print "合計は? :"
final_sum = gets.to_i

book = []
pages_sum = 0
torn_page_list = []

i = 1
while i <= final_sum + 1 do
  lost_page_is = torn_page_list.find { |lost| pages_sum - lost.sum == final_sum }
  break if lost_page_is != nil

  book << i
  pages_sum += i
  if book.last.even?
    torn_page_list << [i - 1, i]
  end
  i += 1
end

if lost_page_is != nil
  puts "この本のページ数は、#{book.length}ページです。"
  puts "破かれたのは、#{lost_page_is[0]}ページ目です(同時に、#{lost_page_is[1]}ページ目も失われています)。"
else
  puts "入力した合計値の条件では、解が存在しませんでした。"
end

結果

以下の通り、期待する結果を得ることが出来ました。

結果1
破かれた本の、ページ数を全て足した合計を入力してください。
合計は? : 15000
             ↓
この本のページ数は、173ページです。
破かれたのは、25ページ目です(同時に、26ページ目も失われています)。
結果2
破かれた本の、ページ数を全て足した合計を入力してください。
合計は? : 3
             ↓
この本のページ数は、3ページです。
破かれたのは、1ページ目です(同時に、2ページ目も失われています)。
結果3
破かれた本の、ページ数を全て足した合計を入力してください。
合計は? : 12345
             ↓
入力した合計値の条件では、解が存在しませんでした。

このコードの課題

上記で無事に解を得ることは出来たものの、解が存在しなかった場合の処理速度に課題を感じました。

アルゴリズムが持つべき性質…
有限性:アルゴリズムは終了しなければならない
明確性:アルゴリズムは実行する際の条件によって変動しないようにしなけらばならない
理解性:アルゴリズムはわかりやすいものでなければならない
高速性:アルゴリズムの処理は効率的でなければならない ←これ

上記が発生している理由は、while i <= final_sum + 1 do 〜 endの部分。あまりにも無駄な回数の繰り返し処理をさせていました。「これ以上繰り返しても無駄だよ」の境界値は、もっと厳密に定義しないといけません。

そこで考えたのが以下の表のイメージです。

本のページ数 破いた際の最小合計値(最後のページを破いた場合) 破いた際の最大合計値(1ページ目を破いた場合)
1ページ - -
2ページ 0 0
3ページ 1 3
4ページ 3 7
5ページ 6 12
nページ book.sum-(book[n-1]+book[n]) book.sum-(book[0]+book[1])

"本を破いた際に得られる合計値"には、上記の通り得られる数値の範囲が決まっています(4ページある本なら、破いた後の合計値の取りうる範囲は、表の通り3〜7の間となる)。なので、その範囲を超えてまで、繰り返し処理をする必要は無さそうだ…ということが分かります。

改善

というわけで、while文の条件を以下のように書き換えようと思います。

sample.rb
# while i <= final_sum + 1 do 〜 end  ←これでは無駄に繰り返してしまうので、以下に変更
while pages_sum <= final_sum + (2 * i) - 1 do

上記のようにすると無駄な繰り返し処理がなくなり、例外処理も高速で処理出来るようになりました(元々がひどかっただけですがw)。

ちなみに別コードで検証してみた結果、どのような場合でも解は必ず1つのみになるようです。当たり前と言えば当たり前かもしれませんが、数学得意ではないので証明は出来ず…。解は一つという事実から、今回のようにfindメソッドを用いることが出来ます(以下13行目)。

完成コード

sample.rb(完成バージョン)
puts "破かれた本の、ページ数を全て足した合計を入力してください。"
print "合計は? :"
final_sum = gets.to_i

book = []
pages_sum = 0
torn_page_list = []

i = 1
# -- 修正箇所 --
while pages_sum <= final_sum + (2 * i) - 1 do
# --  -- --  --
  lost_page_is = torn_page_list.find { |lost| pages_sum - lost.sum == final_sum }
  break if lost_page_is != nil

  book << i
  pages_sum += i
  if book.last.even?
    torn_page_list << [i - 1, i]
  end
  i += 1
end

if lost_page_is != nil
  puts "この本のページ数は、#{book.length}ページです。"
  puts "破かれたのは、#{lost_page_is[0]}ページ目です(同時に、#{lost_page_is[1]}ページ目も失われています)。"
else
  puts "入力した合計値の条件では、解が存在しませんでした。"
end

以上、"「破れたページの少女」を、Rubyで解いてみた"でした。ご覧いただきありがとうございました!

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

『TECH CAMP』2ヶ月通ってできるようになったこと

はじめに

TECH CAMPに通って2ヶ月でできるようになったことをなんとなくまとめてみようと思います!

言語としてはRubyとjsフレームワークとライブラリでRailsとjqueryを使えばクオリティは低いがtwitter、インスタ、メルカリのようなwebサイトは作れるようになる(本物には遠く及ばない)

プログラミング学習サイトpaizaのBランク問題(選りすぐりの簡単な物)が2,3問解けるようになる。
文字列を複数形にみたいな問題はとっっても簡単なのでBランクまであげたい方にはおすすめです。

3

webサイトの模写だったらできるようになる(もちろんクオリティは低い)
だが、デザインのセンスが身につくわけではないので個人アプリを1からデザインしてUIに優れたサイトを作るのは厳しい。

デザインの才能が高い方は例外です。まーでもほとんどの方が自分で「これはいい!!最高!!」っていうUIを備えたサイトを作るのは厳しいと思います。

4

実装方法がネット上に調べて出てくるものであればある程度マネができるようになる。(簡単なゲームなども)

まとめ

  • 大前提としてプログラミングスクールに通ったからといってプログラマーの実務で使うような技術はつかない。

  • スクレイピングやクローラーの技術は全くつかないしテストコードすらまともに書けないのでこのままではプログラマーとは到底言えない。

  • 答えに近いものがネット上にない場合は自分じゃ作れない。

  • 0から1を作るなんて言えるほど技術はない。

僕自身プログラミングスクールに通えばもっと実践的な技術がつくと絵空事を浮かべてましたが実際短期間で得れる技術なんてこんなもんです。むしろすごく成長させてくれたと思います:bow_tone3:

僕は燃えています。自分の未知の世界ってすごい楽しい。今はペーペーだけど5年後、10年後に優秀なプログラマーになれるようにプログラムと日々遊んで行こうと思います。

OSSにプルリクエスト出してマージされる日がきたらいいな:relaxed:
その前に英語読めるようにならないとまずいか笑

最後に

TECH CAMPがダメって言いたいわけじゃないです!チーム開発や勉強に励みやすい環境(一週間ちょっとくらいしか行ってないが笑)そして学習に一番大切な「きっかけ」を与えてくれる最高のスクールです!!!!

それを踏まえた上でそんなに素晴らしいスクールに通っても自分の思い浮かぶキラキラエンジニアになれるわけではないよというのを伝えたかったです!

キャラでもなくこんな記事書くようになるなんてプログラミングめっちゃ好きなんだなって感じて嬉しいですね:joy:
僕の友達がこんな記事見たらめちゃめちゃ笑われるんだろうな笑

こんなくだらない記事を最後までご閲覧いただきありがとうございました

  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

メソッド定義の中におけるselfについて

selfがよくわからんので学んだことのメモを。

※※ここではメソッド定義の中におけるselfの話をさします
※※クラスメソッドのselfなどは無視します

selfはメソッドの中で使えるオブジェクト。
selfはそのメソッドをうけとっているインスタンス自身を指す。

class User
attr_accessor :name
 # setter: name=(value)
 # getter: name

def initialize(name)
 @name=name
end    

def sayhi
 # self
 puts "hi.#{@name}"
end

end

user=User.new("bob")
user.sayhi # userがselfにあたり、レシーバーと呼ぶこともある

上記でコメントにしているselfはUser.newによって生成されたオブジェクトbobを指す。

続いてselfを実際につかう場合、、

class User
attr_accessor :name
 # setter: name=(value)
 # getter: name

def initialize(name)
 @name=name
end    

def sayhi
 # self
 puts "hi.#{@name}"
 # 追記箇所
 puts "hi.#{self.name}"
end

end

user=User.new("bob")
user.sayhi # userがselfにあたり、レシーバーと呼ぶこともある

コンソールで吐き出すと

ec2-user:~/environment/ruby_lessons $ ruby hello.rb 
hi.bob
hi.bob

と、なる。

selfが使えるのは
「getterが設定されてるからself.nameにすると、結果としてインスタンス変数が取得できて@nameがかえってくる」

ということらしい。

なのでためしにattr_accessorをコメントアウトしてみると

class User
#  attr_accessor :name コメントアウトに
 # setter: name=(value)
 # getter: name

def initialize(name)
 @name=name
end    

def sayhi
 # self
 # self.name -> @name(getterが設定されてるから)
 puts "hi.#{@name}"
 puts "hi.#{self.name}"
 # puts "hi.#{name}" # ややこしくならない場合は省略できる
end

end

user=User.new("bob")
user.sayhi

コンソールで吐き出すと

ec2-user:~/environment/ruby_lessons $ ruby hello.rb 
hi.bob
Traceback (most recent call last):
        1: from hello.rb:118:in `<main>'
hello.rb:111:in `sayhi': undefined method `name' for #<User:0x000000000256fef8 @name="bob"> (NoMethodError)
ec2-user:~/environment/ruby_lessons $ 

と、なselfのほうがエラーとなる。

要するに、selfはattr_accessor(厳密にはgetter)とセットってことですかね。

同じ事例で分かりやすい説明を発見しました。

こちらです

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

gemのancestoryを使用した多層階構造の実装

はじめに

今回はフリマアプリでよく見かける、カテゴリの多層階構造についての実装手順を紹介していきます。

完成図

こんな感じのやつ。
Image from Gyazo

やりたいこと

カテゴリを親、子、孫の三階層に分けて登録、表示する。

1.モデルの準備

今回実装したいこととして第一に商品とカテゴリを紐付けたいので、categoryとitemのモデルを作成します。

$ rails g model category 
$ rails g model item 

2.ancestoryの導入

Gemfileにancestoryを導入します。

gem 'ancestry'

ターミナルで下記コマンドを実行しよう。

$ bundle install
$ rails g migration add_ancestry_to_category ancestry:string:index
$ rails db:migrate

ancestoryを使うことで多対多の関係が1対多の関係になりDB設計がシンプルになります。
category.rbにhas_ancestoryを入れてあげましょう。

category.rb
class Category < ApplicationRecord
 has_many :items
 has_ancestry
end
item.rb
class Item < ApplicationRecord
 belongs_to :category
end

3.データの導入

seeds.rbにカテゴリのレコードを導入していきます。

seeds.rb
lady = Category.create(name: "レディース")
lady_1 = lady.children.create(name: "トップス")
lady_1.children.create([{name: "Tシャツ/カットソー(半袖/袖なし)"},{name: "Tシャツ/カットソー(七分/長袖)"},{name: "シャツ/ブラウス(半袖/袖なし)"},{name: "シャツ/ブラウス(七分/長袖)"},{name: "ポロシャツ"},{name: "キャミソール"},{name: "タンクトップ"},{name: "ホルターネック"},{name: "ニット/セーター"},{name: "チュニック"},{name: "カーディガン/ボレロ"},{name: "アンサンブル"},{name: "ベスト/ジレ"},{name: "パーカー"},{name: "トレーナー/スウェット"},{name: "ベアトップ/チューブトップ"},{name: "ジャージ"},{name: "その他"}])

ancestryを入れるとchildrenと記述することで直前の変数の子要素として扱うことができます。
細かく説明するとまず、以下の記述で親カテゴリであるレディースが登録されます。

seeds.rb
lady = Category.create(name: "レディース")

次に、以下の記述で子カテゴリであるトップスが登録されます。

seeds.rb
lady_1 = lady.children.create(name: "トップス")

そして、以下の記述で子カテゴリの孫カテゴリ群が登録されます。

seeds.rb
lady_1.children.create([{name: "Tシャツ/カットソー(半袖/袖なし)"},{name: "Tシャツ/カットソー(七分/長袖)"},{name: "シャツ/ブラウス(半袖/袖なし)"},{name: "シャツ/ブラウス(七分/長袖)"},{name: "ポロシャツ"},{name: "キャミソール"},{name: "タンクトップ"},{name: "ホルターネック"},{name: "ニット/セーター"},{name: "チュニック"},{name: "カーディガン/ボレロ"},{name: "アンサンブル"},{name: "ベスト/ジレ"},{name: "パーカー"},{name: "トレーナー/スウェット"},{name: "ベアトップ/チューブトップ"},{name: "ジャージ"},{name: "その他"}])

seeds.rb内にレコードを記述し終わったら、ターミナルで以下のコマンドを実行し、DBに反映させましょう。

$ rails db:seed

DBではこのように登録されます。

Image from Gyazo

まとめ

ancestoryを使用すればアソシエーションの関係性もシンプルになるのでかなりオススメです!!!

参考記事

https://github.com/stefankroes/ancestry

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

Rails6環境構築時に発生した"webpacker"と"yarn"の未導入が原因のエラーとその解決方法

はじめに 

rails tutorial(第6.0版)を参考にrails6の環境を構築したところ、rails server立上げの際にエラーが発生しました。

原因は"webpacker"と"yarn"の未導入でした。

今後のために、エラー内容と"webpacker"と"yarn"の導入方法を備忘録として残します。

参考記事

Rails6 開発時につまづきそうな webpacker, yarn 関係のエラーと解決方法
Rails6 Webpackerでエラーが出た
Ubuntu18.04LTSにlinuxbrew(linux版homebrew)を入れる

開発環境

Cloud9 (ubuntu server)
Ruby 2.6.3
rails 6.0.0

エラー発生までの経緯

rails tutorial(第6.0版)第1章を参考にrails6の開発環境を構築開始。
1.3.1までチュートリアル通り実施し、rails severコマンドを入力すると、

{略}
rescue in load': Webpacker configuration file not found /home/ubuntu/environment/hello_app/config/webpacker.yml. Please run rails webpacker:install \
Error: No such file or directory @ rb_sysopen - /home/ubuntu/environment/hello_app/config/webpacker.yml (RuntimeError)

上記エラーが表示されました。

エラーの内容

/hello_app/config/ 内に"webpacker.yml"が存在しないから、webpackerをインストールしなさい。・・・と理解しました。

webpackerとyarnのインストール

webpacker導入コマンド

$ rails webpacker:install 
Yarn not installed. Please download and install Yarn from https://yarnpkg.com/lang/en/docs/install/

yarnがないと返事が来た場合。

yarn導入コマンド

$ brew install yarn
Command 'brew' not found, but can be installed with:

上記の返事が来た場合。

$ sudo apt install linuxbrew-wrapper

yarnとwebpackerを再度導入する。

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

attr_accessorメソッドについて

よくわかってないattr_accessorに関して学んだことのメモ。

以下、今回のサンプルコードです。
※attr_accessorはコメントにしてます

class User
# attr_accessor :name
 # setter: name=(value)
 # getter: name

def initialize(name)
 @name=name
end    

def sayhi
 puts "hi.#{@name}"
end

end

user=User.new("bob")
user.sayhi

まず、自分で定義するインスタンス変数(@name)に関しては外部からアクセスすることはできない。
ここでいう外部と内部ですが、アクセスできる範囲としては
サンプルコードでいうと、

class User
# attr_accessor :name
 # setter: name=(value)
 # getter: name

def initialize(name)
 @name=name
end    

def sayhi
 puts "hi.#{@name}"
end

end

ということになるかと思います。

そして、newでインスタンスを作成し、sayhiメソッドを呼び出した場合、
sayhiメソッドが該当エリア内なので問題なく使えている、ということになります。

上記サンプルコードをコンソールで吐き出すと

ec2-user:~/environment/ruby_lessons $ ruby hello.rb 
hi.bob

となる。

続いてサンプルコードに、「外部」からインスタンス変数にアクセスしようとするコードを追記。

class User
# attr_accessor :name
 # setter: name=(value)
 # getter: name

def initialize(name)
 @name=name
end    

def sayhi
 puts "hi.#{@name}"
end

end

user=User.new("bob")
user.sayhi

# 追記箇所(attr_accessorメソッドはコメントのママ)
user.name ="bob.jr"
p user.name

これをコンソールで吐き出すと、、、

ec2-user:~/environment/ruby_lessons $ ruby hello.rb 
hi.bob
Traceback (most recent call last):
hello.rb:89:in `<main>': undefined method `name=' for #<User:0x0000000002973f40 @name="bob"> (NoMethodError)

エラーがでます。
ただ、これができるようになるattr_accessorメソッドのコメントをとると、、、

class User
attr_accessor :name
 # setter: name=(value)
 # getter: name

def initialize(name)
 @name=name
end    

def sayhi
 puts "hi.#{@name}"
end

end

user=User.new("bob")
user.sayhi

# 追記箇所(attr_accessorメソッドはコメントをはずす)
user.name ="bob.jr"
p user.name

こうなり、コンソールで吐き出すと

ec2-user:~/environment/ruby_lessons $ ruby hello.rb 
hi.bob
"bob.jr"

見事に書き換え&表示に成功。

念のため、書き換え前の出力も追加してみる。

class User
attr_accessor :name
 # setter: name=(value)
 # getter: name

def initialize(name)
 @name=name
end    

def sayhi
 puts "hi.#{@name}"
end

end

user=User.new("bob")
user.sayhi

# 追記箇所
p user.name

user.name ="bob.jr"
p user.name

すると

ec2-user:~/environment/ruby_lessons $ ruby hello.rb 
hi.bob
"bob"
"bob.jr"

いい感じ。

上書きされた状態でメソッドを呼び出すコードを追加。

class User
attr_accessor :name
 # setter: name=(value)
 # getter: name

def initialize(name)
 @name=name
end    

def sayhi
 puts "hi.#{@name}"
end

end

user=User.new("bob")
user.sayhi

p user.name
user.name ="bob.jr"
p user.name
# 追加部分
user.sayhi

すると

ec2-user:~/environment/ruby_lessons $ ruby hello.rb 
hi.bob
"bob"
"bob.jr"
hi.bob.jr

上書きされた形でメソッドを呼び出せた!

こんな感じで「外部」からインスタンス変数の値にアクセスしたい場合に
attr_accessorメソッドを用いるとよい、らしい。

  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

【Ruby】引数のデフォルト値設定の書き方について

はじめに

デフォルト値ってこんな感じだったよな、、

class ApiSuggest
  def self.suggest(keyword, suggest_max_count = 99)#ここ
    Product::Suggest.where('keyword like ?', "#{keyword}%").limit(suggest_max_count).pluck(:keyword)
  end
end

ってすると。
rubocopから軽く怒られた。のでその対処について。

怒られ方

 Do not use default positional arguments. Use keyword arguments or an options hash instead.Airbnb/OptArgParameters(RuboCop)

 デフォルトの位置引数は使用しないでください。代わりにキーワード引数やオプションハッシュを使用してください。

ここで’キーワード’が出てきてなんとなく’:’を使うのかなと思った。
更に、上の英語で検索する。
と、こちらがヒットし、大変参考になった。

こういった細かいところも、自然と正しくかけるように継続してrubocopの指摘に注目していきたい。

終わりに。

最後まで読んで頂きありがとうございます:bow_tone1:
転職の為、未経験の状態からRailsを学習しております。正しい知識を着実に身に着け、実力のあるエンジニアになりたいと考えています。継続して投稿していく中で、その為のインプットも必然的に増え、成長に繋がるかと考えています。
今現在、初心者だからといって言い訳はできないですが、投稿の内容に間違っているところや、付け加えるべきところが多々あるかと思いますので、ご指摘頂けると幸いです。この記事を読んで下さりありがとうございました。

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

daw

dwa

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