20190704のRubyに関する記事は14件です。

関連する ActiveModel::Serializer を切り替える

はじめに

  • Associations において利用する Serializer を切り替えました
  • AMS で該当する機能は見つからなかったので素朴に実現しました

目次

  • はじめに
  • TL;DR
  • 環境
  • 構成
    • Model
    • API
  • やりたいこと
  • 実現方法
    • Controller
    • Serializer
  • まとめ
  • 参考

TL;DR

  • Rendering の際に options を渡す
reservations_controller.rb
  render json: reservation,
          include: '**', restaurant_type: :detail
  • Serializer の initialize で options を受け取る
reservation_serializer.rb
  def initialize(object, options = {})
    super
    @restaurant_type = options[:restaurant_type]
  • options の値によって 関連先で利用する Serializer を切り替える
reservation_serializer.rb
  attributes :id, :restaurant

  def restaurant
    serializer = case @restaurant_type
                  when :base then RestaurantSerializer
                  when :detail then RestaurantDetailSerializer
                  else RestaurantSerializer
                  end

    serializer.new(object.restaurant, @options)

環境

- ruby: 2.6.2
- rails: 5.2.3
- active_model_serializers: 0.10.9 

構成

Model

app/models/restaurant.rb
# == Schema Information
#
# Table name: restaurants
#
#  id                   :bigint(8)        not null, primary key
#  name                 :string           not null
#  phone                :string           not null
#  address              :string           not null

class Restaurant < ApplicationRecord
  has_many :reservations
app/models/reservation.rb
# == Schema Information
#
# Table name: reservations
#
#  id                   :bigint(8)        not null, primary key
#  restaurant_id        :bigint(8)        not null

class Reservation < ApplicationRecord
  belongs_to :restaurant

API

reservations_list.json
{
  "reservations": [
    {
      "id": 42,
      "restaurant": {
        "id": 42,
        "name": "Domino's Pizza"
      }
    }
  ]
}
reservation_detail.json
{
  "reservation": {
    "id": 42,
    "restaurant": {
      "id": 42,
      "name": "Domino's Pizza",
      "phone": "03-1234-5678",
      "address": "東京都XXXXXX"
    }
  }
}

やりたいこと

  • Reservations List API では restaurant の id, name のみを含める
  • Reservation Detail API では restaurant の id, name, phone, address を含める

実現方法

Controller

  • restaurant_type を Serializers に渡す
reservations_controller.rb
module API
  class ReservationsController < API::ApplicationController
    def index
      reservations = Reservation.all
      render json: reservations,
              include: '**', restaurant_type: :base
    end

    def show
      reservation = Reservation.find(params[:id])
      render json: reservation,
              include: '**', restaurant_type: :detail
    end
  end
end

Serializers

  • initialize で options[:restaurant_type] を受け取る
  • options[:restaurant_type] の値によって, Serializer を切り替える
  • @options を 渡すことで, options や scope を 引き継ぐ
reservation_serializer.rb
class ReservationSerializer < ActiveModel::Serializer
  attributes :id, :restaurant

  def initialize(object, options = {})
    super
    @options = options
    @restaurant_type = options[:restaurant_type]
  end

  def restaurant
    serializer = case @restaurant_type
                  when :base then RestaurantSerializer
                  when :detail then RestaurantDetailSerializer
                  else RestaurantSerializer
                  end

    serializer.new(object.restaurant, @options)
  end
end
restaurant_serializer.rb
class RestaurantSerializer < ActiveModel::Serializer
  attributes :id, :name
end
restaurant_detail_serializer.rb
class RestaurantDetailSerializer < RestaurantSerializer
  attributes :address, :phone
end

まとめ

  • 今回は牧歌的な方法で切り替えました
  • 良い方法があれば教えてください :bow:

参考

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

ステップバイステップで学ぶCapistrano 3によるRails 5.2 + puma + nginxのデプロイ

このドキュメントを書いた理由

qiita内も含め、同じような事例は数多く公開されているが、残念ながらイチから学ぶ上ではほとんど参考にならなかった。

なぜなら重要なのは、最終的な作業内容や設定ファイルの中身ではないからだ。それらはソフトウェアの構成やバージョンが変われば容易に変化する。

本当に重要なのは、デプロイ作業がどんなステップで成り立っていて、各ステップで何を目的とし、そのために最低限どんな設定が必要なのか、理解することだ。

そういうわけで、自分で考えて調べて実行したので、結果をまとめることにした。

方針

以下の6ステップに分割して作業を進めた。概ね、各ステップがCapistranoの一プラグインに対応している。つまり一つずつプラグインを追加していくイメージである。

  1. ssh/gitによるファイルの配置
  2. rbenvの動作確認
  3. bundlerによるgemインストール
  4. Railsの設定
  5. pumaの起動
  6. nginx と pumaの連携

可能な限り、次の2つの指針をもって進めた。

  • デフォルト値が妥当な場合は、設定を書かない
  • 1回きりとわかっている手順は、手動で行う

デフォルト値が妥当な場合は、設定を書かない

そのステップで本当に必要な設定が、一目でわかるようにするためである。

もちろん、人によって構成によって必要な設定は異なる。異なるからこそ、他人の設定ファイルをコピペするのではなく、公式ドキュメントを読んで一つずつ妥当性を判断すべきである。全部まとめてやるのは大変だが、一度に1プラグインなら難しくない。

1回きりとわかっている手順は手動で行う

Capistranoは独自のタスクを定義できるから、ついカッコよく自動化してみたくなる。しかしそれは「早すぎる最適化」というものだ。結局今回は、カスタムタスクは作らなかった。

環境情報とドキュメント化の範囲

サーバOS: Debian 9 stretch
クライアントOS: Ubuntu 18.04.2 LTS on WSL 1

  • Ruby 2.6.3 on rbenv
  • Rails 5.2.3
  • puma 3.12.1
  • Capistrano 3.11.0
  • SQLite

各ソフトウェアのインストールには触れない。ユーザを追加したりSSH鍵をセットアップしたりといった手順にも触れない。またプラグインを追加した後のbundle installなど、自明な手順はしばしば省略する。

使用するサーバはweb/app/dbを兼ねる1台のみ、productionオンリー、しかもDBはsqlite。オモチャのようなアプリであるが、手順を学ぶ題材にはちょうど良かった。

デプロイ手順の説明

前置きが長くなった。具体的な作業に入る。Capstranoのインストールから。

Gemfile
# Use Capistrano for deployment
group :development do
  gem 'capistrano'
end

bundle installの後、cap install を実行して設定ファイルを作る。今回はproductionの分しか作らない。

bundle exec cap install STAGES=production

Capistranoの設定は、Railsアプリと共通のGemfileconfig/以下に書き込んでいく。しかし事実上、「独立したデプロイ用のプログラムを作る」と考えた方がいい。

例えばCapistrano関係の設定を変更しても、その都度 git commit / push する必要はない。Capistranoは実行時のRailsアプリとは無関係だ。

1. ssh/gitによるファイルの配置

目的

  • 素のCapistranoで、ssh/gitを用いたファイル配置のみを行う。

前提条件

  • サーバにSSH接続できること
  • 接続したユーザで配置先のディレクトリに書き込めること
  • サーバからGitリポジトリに接続できること

方針通り、最低限の設定のみ記す。必要なのはまずアプリケーションの名前と、gitリポジトリのURLと、サーバ上での配置先。

config/deploy.rb
set :application, 'myapp'
set :repo_url, 'git@github.com:myapp.git'
set :deploy_to, "/var/www/apps/myapp"

それからサーバのアドレスとユーザ名。Capistranoは実行ユーザの ~/.ssh/config を(完全ではないが)認識するので、sshコマンドで普通に接続できるなら、改めてSSH関連の設定はしなくていい。

config/deploy/production.rb
server "myserver", user: "tkyk", roles: %w{app db web}

デプロイ実行。

bundle exec cap production deploy

指定した配置先にCapistranoが管理するディレクトリ構造が作られ、gitからファイルがチェックアウトされる。サーバにログインして確認しておこう。

2. rbenvの動作確認

目的

  • 使用したいバージョンのRubyがrbenv経由で使える(Capistranoが認識する)ことを確認する。

前提条件

  • rbenvおよび使用するバージョンのRubyがインストールされていること

capistrano-rbenvプラグインを追加・有効化する。

Gemfile
group :development do
  #...
  gem 'capistrano-rbenv'
end
Capfile
require "capistrano/rbenv"

今回、rbenvはシステムレベルで(/usr/local/rbenv に)インストールしており、Rubyのバージョンは.ruby-version で指定する。この場合、設定は次のようになる。

config/deploy.rb
set :rbenv_type, :system
set :rbenv_ruby, File.read('.ruby-version').strip

rbenvの構成違いは、

  • rbenv_type
  • rbenv_ruby
  • rbenv_prefix

という3つの設定を組み合わせて対応する。たとえばrbenvをユーザレベルでインストールして、かつ.ruby-versionを使用しないなら、次のような設定になるはず(動作確認はしてない)。

config/deploy.rb
set :rbenv_type, :user
set :rbenv_ruby, '2.6.3'
set :rbenv_prefix, "RBENV_ROOT=#{fetch(:rbenv_path)} RBENV_VERSION=#{fetch(:rbenv_ruby)} #{fetch(:rbenv_path)}/bin/rbenv exec"

deployの実行。

bundle exec cap production deploy

この段階では実質的に何も実行しないが、設定を間違えたりして必要なバージョンが見つからない場合、警告が出てデプロイが中止される。

3. bundlerによるgemのインストール

目的

  • Rails他、アプリケーションが必要とするgemをbundlerでインストールする

前提条件

  • 使用するバージョンのRubyにおいてbundlerがインストールされていること

capistrano-bundlerプラグインを追加。

Gemfile
group :development do
  #...
  gem 'capistrano-bundler'
end
Capfile
require "capistrano/bundler

アプリケーションで使用するgemはリリース間で共有したいので、プラグインの標準の規約に従い、.bundle をlinked_dirsに追加する。ここに追加したディレクトリは shared ディレクトリ下に配置され、各リリースからはシンボリックリンクで参照される。

config/deploy.rb
append :linked_dirs, '.bundle'

他の設定はデフォルト値で問題なかったが、並列数だけはサーバスペックに合わせて設定すると良いと思う。デフォルトは4。

config/deploy.rb
set :bundle_jobs, 2

デプロイ実行。bundle installが走るので初回はかなり時間がかかるだろう。

bundle exec cap production deploy

4. Railsの設定

このステップはやや複雑で、おそらく構成による差異も大きいはず。

目的

  • Railsがリリース間で共有するリソースを定義する
  • データベースのmigrationを行う
  • assetコンパイルを行う

前提条件

  • 特になし

まずはcapistrano-railsプラグインを追加し、migrationおよびassetコンパイルを有効化する。

Gemfile
group :development do
  #...
  gem 'capistrano-rails'
end
Capfile
require "capistrano/rails/assets"
require "capistrano/rails/migrations"

第1の目的。railsがリリース間で共有するリソースを定義する。次のファイル・ディレクトリは、基本的にどんなRailsアプリでも該当するだろう。

config/deploy.rb
append :linked_files, "config/master.key"
append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets"

他にも tmp/storage とか public/uploads とか、アプリケーションによって必要なものを追加する。

こうしたリソースのうち、ディレクトリは自動で作られるが、ファイルはそうではない。よってconfig/master.key は予めサーバのshared以下にコピーしておこう。

scp config/master.key myserver:/var/www/apps/myapp/shared/config/

第2の目的。データベースのmigration。

独立したデータベースサーバがあるなら、database.ymlは普通、リポジトリに追加しないはず。その場合はlinked_filesにdatabase.ymlを追加し、何らかの手段で shared/config/database.yml を設置する。

一方、今回の私のアプリではsqliteを使うため、データベースファイル *.sqlite3 自体をリリース間で共有したい。そのため、database.ymlを次のように書き換え、1段階ディレクトリ階層を深くして……

config/database.yml
production:
  <<: *default
  database: db/production/production.sqlite3

このディレクトリを共有リソースとした(database.ymlはリポジトリ内で管理する)。

config/deploy.rb
append :linked_dirs, 'db/production'

第3の目的、assetコンパイルについては、特に設定する箇所がなかったのでデフォルトのまま。

デプロイ実行。migration, assetコンパイルが実行され、諸々シンボリックが作られる。

bundle exec cap production deploy

5. pumaの起動

目的

  • pumaを起動できるようにする
  • pumaプロセスのステータスをローカルから確認できるようにする
  • デプロイ完了時にpumaプロセスを再起動させる

前提条件

  • 特になし

capistrano-pumaプラグインのインストールと有効化。もちろんpuma自体まだインストールしていないなら、それも。

Gemfile
# Rails 5.2.3でrails newしたら最初から入っていた
gem 'puma', '~> 3.11'

group :development do
  #...
  gem 'capistrano3-puma'
end
Capfile
require 'capistrano/puma'
install_plugin Capistrano::Puma

とりあえず動かしてみたいなら、プラグインに設定ファイルを自動生成してもらうのが簡単である。

bundle exec cap production puma:config

このコマンドで、サーバ上の shared/puma.rb に設定ファイルが作られる。

(すでにサーバ上にpuma gemをインストール済みなら)以下のようなコマンドで、起動や終了、ステータスを確認できる。

bundle exec cap puma:start
bundle exec cap puma:stop
bundle exec cap puma:status

デプロイ実行。完了時に再起動されることを確認しよう。

bundle exec cap production deploy

6. nginx と puma の連携

目的

  • nginxからUNIXドメインソケットを通してpumaにアクセスできるようにする

前提条件

  • nginxがインストールされていること

capistrano-pumaのnginx用プラグインを有効化。

config/deploy.rb
install_plugin Capistrano::Puma::Nginx

これまた、とりあえず動かしたいなら、設定ファイルは自動生成すると良い。

bundle exec cap production puma:nginx_config

これで /etc/nginx/sites-available/myapp_pruduction のようなパスに設定ファイルが作られ、sites-enabled からはシンボリックリンクが作られる(当然、ディレクトリへの書き込み権限が必要。もし書き込み権限を与えたくないなら rails g capistrano:nginx_puma:config でローカルに設定ファイルを生成できる)。

あとは必要に応じて設定ファイルを修整、例えばnginxのデフォルトサイト(sites-enabled/default)を削除したりして、nginxをリロードすれば完了。

sudo nginx -t && sudo systemctl reload nginx

前ステップでpumaが起動しているなら再デプロイは不要なはず。httpアクセスして動作確認しよう。

終わりに

pumaプラグインが設定ファイルを自動生成してくれるおかげで、動作させるだけならとても簡単だった。もちろん実運用においては、それら設定ファイルの管理ポリシーが必要だが。

一連の過程において唯一引っかかったのは、以下の問題だった。

capistrano3-pumaのアップデートで起きたバグ解決

結局これも、デフォルトが妥当なら上書きしない、という方針で回避できた。

(とはいえcapistrano-rbenvの場合、デフォルト値が何なのか公式ドキュメントに書いてないのが困りものである。例示されている設定はデフォルト値ではない)

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

初めてのRuby3 〜クラスとインスタンス、命名規則〜

クラスとインスタンス

:sunny:クラス : オブジェクトの種類を表すもの、型(クッキーの型)
         eg:)Arrayオブジェクト、stringオブジェクト

:sunny:インスタンス : クラスから作られたもの(クッキー:cookie:
         eg:)[1,2,3], "name"

newメソッド

:star:new : クラスからインスタンスオブジェクトを作るメソッド(クッキーの型でクッキー:cookie:を作る)
eg:) Array.new: []
String.new: ""
:writing_hand_tone1:メソッド=お仕事)

:loudspeaker:インスタンスオブジェクトは直接作ることも可能
eg:) [1,2,3] , "name"

クラスの作り方

class クラス名(必ず一文字目は大文字)
 クラスの定義
end
作ったクラスをnewする
class Recipe
end

recipe = Recipe.new
classにメソッドを作る

:necktie:メソッドを作ってRecipeクラスが仕事をできるようにしてあげる

class Recipe
 def title
   "cheese cake"
 end
end

recipe = Recipe.new
recipe.title => "cheese cake"

タイトルを返せるようになった:point_up_tone1:

インスタンス変数

:star:インスタンス変数 : インスタンスがある間ずっと使える変数。頭に@をつける。
1、インスタンスオブジェクト(クッキー:cookie:)ごとにインスタンス変数を持っている。
2、オブジェクトの外から直接アクセスできない。アクセスするときはそのオブジェクトのメソッドを通す。

:helmet_with_cross:変数のスコープ(有効範囲)
あるメゾット内で作成した変数はそのメソッドの中でしか有効でない。
この有効範囲を広げるためにインスタンス変数を使う。

❶
class Recipe

 def title = (t)
   @title = t
 end

 def title = (t)
   @title 
 end
end
recipe = Recipe.new
recipe.title = "cheese cake"
p recipe title => "cheese cake"

❶はよく使われるのでattr_accessorという書き方が用意されている
書き方 : attr_accessor インスタンス変数のシンボル

書き換えるとこうなる↓

❶
class Recipe
 attr_accessor :title
end

命名規則

:sunny:クラス名 : 大文字始まり。2語以上は単語境界文字を大文字にする(Camel Case):camel:

:sunny:変数名 : 小文字のみ。2語以上は_でつなぐ。(Snake Case):snake:

initializeメゾット

:sunny:initialize : newメゾットが呼ばれた時に自動で実行するメソッド
newメソッドに引数を渡すとinitializeメソッドで受け取ることができる

class Recipe 
 attr_accessor :title, :author
 def initialize (title, author)
  @title = title
  @author = author
 end
end

recipe = Recipe.new("cheese cake", "chef")
p recipe.title => "cheese cake"
p recipe.author => "chef"

インスタンスメソッドとクラスメソッド

:sunny:インスタンスメソッド

クラスの中で定義したメソッド。インスタンス(クッキー:cookie:)に対して呼ぶことができる。
:writing_hand_tone1:(記法)クラス名#インスタンスメソッド名

:sunny:クラスメソッド

クラス(クッキーの型)に対して呼び出す。
self.メソッド名で定義。
:writing_hand_tone1:(記法)クラス名.クラスメソッド名

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

Goやるなら知っておきたい「Composition over inheritance」

Goを勉強している中で、「Composition over inheritance」という概念が出てきました。ちゃんと理解していなかったので、ここで改めて掘り下げます。

特に、普段 Ruby や Rails を書いている初中級者の方は、Go を勉強する前におさらいしておくことをオススメします。なぜなら、Go では、Rubyで馴染みのある Inheritance(継承)ではなく、Composition(合成)のみが使われるからです。

「Composition over inheritance」とは

「Composition over inheritance」は、日本語だと「継承より合成」と表現されます。

これは、オブジェクト思考プログラミングにおいて、親クラスやベースクラスを「継承」するよりも、「合成」によってコードを共通化・再利用する方が望ましい、という考え方です。

つまり、継承より合成の方が良いということです。

それを理由に、Go では合成のみが採用されており、型の継承はできません

Composition と Inheritance の比較

Composition と Inheritance、それぞれについておさらいします。

両者の対比は、次のようにまとめられます。

英語 日本語 型の関係 考え方の例
Composition 合成 has-a ソファには綿が入っている
Inheritance 継承 is-a ソファは家具である

具体的なコード例

文字だけでは抽象的なので、具体的なコード例で考えてみましょう。

ここでは、挨拶ができる「英語圏の人」と「日本人」をコードで表してみます。

Composition と Inheritance を対比させたいので、どちらも表現しやすい Ruby を用います(独断と偏見)。

Inheritance の例

Inheritance で設計する場合、まず「挨拶ができる」スーパークラスとして Person クラスを定義し、greet メソッドを持たせます。

person.rb
class Person
  def greet
    print word
  end
end

その Person クラスを継承する English クラスを定義します。

english.rb
class English < Person
  def word
    'Hello!'
  end
end

同様に Japanese クラスも定義します。

japanese.rb
class Japanese < Person
  def word
    'こんにちは!'
  end
end

EnglishJapanese、どちらでも挨拶できます。

e = English.new
e.greet # 'Hello!'

j = Japanese.new
j.greet # 'こんにちは!'

Inheritance は、普段 Ruby・Railsを書いてる人にとって、ごく一般的で馴染みのある設計だと思います。

Composition の例

Composition で設計する場合、「挨拶ができる」ことに着目し、そのビジネスロジックを Greeting クラスとして切り出します。(モジュールを include するやり方は、クラス継承ツリーに含まれてしまうので、Composition の純粋な例としては使えません。)

greeting.rb
class Greeting
  def greet(word)
    print word
  end
end

その Greeting クラスを English クラスに持たせます(合成します)。

english.rb
class English
  attr_reader :greeting

  def initialize
    @greeting = Greeting.new
  end

  def greet
    greeting.greet(word)
  end

  def word
    'Hello!'
  end
end

同様に Japanese クラスも定義します。

japanese.rb
class Japanese
  attr_reader :greeting

  def initialize
    @greeting = Greeting.new
  end

  def greet
    greeting.greet(word)
  end

  def word
    'こんにちは!'
  end
end

これで先ほどと同様に、EnglishJapanese どちらでも挨拶できます。

e = English.new
e.greet # 'Hello!'

j = Japanese.new
j.greet # 'こんにちは!'

Composition の例 (Go ver.)

参考として、Go で上の Composition を表すとこのようになります → こちら (Go Playground)

なぜ、Compositionが良いのか?

ここからが本題です。

なぜ「Composition over inheritance」、つまり、継承より合成の方が良いのでしょうか?

Composition のメリット

一般的に考えられているメリットは次の2つです。

1. 考えやすい

Composition の場合、その共通化させるビジネスロジック(上の例では、挨拶できること)に着目してコードを設計します。そのため、Inheritance の場合と違って、抽象化させるためのスーパークラス名や、継承ツリーの構成などに頭を悩まされることはありません。

2. 変更しやすい

コードに変更を加える場合、Composition では、そのビジネスロジックを持っている当事者のみが影響を受けます。また、あるクラスにビジネスロジックを追加する場合も、Composition として共通化していれば簡単に対応できます。

一方 Inheritance では、スーパークラスのコードを変更すると、その継承ツリーの下位クラス全体に影響があります。そのため、スーパークラスに新しい振る舞いを追加することは、意図しないエラーを生むリスクがあります。

例えば、挨拶できるロボットがいた場合...

Composition だと、新たな Robot クラスに Greeting クラスを持たすだけでOKです。

一方 Inheritance では、RobotPerson ではないので、スーパークラス名や継承ツリーの構成を再検討しないといけません。また、スーパークラスに変更を加えると、その下位クラスで意図しないエラーが起きるリスクがあります。

Composition のデメリット

一般的に考えられているデメリットは1つで、合成したメソッドを呼ぶためのメソッドを書かないといけないことです。上の Ruby の例で言う、English#greetJapanese#greet ですね。

一方の Inheritance では、継承したスーパークラスのメソッドをそのまま呼び出せます。したがって、スーパークラスに基本的なビジネスロジックを持たせておけば、Composition よりも少ないコードで同じ振る舞いを実装することが可能です。(ただし、何でもかんでも詰め込むと「神クラス」となって収集がつかなくなるので注意!)

デメリットの回避

この Composition のデメリットを回避するために、Go では Embedding types が用いられます。

先ほどあげた Go Playground のコード例でも Greeting タイプを EnglishJapanese タイプに組み込むことで、それぞれのタイプで greet メソッドを定義することを回避しています。

// 一部抜粋
type Greeting struct{}

type English struct {
    Greeting
}
type Japanese struct {
    Greeting
}

まとめ

継承は、抽象化したコードで効率的な実装ができるので、変更の可能性が少ない部分などにはとても有効な設計手法です。そして、Ruby を使っている人にとっては、とても馴染みがあります。

しかし、Go では、合成のみが採用されており、型の継承はできません

この言語仕様の違いを、背景にある「Composition over inheritance」と一緒にしっかり理解しておけば、より早く Go の世界に馴染むことができるでしょう。

Sources

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

Sequel::Mock::Datasetの使い方(ついでにDatasetとArrayの違い)

すごい雑なコードです。
Qiitaに載せるようにいろいろ変更を加えているのでサンプルコードでの動作確認まではできていないです。
なので参考程度に見ていただけると:bow:

require 'sequel'
require 'sequel/adapters/mock' # sequelのrequireだけではこのファイルはrequireしてくれない模様

class Sample
  # 0埋め済みの市区町村IDを返す
  # @return [Array]
  def get
    db = Sequel.connect(config)
    dataset = db.fetch('select city_id from sample_table')
    # 市区町村コードを0埋めした状態で返したいのでeachで処理する
    dataset.each do |row|
      row[:city_id] = '%05%d' % row[:city_id]
    end
    dataset.all
  end

  private def config
    # DB接続に必要な設定
  end
end

describe Sample do
  describe '#get' do
    subject { described_class.new.get }
    let(:db) { Sequel::Mock::Database.new(config) } # configメソッドと同内容を引数に取る
    let(:records) do
      [
        { city_id: 1101 },
        { city_id: 1102 }
      ]
    end
    let(:dataset) { Sequel::Mock::Dataset.new(db) }
    before :each do
      allow_any_instance_of(Sequel).to receive(:connect).with(config).and_return(db)
      db.fetch = records # DBに格納されているであろうデータをセットする
      allow(db).to receive(:fetch).with('select city_id from sample_table').and_return(dataset)
    end

    it 'city_idが0埋めされて5桁の文字列になっていること' do
      subject.each do |row|
        expect(row[:city_id]).to be_a(String)
        expect(row[:city_id].size).to eq 5
        # start_withも使って0で始まっていることの確認もしたいけど群馬県以南は1〜4で始まってる5桁なのでやらない
        # 脳死でsizeとか書いたけど正規表現で書けばいいじゃんって思う
      end
    end
  end
end

ざっくり雰囲気は掴んでもらえるでしょうか。
Sequel::Mock::Databaseに対してfetchでデータを投入しておくというのがミソです。
これをしないとdatasetのモックがnil[]を返すようになります。
モック化、結構手間ですね:frowning2:

ついでにDatasetとArrayの違い

実はこの#getは要件を満たせていないです:thumbsdown:

今回はRubyでの参照の値渡しを利用して#each内でcity_idを上書きする、ということをしようとしています。(サンプルなのでこれのいい悪いは一旦置いといて)

arr = [{city_id: 1101}]
arr.each {|row| row[:city_id] = '%05d' % row[:city_id]}
p arr #=> [{:city_id=>"01101"}]

Sequel::Dataset#eachを提供しているのでうまく行っているかのように見えますが、実際はそうではありません。

dataset = db.fetch('select city_id from sample_table')
dataset.each do |r|
  r[:city_id] = '%05d' % r[:city_id]
end
dataset.all # [{:city_id=>1101}]

この手法はArrayであれば成立しますがSequel::Datasetの場合はdataset.eachの操作をするたびにSQLを実行した結果が返されます。(内部的にキャッシュをしていてSQLの実行は1回で済んでいる可能性もあるかもしれませんが、そこまで検証してないです)
そのため上書きした結果を返さずに#allを呼んだ際にまた別の配列を返しているというものになります。


対象のデータ型が何かを理解した上で実装を進めたいですね。

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

【Rails】RSpecでFactoryBotをcreateしようとしたらErrorが吐き出されたので調べた

RSpecを書いたところ、FactoryBotからuserをcreateするところでエラーが発生してしまうので、原因を調べてみた。

事象

spec/factories/user.rb
FactoryBot.define do
  factory :user do
    name { "テストユーザー" }
    email { "test_user@test.co.jp" }
    password { "12345678" }
  end
end
user_spec.rb
require 'rails_helper'

RSpec.describe User, type: :model do
  let(:user) { user :user }

  describe "バリデーション" do

    it "nameが空だとinvalid" do
      user.name = ""
      expect(user).not_to be_valid
    end
  end
end

rspecを実行したところ、エラーが発生。
ちなみに今回、Userモデルのvalidatesは:name, presence: trueだけとする。

Failures:

  1) User バリデーション nameが空だとinvalid
     Failure/Error: let(:user) { create :user }

     ActionView::Template::Error:
       Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true
     # ./spec/models/client_spec.rb:4:in `block (2 levels) in <top (required)>'
     # ./spec/models/client_spec.rb:9:in `block (3 levels) in <top (required)>'
     # ------------------
     # --- Caused by: ---
     # ArgumentError:
     #   Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true
     #   ./spec/models/client_spec.rb:4:in `block (2 levels) in <top (required)>'

Finished in 0.52519 seconds (files took 2.18 seconds to load)
1 example, 1 failure

何やらMissing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true というエラーが吐き出されている。

結論

今回はdeviseを使っているのだが、メール認証機能のconfirmableを有効にしている。

user.rb
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable,
         :confirmable

メール認証機能とは→アカウント新規作成時に登録したアドレスにメールが送られ、確認用リンクをクリックするとログイン画面からログイン可能となる。
このリンクをクリックしたタイミングで、Userのconfirmed_atカラムにその時刻がsaveされる。
どうやらconfirmableが有効の場合、このconfirmed_atがpresentかnilかを見分けて、そのユーザーがログイン可/不可を条件分けしているらしい。
(詳細までは調べられてません)

従って、FactoryBotのファイルにconfirmed_atを書き加えると

spec/factories/user.rb
FactoryBot.define do
  factory :user do
    name { "テストユーザー" }
    email { "test_user@test.co.jp" }
    password { "12345678" }
    confirmed_at { Date.today }
  end
end
.

Finished in 0.2486 seconds (files took 4.21 seconds to load)
1 example, 0 failures

無事、rspecが通りました。

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

【Rails】FactoryBotをcreateしようとしたらErrorが吐き出されたので調べてみた

RSpecを書いたところ、FactoryBotからuserをcreateするところでエラーが発生してしまうので、原因を調べてみた。

事象

spec/factories/user.rb
FactoryBot.define do
  factory :user do
    name { "テストユーザー" }
    email { "test_user@test.co.jp" }
    password { "12345678" }
  end
end
user_spec.rb
require 'rails_helper'

RSpec.describe User, type: :model do
  let(:user) { user :user }

  describe "バリデーション" do

    it "nameが空だとinvalid" do
      user.name = ""
      expect(user).not_to be_valid
    end
  end
end

rspecを実行したところ、エラーが発生。
ちなみに今回、Userモデルのvalidatesは:name, presence: trueだけとする。

Failures:

  1) User バリデーション nameが空だとinvalid
     Failure/Error: let(:user) { create :user }

     ActionView::Template::Error:
       Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true
     # ./spec/models/client_spec.rb:4:in `block (2 levels) in <top (required)>'
     # ./spec/models/client_spec.rb:9:in `block (3 levels) in <top (required)>'
     # ------------------
     # --- Caused by: ---
     # ArgumentError:
     #   Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true
     #   ./spec/models/client_spec.rb:4:in `block (2 levels) in <top (required)>'

Finished in 0.52519 seconds (files took 2.18 seconds to load)
1 example, 1 failure

何やらMissing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true というエラーが吐き出されている。

結論

今回はdeviseを使っているのだが、メール認証機能のconfirmableを有効にしている。

user.rb
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable,
         :confirmable

メール認証機能とは→アカウント新規作成時に登録したアドレスにメールが送られ、確認用リンクをクリックするとログイン画面からログイン可能となる。
このリンクをクリックしたタイミングで、Userのconfirmed_atカラムにその時刻がsaveされる。
どうやらconfirmableが有効の場合、このconfirmed_atがpresentかnilかを見分けて、そのユーザーがログイン可/不可を条件分けしているらしい。
(詳細までは調べられてません)

従って、FactoryBotのファイルにconfirmed_atを書き加えると

spec/factories/user.rb
FactoryBot.define do
  factory :user do
    name { "テストユーザー" }
    email { "test_user@test.co.jp" }
    password { "12345678" }
    confirmed_at { Date.today }
  end
end
.

Finished in 0.2486 seconds (files took 4.21 seconds to load)
1 example, 0 failures

無事、rspecが通りました。

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

google-api-client,optimist(trollop)を使うと出てくる3つのエラーとその解決法

概要

Youtube APIを使用し動画情報を 取得し一覧表示するサイトを作成中にハマってしまったエラーが3つありましたので、エラーパターンと解決法を書き残しておきます。
Gemのバージョンにも関わってきますし、デプロイしているサーバの環境にも依存しますので、このエラー現象と解決方法は2019年7月段階のものだということを念頭に置いてください。

環境

  • Ruby 2.4.1
  • rails 5.2.3
  • google-api-client 0.8.6
  • optimist 3.0.0(旧trollop)
  • サーバ
    • heroku

エラーパターン1:oprimist(trollop)のエラー

herokuのlog
2019-06-18T11:44:23.578660+00:00 app[web.1]: Error: unknown argument '-p'.
2019-06-18T11:44:23.578668+00:00 app[web.1]: Try --help for help.

このパターンはoptimist::optionsメソッドにコマンドライン引数がぶっこまれていることが原因です。
heroku上だと-pと-eオプションを付けた状態でrails sされます。
参考(optimistのソース):https://github.com/ManageIQ/optimist

ローカル環境でサーバを起動する際にrails s だけで起動している場合は起きないのにheroku上だと上記のエラーが出てきます。ためしにローカル環境でrails s -p ポート番号(3000) -e development で起動して実行するとheroku上で動かした時と同じエラーが出ることが確認できます。
もしくはbyebugでoptimist::optionsをコールする直前のARGVの値を参照してみると確認できると思います。

エラーパターン1:解決法

1.ARGVの値をローカル変数に退避
2.ARGVを空にする
3.optimist::optionsをコール
4.ARGVの値を元にもどす

修正コード
arg_array_save = []
arg_array_save = ARGV.shift(4)
opts = Optimist::options do
     ・・・中略・・・
arg_array_save.each do |arg_push|
  ARGV.push(arg_push)
end

こんな感じになります。ARGVはメソッド内で値を直接操作すること(代入等)ができないので、shift,push等のメソッドを使用していじっています。
もっといい方法があればぜひコメントで教えて頂きたいです。

エラーパターン2:ArgumentError

herokuのlog
2019-06-24T14:54:19.983862+00:00 app[web.1]: F, [2019-06-24T14:54:19.983799 #4] FATAL -- : [d9d33d3a-e4a5-4ba3-a0f6-7286855ae09a] ArgumentError (header field value cannot include CR/LF):

このパターンは上記のパターン1を解決した後にでてくる可能性があります。
原因はgoogle-api-clientのバージョンとrubyのバージョンがうまく対応できてないみたいです。gem内のソースコードレベルの原因は解明できてません。gem内の解析めんどくさい

解決法は2つありますが、解決法パターン1推奨です。

エラーパターン2:解決法パターン1

Rubyのバージョンを 2.4.1にする。
なぜかわかりませんが2.4.6から2.4.1にバージョンを落とすと解決しました。エラーメッセ的には改行コードだと思うので、その辺の処理がバージョンによって違うのでしょうか。
わかる人いたら教えてください。

エラーパターン2:解決法パターン2

google-api-client を最新のバージョンにする。
2019年7月現在だと最新は0.30.4です。
Gemを最新にするとエラーパターン2のエラーメッセージは消えるのですが、後述するエラーパターン3の状態に陥ってしまうので、この解決法はあまりお勧めできないです。
GitHubのReadmeを全部翻訳してバージョンによる変更点を全部理解すると解決できるかもしれないです。

エラーパターン3:LoadError

herokuのlog
2019-06-20T16:12:55.170597+00:00 app[web.1]: /app/vendor/bundle/ruby/2.4.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:33:in `require': No such file to load -- google/api_client.rb (LoadError)

ファイルが読み込めてないみたいですが、heroku内のファイルとかいじれるんでしょうか。

エラーパターン3:解決法

boot.rbのbootsnapをOFFにする。

これでエラーメッセージ自体は消えますが、また同じようなエラーが今度はactivesupportで出てきます。
この解決ルートは永遠と上記の解決法を繰り返す羽目になりそうだったので、やめました。
ですので、エラーパターン3の明確な解決法はわかってない状態です。

まとめ

  • エラーパターン3の最終的な解決法は不明。エラーパターン2でrubyのバージョンを変えて解決する方が現実的。
  • Gemのバージョンアップなどで仕様が変わるのでReadmeを読まないとわからない。
  • 英語を勉強する必要がある。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

プログラミング初心者はまずRailsチュートリアルをやるべきなのか?

Railsチュートリアルは初心者がやるべきか?

プログラミング初心者の流れとして、最近はProgateやドットインストールをやって基礎を学んで、そして次の段階でRailsチュートリアルをやろう!というようなものが結構多いように感じます。
Railsチュートリアルは、手を動かしものを作りながら学習を進められるとてもいい教材です。しかし、これをプログラミングを始めたばかりの初心者が取り組むべきなのでしょうか?

個人的な意見

私の個人的な意見としては、NOです。特に独学の場合はあまりお勧めできないと思っています。
先ほど書いたようにRailsチュートリアルはとてもいい教材です。しかしながら、初心者向けかというとちょっと違うかな、と思います。

なぜNO?

なぜ初心者向けではないと思うか、その最たる理由は初心者のレベル感は大きく異なるということです。
初心者といってもできることはかなり違ってくると思っています。
例えば、ProgateでRubyのコースを終えた方はプログラミング初心者と言えるでしょう。また一方で、自分でとりあえずは開発環境を作って、Progateの知識をつかって何か書いてみた!みたいな人もプログラミング初心者ということもできるでしょう。
つまり同じプログラミング初心者といってもできることの幅が大きく違うのです。そもそも初心者の線引きは人それぞれですしね。

じゃあいつ、どうやってやるべきなの?

さて、NOとはいったものの、永久に基礎練を続けていては意味がありません。ではいつやるべきなのか。
ベストなタイミングは挑戦を何度もしてみて、行けそうだなと感じたときですが、これだと元も子もないので、ちょっとした自分なりの基準を置きたいと思います。

まずは何を勉強したうえでやるべきか。

ここはProgateが個人的にも好きな教材なので、それを例にして書いていきます。
Progateでこれはやっておいておくべき、というものは

  • Html&CSS
  • Ruby
  • Rails
  • Command line
  • SQL

です。このすべてをやって、少し足りないかな、という感じです。
さらに、ここに加えてWebの知識を付けておくべきだと私は思います。というのも、Railsは簡単にアプリケーションが作れてしまうため、何がどう動いているのかわからなくても、なんとなくものを作れてしまいます。しかし、なんとなくでやっているので知識として定着しなかったり、他の言語への知識の転用ができなかったりします。
なので、どんな形でもいいのでWebの知識をつけておくとベストです。

おすすめのやり方

上に書いた勉強をしたうえでいよいよRailsチュートリアルに入っていきます。
そしてその時のおすすめのやり方ですが、

  • Heroku(デプロイ)
  • Bitbucket(バージョン管理)
  • テスト

このすべてを飛ばしてまずは一周することです。
この部分がRailsではとても難しい部分になると思います。
特にテストで詰まると進まなくなってしまうので、まずは何か作り切る!というところに焦点をおいて、作ることに関係のない、上の三つの難しい部分は飛ばしてしまいましょう。
そして二週目でここの部分も余裕があれば触り、勉強していくと詰まらずにすすめると思います!

終わりに

私はプログラミングを始めたとき、一緒に勉強する仲間がいました。しかし、Railsチュートリアルで自分以外が挫折してしまい、気づけば一人になってしまっていました。
適切なタイミング、適切な知識で勉強すればRailsチュートリアルはとてもいい教材です。しかし、その一方で、多くのプログラミング初心者を挫折させてしまっているものでもあります。(TwitterでRailsチュートリアルを始めて気づけば消える方の多い事!)
一人でもそんな人を減らせるように願います。

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

Rails6 のちょい足しな新機能を試す46(multidb connects_to 編)

はじめに

Rails 6 に追加されそうな新機能を試す第46段。 今回は、 multidb connects_to 編です。
Rails 6 では、 複数DBに対応したため、DB接続先を切り変えることができるようになっています。

Ruby 2.6.3, Rails 6.0.0.rc1 で確認しました。Rails 6.0.0.rc1 は gem install rails --prerelease でインストールできます。

$ rails --version
Rails 6.0.0.rc1

今回の準備

今回は、 Rails6 のちょい足しな新機能を試す35(multidb migration --database オプション編)
をベースに作業を進めます。

library 側にも users テーブルを追加する

backbone 側に users テーブルが存在しますが、 library 側にも users テーブルを追加します。

$ bin/rails g migration CreateUser name --db=library

db:migrate を実行する

bin/rails g db:migrate

User モデルを作る

User モデルを作ります。

app/models/user.rb
class User < ApplicationRecord
end

Book モデルを作る

Book モデルを作ります。
books テーブルは、 backbone にはなく、 library 側にありますので、DB接続先を connects_to を使って明示的に設定します。

app/models/book.rb
class Book < ApplicationRecord
  connects_to database: { writing: :library, reading: :library }
end

Rails 6.0.0rc1 では、 :writing:reading の両方を指定する必要があるようです。

  connects_to database: :library

だとエラーになりました。

今回は、Book クラスに直接 DB 接続先を記載しましたが、もし、Bookモデルの他にも、 library 側に接続する接続するモデルがあれば、 ApplicationRecord から派生させた抽象クラスを作成して、抽象クラスの中で、 connects_to を使い、 Book クラスは、その抽象クラスから派生させた方が良いでしょう。(Rails Guide ではそういう書き方になってます。)

seed データを作る

backbonelibrary 側双方に users テーブルが存在するため、両方のDBに接続してデータを登録するように、 ActiveRecord::Base.connected_to を使います。

books テーブルは、 library 側に存在しますが、 Book クラスで接続先を library に指定しているため、 ActiveRecord::Base.connected_to を使う必要がありません。

db/seeds.rb
User.create(name: 'Taro')

ActiveRecord::Base.connected_to(database: :library) do
  User.create(name: 'Hanako')
end

Book.create(title: 'Programming Ruby')

seed データを登録する

$ bin/rails db:seed

controller と View を作る

登録した seed データを表示するため、 controller と view を作ります。

bin/rails g controller Dashboard index

User モデルにメソッドを追加する

library の users テーブルを検索できるように User モデルに all_in_library メソッドを追加します。
ActiveRecord::Base.connected_to を使って library 側に接続先を変更してから all メソッドを呼び出します。

app/models/user.rb
class User < ApplicationRecord
  def self.all_in_library
    ActiveRecord::Base.connected_to(database: :library) do
      all
    end
  end
end

DashboardController#index メソッドを修正する

登録したデータを取得するように index メソッドを修正します。

app/controllers/dashboard_controller.rb
class DashboardController < ApplicationController
  def index
    @books = Book.all
    @users = User.all.to_a
    @users_in_library = User.all_in_library
  end
end

ここで、 メソッドの2行目を @user = User.all.to_a としている理由は後述します。

データを表示する View を作成する

各変数の値を表示するための View を作成します。

app/views/dashboard/index.html.erb
<h1>Dashboard#index</h1>

<table>
  <thead>
    <tr>
      <th>
        valiable
      </th>
      <th>
        value
      </th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>
        @books
      </td>
      <td>
        <%= @books.map(&:title) %>
      </td>
    </tr>
    <tr>
      <td>
        @users
      </td>
      <td>
        <%= @users.map(&:name) %>
      </td>
    </tr>
    <tr>
      <td>
        @users_in_library
      </td>
      <td>
        <%= @users_in_library.map(&:name) %>
      </td>
    </tr>
  </tbody>
</table>

rails server を起動してブラウザで表示する

$ bin/rails s

http://localhost/dashboard/index にアクセスすると値が表示されます。
index_ok.png

User.all ではなく User.all.to_a な理由

User.all にすると、 Taro の値が表示されず Hanako になってしまいます。

index_ng.png

これは、 all メソッドが ActiveRecord::Relation を返すだけで、 実際にSQLを発行しないためです。 このため、 all にすると、 View を rendering するときに、SQLが発行されてしまい、接続先が library に設定された状態で SQLが発行されてしまうようです。
これを防ぐために、DB の接続が library に変わる前に SQLを発行するため、意図的に to_a をつけました。

app/controllers/dashboard_controller.rb
  def index
    @books = Book.all
    @users = User.all # => ActiveRecord::Relation が返され SQLは実行されない。
    @users_in_library = User.all_in_library # 接続先が library に変わる

    # このあと index View をレンダリングする際に、全てのSQLが発行される。
  end

User.all の場合、View のレンダリングの際にすべての SQL が発行されていることがログからもわかります。

  Rendering dashboard/index.html.erb within layouts/application
  Book Load (0.3ms)  SELECT "books".* FROM "books"
  ↳ app/views/dashboard/index.html.erb:20:in `map'
  User Load (0.3ms)  SELECT "users".* FROM "users"
  ↳ app/views/dashboard/index.html.erb:28:in `map'
  User Load (0.5ms)  SELECT "users".* FROM "users"
  ↳ app/views/dashboard/index.html.erb:36:in `map'

User.all.to_a とした場合、controller の中で SQL が発行されていることがわかります。

  User Load (0.4ms)  SELECT "users".* FROM "users"
  ↳ app/controllers/dashboard_controller.rb:4:in `index'
  Rendering dashboard/index.html.erb within layouts/application
  Book Load (0.3ms)  SELECT "books".* FROM "books"
  ↳ app/views/dashboard/index.html.erb:20:in `map'
  User Load (0.4ms)  SELECT "users".* FROM "users"
  ↳ app/views/dashboard/index.html.erb:36:in `map'

今回、試したコードが特殊な例かも知れませんが、挙動が少し紛らわしいので、今後のバージョンで修正されるかも知れませんね。

試したソース

試したソースは以下にあります。
https://github.com/suketa/rails6_0_0rc1/tree/try046_multidb_connection_switching

参考情報

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

rubyの配列について

今更だけどrubyの配列の使い方について。Qiitaデビューを兼ねて勉強になったことを復習。

⭐map関数
配列の要素ごとに処理を実行して、
新たな配列を作成できます。

例:各要素に対して、1を足した配列を返す。

???
%w|1 2 3 4 5|.map{|x| x.to_i+1}
???
結果:[2, 3, 4, 5, 6]

⭐二つの配列の要素が、
全く同じ順番かどうかを比較する
???
if [1, 2, 3] == [1, 2, 3]
end
???
これでOK

⭐ハッシュも比較が可能です。
???
if {a:1, b:2, c:3} == {a:1, b:2, c:3}
end
???

⭐ハッシュと配列を合わさった配列でも大丈夫です。
???
If [{a:1, b:2}, {array:[3, 4, 5]}] == [{a:1, b:2}, {array:[3, 4, 5]}]
???

⭐配列の中で重複しているものを削除する
???
arr = [1, 2, 5, 5, 1, 3, 1, 2, 4, 3]
arr.uniq
???
結果:[1, 2, 5, 3, 4]

⭐配列が生成されていない時だけ代入する
???
list ||= ["1","2"]
???

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

今日の学習0703

今日やったこと

①(https://railstutorial.jp/chapters/beginning?version=5.1#cha-beginning) を参考にわからない単語のピックアップ。
→Ruby gem
bundle install
bundle updateの実行方法
MVCやREST
ジェネレータ、マイグレーション
ERB
scaffold
MITライセンス
DSL (ドメイン固有言語)
API
gem (ページネーションや画像アップロードといった特定の問題を解決するためのgemなど)、
クラウド統合開発環境 (クラウドIDE)
AWS Cloud9
Bashなどの標準的なシェルコマンドラインインターフェイス
②授業の動画の復習
③実際に演習
Ⅰ、Ruby on Railsで使うRuby gemはどのWebサイトにありますか?
→ (RubyGems.org)として置かれている
Ⅱ、現時点でのRailsの最新バージョンはいくつですか?
https://rubyonrails.org/ で確認。現在は5.2.3
Ⅲ、Ruby on Railsはこれまでに何回ダウンロードされたか?
→ RubyGems.orgで確認。現在は37,303,000,945。

学んだこと

①開発環境を大別すると、テキストエディタやコマンドラインを使う環境と、IDE (統合開発環境) の2つに分けられる。
②ローカルサーバーはコントロール+Cで閉じることが可能。
③Railsのアプリを表示するには、http://localhost:3000/

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

fields_forについて

はじめに

fields_forとはform内で異なるモデルを編集することができるようになるもの。
今回はitemテーブルとimageテーブルが1対多の関係の時にimageテーブルに画像を保存できるようにするために使用した。

使い方

new.html.haml
.item-contents__image
              %label
                .item-contents__image__box
                  .input-area
                  .item-contents__image__box__text
                    ドラッグアンドドロップ
                    またはクリックしてファイルをアップロード
                  #file-preview
                  = f.fields_for :images do |i|
                    =i.file_field :url, class: 'item-contents__image__drop-file', type: "file"
item.controller.rb
def new
    @item = Item.new
    @item.images.build
end

def create
    Item.create(item_params)
    redirect_to root_path, notice: '商品を出品しました'
end


private
  def item_params
    params.require(:item).permit(:name, :description, :price, images_attributes: [:url, :item_id])
  end
item.rb
  has_many :images
  accepts_nested_attributes_for :images
image.rb
  belongs_to :item
  mount_uploader :url, ImageUploader

これらの記述をすることでimageテーブルに画像を保存することが可能になります。

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

This copy of libswiftCore.dylib requires an OS version prior to 10.14.4.

メモ

タイトルのエラーが出たら

cp -r /usr/lib/swift/*.dylib /Applications/Xcode.app/Contents/Frameworks

を実行。

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