20190403のRubyに関する記事は23件です。

[RUBY] %記法 

%記法

「’ ‘」や「” “」に対するエスケープを省略するためのもの。

%w

文字列の配列の("")
などを省略することができる。

例:

puts ["test1", "test2", "test3"]

%wを使えばスッキリ書くことができる。

puts %w(test1 test2 test3)

また、%Wと%wの違いは以下の通り。

%W→変数の式展開あり
%w→変数の式展開なし

%と%Q

「” “」(ダブルクォーテーション)の代替をする。

例:

puts "\"テスト\" \"サンプル\""
puts %("テスト","サンプル")

さいごに

まぁ、%wがめちゃ便利だということがわかりました。

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

Railsで「投稿お気に入り追加機能」を実装する

プログラミングスクールの発展的な課題で、【Railsで「投稿お気に入り追加機能」」の実装】がありましたが、難易度が高く理解するのに数十時間を要しため、簡単に整理します。

なお、ユーザーUserモデルと投稿Micropostモデルの中間テーブルをFavoriteモデルとして定義し、進めていきます。

説明は、基本的に「投稿お気に入り追加機能」の実装に絞ります。

モデルを作成する

Userモデルの作成

Userモデルはログイン機能を実装することを前提にしています。

rails g model User name:string email:string password_digest:string

下記のマイグレーションファイルが作成されます。

class CreateUsers < ActiveRecord::Migration[5.0]
  def change
    create_table :users do |t|
      t.string :name
      t.string :email
      t.string :password_digest

      t.timestamps
    end
  end
end

Micropostモデルの作成

MicropostモデルはUserモデルに関連づけることを前提にしています。

rails g model Micropost content:string user:references

下記のマイグレーションファイルが作成されます。

class CreateMicroposts < ActiveRecord::Migration[5.0]
  def change
    create_table :microposts do |t|
      t.string :content
      t.references :user, foreign_key: true

      t.timestamps
    end
  end
end

Favoriteモデルの作成

rails g model Favorite user:references micropost:references 

下記のマイグレーションファイルさ作成されるので、t.index [:user_id, :micropost_id], unique: trueを追加します。

これは、user_idmicropost_idの組み合わせが重複して保存されないようにするためです。

class CreateFavorites < ActiveRecord::Migration[5.0]
  def change
    create_table :favorites do |t|
      t.references :user, foreign_key: true
      t.references :micropost, foreign_key: true

      t.timestamps
      t.index [:user_id, :micropost_id], unique: true #ここを追記
    end
  end
end

関連モデルを追記する

Favoriteモデルの確認

favorite.rb
class Favorite < ApplicationRecord
  belongs_to :user
  belongs_to :micropost
end

Micropostモデルの追記

has_many :favoritesでMicropostモデルは複数のFavoriteモデルに関連づけられることを定義します。

foreign_keyにはFavariteモデル作成時に追加したt.index [:user_id, :micropost_id], unique: trueにおけるmicropost_idを追加します。

has_many :users, through: :favoritesでは、MicropostモデルはFavoriteモデルを介して複数のUserモデルに関連づけられることを定義します。

micropost.rb
class Micropost < ApplicationRecord
  belongs_to :user

  validates :content, presence: true, length: { maximum:255 }

  #お気に入り機能追加用中間テーブル追加
  has_many :favorites, foreign_key: 'micropost_id', dependent: :destroy
  has_many :users, through: :favorites
end

Userモデルの追記

Userモデルの追記は複雑で、この投稿お気に入り追加機能の一番重要な部分です。

Userモデルはhas_many :microposts複数のMicropostモデルに紐づけられます。

次に、Micropostモデル追記時と同様に、has_many :favoritesでUserモデルは複数のFavoriteモデルに関連づけられることを定義します。

ここがかなり重要で、has_many :favposts, through: :favorites, source: :micropostは、UserモデルがFavoriteモデルを介して複数のMicropostモデルと関連づけます。しかし、すでにhas_many :micropostsが使用されているため、favpostsと定義し、source: :micropostによって情報元はmicropostだと補足することで中間モデルで定義されたbelongs_to :micropostと関連づけます。

user.rb
class User < ApplicationRecord

  before_save { self.email.downcase! }
  validates :name, presence: true, length: { maximum: 50}
  validates :email, presence: true, length: { maximum: 50},
                    format: { with: /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i },
                    uniqueness: { case_sensitive: false }

  has_secure_password

  #Userモデルは複数のMicropostモデルに関連づけられる
  has_many :microposts

  #お気に入り機能追加用
  has_many :favorites
  has_many :favposts, through: :favorites, source: :micropost

投稿お気に入り追加メソッドを追加

like(micropost)メソッドでは、favoritesテーブルに保存されているFavoriteモデル作成時に定義したmicropost_idを投稿一覧のインスタンスmicropostのidであるmicropost.idと照合(find)し、見つからなければ新規に作成(create)します。

unlike(micropost)メソッドでは、favoritesテーブルに保存されているFavoriteモデル作成時に定義したmicropost_idを投稿一覧のインスタンスmicropostのidであるmicropost.idと照合(find)し、削除します。likeメソッド違いcreateされない点がポイントです。

favpost?メソッドでは、お気に入り追加した投稿が、投稿一覧の投稿一覧のインスタンスmicropostを含むかどうかを判定しています。含んでいればtrue含んでいなければfalseを返すメソッドです。

user.rb
class User < ApplicationRecord
  #お気に入り追加
  def like(micropost)
    favorites.find_or_create_by(micropost_id: micropost.id)
  end

  #お気に入り削除
  def unlike(micropost)
    favorite = favorites.find_by(micropost_id: micropost.id)
    favorite.destroy if favorite
  end

  #お気にり登録判定
  def  favpost?(micropost)
    self.favposts.include?(micropost)
  end

end

rails console で確認

user = User.find(1)
micropost = Micropost.find(1)

#favoritesテーブルに保存されるか確認
user.like(1)

#trueが返り値となるか確認
user.favpost?(micropost)

#favoritesテーブルから削除されるか確認
user.unlike(1)

お気に入り追加の確認

お気に入り登録判定の確認

user1.favpost?(micropost1)=> trueが表示されたら成功です。

user1.favpost?(micropost1)

お気に入り削除の確認

Favorite.allでuser_idとmicropost_idの組み合わせが表示されなくなったら成功です。

user1.unlike(micropost1)
Favorite.all

controllerの作成

favoritesコントローラー

このコントローラーは投稿一覧に「お気に入り登録」もしくは「お気に入り削除」機能を追加します。

そのため、元となる投稿一覧のインスタンスが@micropost.each do |micropost|で取り出されたmicropostである点が超重要です。決して@が付く@micropostでは無いのです。

user.rbで定義したlikeメソッドを用いて、micropostをお気に入り登録します。

favorites_controller.rb
class FavoritesController < ApplicationController
  def create
    #@micropostとはならないよ
    micropost = Micropost.find(params[:micropost_id])
    current_user.like(micropost)
    flash[:success] = 'お気に入り登録をしました。'
    redirect_back(fallback_location: root_path)
  end

  def destroy
    micropost = Micropost.find(params[:micropost_id])
    current_user.unlike(micropost)
    flash[:success] = 'お気に入り登録を解除しました。'
    redirect_back(fallback_location: root_path)
  end
end

usersコントローラー

likesメソッドでuserがお気に入り登録した投稿の一覧を取得します。

user.rbで定義したお気に入り登録した投稿であるfavpostsを用いて@favpostsを取得します。

users_controller.rb
class UsersController < ApplicationController
  before_action :require_user_logged_in, only: [:index, :show, :followings, :followers, :likes]

  def index
    @users = User.all.page(params[:page])
  end

  def show
    @user = User.find(params[:id])
    @microposts = @user.microposts.order('created_at DESC').page(params[:page])
    counts(@user)
  end

  def new
    @user = User.new
  end

  def edit
  end

  def create
    @user = User.new(user_params)
    if @user.save
      flash[:success] = 'ユーザ登録しました'
      redirect_to @user
    else
      flash[:danger] = 'ユーザの登録に失敗しました'
      render :new
    end
  end

  def followings
    @user = User.find(params[:id])
    @followings = @user.followings.page(params[:page])
    counts(@user)
  end

  def followers
    @user = User.find(params[:id])
    @followers = @user.followers.page(params[:page])
    counts(@user)
  end

  #お気に入り投稿一覧取得
  def likes
    @user = User.find(params[:id])
    @favposts = @user.favposts.page(params[:page])
    counts(@user)
  end

  private

  def user_params
    params.require(:user).permit(:name, :email, :password, :password_confirmation)
  end
end

micropostsコントローラー

お気に入り投稿機能と直接の関連性はないため説明は省略します。

microposts_controller.rb
class MicropostsController < ApplicationController
  before_action :require_user_logged_in
  before_action :correct_user, only: [:destroy]

  def create
    @micropost = current_user.microposts.build(micropost_params)
    if @micropost.save
      flash[:success] = 'メッセージの投稿に成功しました'
      redirect_to root_url
    else
      @microposts = current_user.feed_microposts.order('created_at DESC').page(params[:page])
      flash.now[:danger] = 'メッセージの投稿に失敗しました'
      render 'toppages/index'
    end
  end

  def destroy
    @micropost.destroy
    flash[:success] = 'メッセージを削除しました。'
    redirect_back(fallback_location: root_path)
  end

  private

  def micropost_params
    params.require(:micropost).permit(:content)
  end

  def correct_user
    @micropost = current_user.microposts.find_by(id: params[:id])
    unless @micropost
      redirect_to root_url
    end
  end
end

routeの作成

下記のURLに対応するようにルートファイルを設定します。

/users/:id/likes お気に入り投稿一覧取得アクション
/favorites お気に入り登録アクション
/favorites/:id お気に入り登録削除アクション

お気に入り投稿一覧取得アクションは、/users/:id/likesとするため、member doを用いてresources内に定義します。

routes.rb
Rails.application.routes.draw do
  root to: 'toppages#index'

  get 'login', to: 'sessions#new'
  post 'login', to: 'sessions#create'
  delete 'logout', to: 'sessions#destroy'

  get 'signup', to: 'users#new'
  resources :users, only: [:index, :show, :new, :create] do
    member do
      get :followings
      get :followers
      get :likes
    end
  end

  resources :microposts, only: [:create, :destroy]
  resources :relationships, only: [:create, :destroy]
  resources :favorites, only: [:create, :destroy]
end

viewの作成

作成するアプリケーションによって、ビューファイルの作成はかなり変わると思いますので、あくまで一例ということで。

viewの作成では、renderを用いるなどする中で、ローカル変数やインスタンス変数に何が代入されているのかを注意することがポイントになります。

お気に入りボタンページ

user.rbで定義したfavpost?(micropost)メソットを用いてお気に入り登録の判定します。

favorites/_like_button.html.erb
<% if current_user.favpost?(micropost) %>
  <%= form_for(current_user.favorites.find_by(micropost_id: micropost.id), html: { method: :delete }) do |f| %>
    <%= hidden_field_tag :micropost_id, micropost.id %>
    <%= f.submit 'お気に入り登録解除', class: 'btn btn-danger' %>
  <% end %>
<% else %>
  <%= form_for(current_user.favorites.build) do |f| %>
    <%= hidden_field_tag :micropost_id, micropost.id %>
    <%= f.submit 'お気に入り登録', class: 'btn btn-primary' %>
  <% end %>
<% end %>

お気に入り投稿一覧取得ページ

render 'microposts/microposts', microposts: @favpostsによってmicropostの一覧取得ページとの共通化を図っています。renderメソッドは<%= render 'パーシャルのパス', パーシャルで使う変数名: 変数に代入する値 %>なので、変数に代入する値にはusers_controller.rbで定義した@favpostを指定します。

users/show.html.erb
<div class="row">
  <aside class="col-xs-4">
    <div class="panel panel-default">
      <div class="panel-heading">
        <h3 class="panel-title"><%= @user.name %></h3>
      </div>
      <div class="panel-body">
        <img class="media-object img-rounded img-responsive" src="<%= gravatar_url(@user, { size: 500 }) %>" alt="">
      </div>
    </div>
    <%= render 'relationships/follow_button', user: @user %>
  </aside>
  <div class="col-xs-8">
    <ul class="nav nav-tabs nav-justified">
      <li class="<%= 'active' if current_page?(user_path(@user)) %>"><%= link_to user_path(@user) do %>Microposts <span class="badge"><%= @count_microposts %></span><% end %></li>
      <li class="<%= 'active' if current_page?(followings_user_path(@user)) %>"><%= link_to followings_user_path(@user) do %>Followings <span class="badge"><%= @count_followings %></span><% end %></li>
      <li class="<%= 'active' if current_page?(followers_user_path(@user)) %>"><%= link_to followers_user_path(@user) do %>Followers <span class="badge"><%= @count_followers %></span><% end %></li>
      <li class="<%= 'active' if current_page?(likes_user_path(@user)) %>"><%= link_to likes_user_path(@user) do %>Likes<span class="badge"><%= @count_favposts %></span><% end %></li>
    </ul>
    <%= render 'microposts/microposts', microposts: @favposts %> #
  </div>
</div>

投稿一覧ページ(お気に入りボタン設置)

お気に入りボタンを投稿一覧ページに設定するため、renderメソッドを用いて<%= render 'favorites/like_button', micropost: micropost %>とします。

microposts.each do |micropost|として抽出されたmicropostに対してお気に入りボタンを1つずつ結びつけるので@不要micropost: micropostになります。

microposts/_microposts.html.erb
<ul class="media-list">
  <% microposts.each do |micropost| %>
    <li class="media">
      <div class="media-left">
        <img class="media-object img-rounded" src="<%= gravatar_url(micropost.user, { size: 50 }) %>" alt="">
      </div>
      <div class="media-body">
        <div>
          <%= link_to micropost.user.name, user_path(micropost.user) %> <span class="text-muted">posted at <%= micropost.created_at %></span>
        </div>
        <div>
          <p><%= micropost.content %></p>
        </div>
        <div>
           <%= link_to "Delete", micropost, method: :delete, data: { confirm: "You sure?" }, class: 'btn btn-danger btn-xs' %>
        </div>
        <!--お気に入りボタン登録-->
        <div>
          <%= render 'favorites/like_button', micropost: micropost %>
        </div>
      </div>
    </li>
  <% end %>
  <%= paginate microposts %>
</ul>

Railsで「投稿お気に入り追加機能」を実装する方法まとめ

以上が、Railsで「投稿お気に入り追加機能」を実装する方法でした。

まとめることで、改めてわかりきっていないことを調べるきっかけになり良い経験となりました。

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

Ruby 文字列の中に特定の文字列が含まれるかの確認

はじめに

文字列中に特定の文字が含まれる場合の文字列の特定の仕方

特定の文字列が含まれる場合、それが含まれる文字列全体を返し、含まれない場合Noneを返すプログラムです

標準入力
cat      #特定の文字列
catbrue      #調べる対象の文字列
word = gets.chomp
array = readlines(chomp: true).select { |x|
  x.include?(word)
}

if array.empty?
    puts "None"
else
    puts array
end
出力結果
catbrue

上のプログラムは調べる対象の文字が増えることにも対応しています

標準入力
cat      #特定の文字列
catbrue      #これ以下は調べる対象の文字列
dogred
birdgreen     
word = gets.chomp
array = readlines(chomp: true).select { |x|
  x.include?(word)
}

if array.empty?
    puts "None"
else
    puts array
end
出力結果
catbrue

もちろん、一つも存在しない場合はNoneを返すようになっています

標準入力
cat        #特定の文字列
rabittpink      #これ以下は調べる対象の文字列
dogred
birdgreen     
word = gets.chomp
array = readlines(chomp: true).select { |x|
  x.include?(word)
}

if array.empty?
    puts "None"
else
    puts array
end
出力結果
None
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

deviseの導入と実装

deviseの導入と実装

Gemfileにgemを追加

Gemfile
gem 'devise'

bundle installを実行し、サーバーを再起動

$ bundle install
$ rails s

deviseの設定ファイルを作成

$ rails g devise:install

userモデルを作成

$ rails g devise user
$ bundle exec rake db:migrate
 #マイグレーションファイルに必要なカラム(name等)を設定した後に実行

deviseのビューファイルを作成

$ rails g devise:views

userのサインアップ時に名前を登録できるようにする

deviseでは初期状態でサインアップ時にメールアドレスとパスワードのみを受け取るようにストロングパラメーターが設定してあるので、
追加のパラメーターを許可したい場合は、application_controllerでbefore_actionにconfigure_permitted_parametersメソッドを設定する。

application_controller.rb
before_action :configure_permitted_parameters, if: :devise_controller?

protected

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

user編集機能を追加する

①ルーティング

routes.rb
resources :users, only: [:edit, :update]

②users_controller作成

$ rails g controller users

③edit,updateの定義

users_controller
class UsersController < ApplicationController
    def edit
    end

    def update
        if current_user.update(user_params)
           redirect_to root_path
        else
            render :edit #更新できない場合は編集画面に戻る
        end
    end

    private

    def user_params
        params.require(:user).permit(:name, :email)
    end
end

④ビューファイル作成

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

自動更新機能の実装

自動更新機能とは、例えばあるブラウザで投稿したメッセージを、自分のブラウザで確認した際に再読み込みせずに自動で呼び込んでくれる昨日のこと。

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

18日目:トランザクションって

授業でやった扱った『トランザクション』について、書く。

参考:「トランザクション」とは何か?を超わかりやすく語ってみた!
解説用の図が分かりやすかった。
あと、ウィキペディアも参照

トランザクションとは

コンピュータ内で実行される、分けることのできない一連の情報処理の一単位。

トランザクション処理における永続性記憶資源の管理では、複数のデータ項目の更新操作列をすべて実行するか、まったく実行しないように制御する必要がある。

ACID 標準

また、トランザクション処理システムは4つの属性の機能をサポートしており、頭文字からACID標準という。
- A : Atomic 不可分性
- C : Consistency 一貫性
- I : Isolation 独立性
- D : Durability 永続性

実際に動かしてみる

DB内で操作

参照:13.3.1 START TRANSACTION、COMMIT、および ROLLBACK 構文 -MySQLリファレンス

MySQLはデフォルトで、自動コミットモードが有効になった状態で動作し、実行するとすぐに、ディスクに格納されて永続的になります。この変更はロールバックできない。

自動コミットモードを暗黙的に無効にするには、START TRANSACTIONをし、その後、COMMITまたはROLLBACK で終了するまで、自動コミットは無効のままになります。そのあと、自動コミットモードはその以前の状態に戻ります。

実作業

今回はMySQLと、以前に作成した大学生徒データAppのデータを再利用する

terminal
mysql -u root -p
MySQL
#使用するデータベース情報の選択
mysql> USE cebu_college_development;

#使用するstudentsテーブルの構造を確認
mysql> DESC students;
+------------+--------------+------+-----+---------+----------------+
| Field      | Type         | Null | Key | Default | Extra          |
+------------+--------------+------+-----+---------+----------------+
| id         | bigint(20)   | NO   | PRI | NULL    | auto_increment |
| name       | varchar(255) | YES  |     | NULL    |                |
| email      | varchar(255) | YES  |     | NULL    |                |
| gender     | int(11)      | YES  |     | NULL    |                |
| age        | int(11)      | YES  |     | NULL    |                |
| opinion    | text         | YES  |     | NULL    |                |
| created_at | datetime     | NO   |     | NULL    |                |
| updated_at | datetime     | NO   |     | NULL    |                |
+------------+--------------+------+-----+---------+----------------+
#id3のtaro-2さんを使ってみる
mysql> SELECT name FROM students WHERE id = 3;
+--------+
| name   |
+--------+
| taro-2 |
+--------+

COMMITするパターン

#
mysql> START TRANSACTION;

# id=3のnameを"tran-sakuko"に更新
mysql> UPDATE students SET name = "tran-sakuko" WHERE id = 3;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

# 確認してみる
mysql> SELECT name FROM students WHERE id =3;
+-------------+
| name        |
+-------------+
| tran-sakuko |
+-------------+
1 row in set (0.00 sec)

# コミットする
mysql> COMMIT;
Query OK, 0 rows affected (0.00 sec)

# COMMITされてるか確認
mysql> select name from students where id =3;
+-------------+
| name        |
+-------------+
| tran-sakuko |
+-------------+
1 row in set (0.00 sec)

ROLLBACKするパターン

mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)

# id3のnameを"tran sakutarou"に更新
mysql> UPDATE students SET name = "tran sakutarou" WHERE id =3;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

# id3のnameに新しいデータの"tran sakutarou"がセットされてる
mysql> SELECT name FROM students WHERE id = 3;
+----------------+
| name           |
+----------------+
| tran sakutarou |
+----------------+
1 row in set (0.00 sec)

# ロールバックしてみる
mysql> ROLLBACK;
Query OK, 0 rows affected (0.01 sec)

#セットした"trans sakutarou"というデータはDBに格納されない。
mysql> SELECT name FROM students WHERE id = 3;
+-------------+
| name        |
+-------------+
| tran-sakuko |
+-------------+
1 row in set (0.00 sec)

RubyonRailsで動かしてみる

新しく、アプリを作成する。
今回はDBの操作だけなので、rails g modelコマンドのみ使用
テーブルはUserとReviewの2つ。

準備

terminal
rails new transact_self -d mysql
# database.ymlを編集後
rails db:create
# Userモデル作成
rails g model User name:string approved:boolean deleted:boolean
# Reviewモデル作成
rails g model Review user:references rate:integer approved:boolean
rails db:migrate

データ入力

console
(1..5).each do |i|
  User.create(name: "taro-#{i}", approved: true, deleted: false)
end
console
(1..5).each do |i|
  user = User.first
  Review.create!(user_id: user.id, rate: i, approved: true)
end

DB内で確認

mysql
mysql> SELECT * FROM users;
+----+--------+----------+---------+---------------------+---------------------+
| id | name   | approved | deleted | created_at          | updated_at          |
+----+--------+----------+---------+---------------------+---------------------+
|  1 | taro-1 |        1 |       0 | 2019-04-03 09:01:31 | 2019-04-03 09:01:31 |
|  2 | taro-2 |        1 |       0 | 2019-04-03 09:01:31 | 2019-04-03 09:01:31 |
|  3 | taro-3 |        1 |       0 | 2019-04-03 09:01:31 | 2019-04-03 09:01:31 |
|  4 | taro-4 |        1 |       0 | 2019-04-03 09:01:31 | 2019-04-03 09:01:31 |
|  5 | taro-5 |        1 |       0 | 2019-04-03 09:01:31 | 2019-04-03 09:01:31 |
+----+--------+----------+---------+---------------------+---------------------+

mysql> SELECT * FROM reviews;
+----+---------+------+----------+---------------------+---------------------+
| id | user_id | rate | approved | created_at          | updated_at          |
+----+---------+------+----------+---------------------+---------------------+
|  1 |       1 |    1 |        1 | 2019-04-03 09:01:38 | 2019-04-03 09:01:38 |
|  2 |       1 |    2 |        1 | 2019-04-03 09:01:38 | 2019-04-03 09:01:38 |
|  3 |       1 |    3 |        1 | 2019-04-03 09:01:38 | 2019-04-03 09:01:38 |
|  4 |       1 |    4 |        1 | 2019-04-03 09:01:38 | 2019-04-03 09:01:38 |
|  5 |       1 |    5 |        1 | 2019-04-03 09:01:38 | 2019-04-03 09:01:38 |
+----+---------+------+----------+---------------------+---------------------+

userとreviewとの、関連付け

app/models/user.rb
class User < ApplicationRecord
  has_many :reviews
end

reviewモデルにvalidation追加
approvedカラムを空欄不可にしておく。

app/models/review.rb
class Review < ApplicationRecord
  belongs_to :user
  validates :approved, presence: true
end

コンソールで、トランザクション処理の挙動を確認

1. トランザクション処理に成功し、commitされるパターン

console
user = User.first
User.transaction do
  user.update!(approved: false)
  user.reviews.each { |review| review.update!(approved: true) }
end
=>(0.1ms)  BEGIN
  User Update (0.3ms)  UPDATE `users` SET `approved` = FALSE, `updated_at` = '2019-04-03 09:10:46' WHERE `users`.`id` = 1
  Review Load (0.2ms)  SELECT `reviews`.* FROM `reviews` WHERE `reviews`.`user_id` = 1
   (6.8ms)  COMMIT
=> [#<Review id: 1, user_id: 1, rate: 1, approved: true, created_at: "2019-04-03 09:01:38", updated_at: "2019-04-03 09:01:38">, 
#<Review id: 2, user_id: 1, rate: 2,approved: true, created_at: "2019-04-03 09:01:38", updated_at: "2019-04-03 09:01:38">, 
#<Review id: 3, user_id: 1, rate: 3, approved: true, created_at: "2019-04-03 09:01:38", updated_at: "2019-04-03 09:01:38">,
,,,

userテーブルのid1のtaro-1が、更新されてる

mysql
mysql> select * from users;
+----+--------+----------+---------+---------------------+---------------------+
| id | name   | approved | deleted | created_at          | updated_at          |
+----+--------+----------+---------+---------------------+---------------------+
|  1 | taro-1 |        0 |       0 | 2019-04-03 09:01:31 | 2019-04-03 09:10:46 |
  1. トランザクション処理に失敗し、rollbackされるパターン :console user = User.first User.transaction do user.update!(approved: true) user.reviews.each { |review| review.update!(approved: false) } end =>(0.1ms) BEGIN User Update (0.2ms) UPDATE `users` SET `approved` = TRUE, `updated_at` = '2019-04-03 09:14:44' WHERE `users`.`id` = 1 Review Load (0.2ms) SELECT `reviews`.* FROM `reviews` WHERE `reviews`.`user_id` = 1 (6.6ms) ROLLBACK Traceback (most recent call last): 3: from (irb):2 2: from (irb):4:in `block in irb_binding' 1: from (irb):4:in `block (2 levels) in irb_binding' ActiveRecord::RecordInvalid (Validation failed: Approved can't be blank) トランザクション処理に失敗し、rollbackしたため、DBに変化はない。

modelファイルを編集して実装

app/models/user.rb
class User < ApplicationRecord
  has_many :reviews

  def suspend!
    self.class.transaction do
      disapprove_user!
      disapprove_reviews!
    end
  end                                                                                                                                                              

private
  def disapprove_user!
    self.update!(approved: false)
  end

  def disapprove_reviews!
    reviews.each { |review| review.update!(approved: false) }
  end
end

と、ここまでが授業でやった分。
トランザクション処理の概念は理解できた(と思う)。

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

Ruby 配列の大文字、小文字を変換する

配列内の文字列を大文字→小文字、小文字→大文字に変換する方法のまとめ

大文字から小文字への変換

a = ["APPLE", "ORANGE", "GRAPE"]
p a.map(&:downcase)
出力結果
["apple", "orange", "grape"]

小文字から大文字への変換

a = ["apple", "orange", "grape"]
p a.map(&:upcase)
出力結果
["APPLE", "ORANGE", "GRAPE"]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsチュートリアル三章あたりから、テストの際にエラーが出る方々へ...

cloud9環境にてRailsチュートリアルを学習中、テスト自体は無事に検証できるものの、毎回下記のようなエラーが出て気になっていました。

今後も同じ経験をされる方がいらっしゃると思い、共有しようと思います!

まずは私自身のコンソールに生じていたエラーです。

ec2-user:~/environment/sample_app (master) $ rails t
Running via Spring preloader in process 19388
Run options: --seed 51796

# Running:

..................

Finished in 0.564856s, 31.8665 runs/s, 63.7331 assertions/s.

18 runs, 36 assertions, 0 failures, 0 errors, 0 skips
Traceback (most recent call last):
        33: from -e:1:in `<main>'
        32: from /home/ec2-user/.rvm/rubies/ruby-2.6.0/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'
        31: from /home/ec2-user/.rvm/rubies/ruby-2.6.0/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'
        30: from /home/ec2-user/.rvm/gems/ruby-2.6.0/gems/spring-2.0.2/lib/spring/application/boot.rb:19:in `<top (required)>'
        29: from /home/ec2-user/.rvm/gems/ruby-2.6.0/gems/spring-2.0.2/lib/spring/application.rb:135:in `run'
        28: from /home/ec2-user/.rvm/gems/ruby-2.6.0/gems/spring-2.0.2/lib/spring/application.rb:135:in `loop'
        27: from /home/ec2-user/.rvm/gems/ruby-2.6.0/gems/spring-2.0.2/lib/spring/application.rb:141:in `block in run'
        26: from /home/ec2-user/.rvm/gems/ruby-2.6.0/gems/spring-2.0.2/lib/spring/application.rb:171:in `serve'
        25: from /home/ec2-user/.rvm/gems/ruby-2.6.0/gems/spring-2.0.2/lib/spring/application.rb:171:in `fork'
        24: from /home/ec2-user/.rvm/gems/ruby-2.6.0/gems/minitest-5.10.3/lib/minitest.rb:63:in `block in autorun'
        23: from /home/ec2-user/.rvm/gems/ruby-2.6.0/gems/minitest-5.10.3/lib/minitest.rb:141:in `run'
        22: from /home/ec2-user/.rvm/gems/ruby-2.6.0/gems/minitest-5.10.3/lib/minitest.rb:687:in `report'
        21: from /home/ec2-user/.rvm/gems/ruby-2.6.0/gems/minitest-5.10.3/lib/minitest.rb:687:in `each'
        20: from /home/ec2-user/.rvm/gems/ruby-2.6.0/gems/guard-minitest-2.4.4/lib/guard/minitest/reporter.rb:10:in `report'
        19: from /home/ec2-user/.rvm/gems/ruby-2.6.0/gems/guard-minitest-2.4.4/lib/guard/minitest/notifier.rb:31:in `notify'
        18: from /home/ec2-user/.rvm/gems/ruby-2.6.0/gems/guard-compat-1.2.1/lib/guard/compat/plugin.rb:113:in `notify'
        17: from /home/ec2-user/.rvm/gems/ruby-2.6.0/gems/guard-2.13.0/lib/guard/notifier.rb:31:in `notify'
        16: from /home/ec2-user/.rvm/gems/ruby-2.6.0/gems/guard-2.13.0/lib/guard/notifier.rb:11:in `connect'
        15: from /home/ec2-user/.rvm/gems/ruby-2.6.0/gems/notiffany-0.1.1/lib/notiffany/notifier.rb:42:in `connect'
        14: from /home/ec2-user/.rvm/gems/ruby-2.6.0/gems/notiffany-0.1.1/lib/notiffany/notifier.rb:42:in `new'
        13: from /home/ec2-user/.rvm/gems/ruby-2.6.0/gems/notiffany-0.1.1/lib/notiffany/notifier.rb:87:in `initialize'
        12: from /home/ec2-user/.rvm/gems/ruby-2.6.0/gems/notiffany-0.1.1/lib/notiffany/notifier.rb:198:in `_activate'
        11: from /home/ec2-user/.rvm/gems/ruby-2.6.0/gems/notiffany-0.1.1/lib/notiffany/notifier.rb:180:in `_detect_or_add_notifiers'
        10: from /home/ec2-user/.rvm/gems/ruby-2.6.0/gems/notiffany-0.1.1/lib/notiffany/notifier/detected.rb:59:in `detect'
         9: from /home/ec2-user/.rvm/gems/ruby-2.6.0/gems/notiffany-0.1.1/lib/notiffany/notifier/detected.rb:59:in `each'
         8: from /home/ec2-user/.rvm/gems/ruby-2.6.0/gems/notiffany-0.1.1/lib/notiffany/notifier/detected.rb:60:in `block in detect'
         7: from /home/ec2-user/.rvm/gems/ruby-2.6.0/gems/notiffany-0.1.1/lib/notiffany/notifier/detected.rb:60:in `detect'
         6: from /home/ec2-user/.rvm/gems/ruby-2.6.0/gems/notiffany-0.1.1/lib/notiffany/notifier/detected.rb:60:in `each'
         5: from /home/ec2-user/.rvm/gems/ruby-2.6.0/gems/notiffany-0.1.1/lib/notiffany/notifier/detected.rb:62:in `block (2 levels) in detect'
         4: from /home/ec2-user/.rvm/gems/ruby-2.6.0/gems/notiffany-0.1.1/lib/notiffany/notifier/detected.rb:100:in `_add'
         3: from /home/ec2-user/.rvm/gems/ruby-2.6.0/gems/notiffany-0.1.1/lib/notiffany/notifier/detected.rb:100:in `new'
         2: from /home/ec2-user/.rvm/gems/ruby-2.6.0/gems/notiffany-0.1.1/lib/notiffany/notifier/base.rb:59:in `initialize'
         1: from /home/ec2-user/.rvm/gems/ruby-2.6.0/gems/notiffany-0.1.1/lib/notiffany/notifier/tmux.rb:69:in `_check_available'
/home/ec2-user/.rvm/gems/ruby-2.6.0/gems/notiffany-0.1.1/lib/notiffany/notifier/tmux/client.rb:12:in `version': undefined method `[]' for nil:NilClass (NoMethodError)

長くなってしまいましたが、、、

_check_available'
/home/ec2-user/.rvm/gems/ruby-2.6.0/gems/notiffany-0.1.1/lib/notiffany/notifier/tmux/client.rb:12:in `version': undefined method `[]' for nil:NilClass (NoMethodError)

一番下のこの辺り↑が怪しそう。。。

結論から述べると、「tmux」の部分が原因です。

まずtmux(てぃーまっくす)とは何者なのか・・・

Answer.
tmuxは端末多重化ソフトウェア

つまり、1つのターミナルで複数の擬似ターミナルを起動することが可能になります。
複数のターミナルを立ち上げずとも、tmux上のターミナルを切り替えてオペレーションすることを可能とするソフトウェアです。

cloud9は一つのターミナルで複数のターミナルに切り替えることが出来ますよね。。。

そこで、とりあえずtmuxをインストールすることに。。。

 $ sudo yum install -y tmux
Loaded plugins: priorities, update-motd, upgrade-helper
amzn-main                                                                            | 2.1 kB  00:00:00     
amzn-updates                                                                         | 2.5 kB  00:00:00     
epel/x86_64/metalink                                                                 |  13 kB  00:00:00     
epel                                                                                 | 4.7 kB  00:00:00     
1060 packages excluded due to repository priority protections
Resolving Dependencies
--> Running transaction check
---> Package tmux.x86_64 0:1.8-4.12.amzn1 will be installed
--> Finished Dependency Resolution

Dependencies Resolved

============================================================================================================
 Package              Arch                   Version                        Repository                 Size
============================================================================================================
Installing:
 tmux                 x86_64                 1.8-4.12.amzn1                 amzn-main                 254 k

Transaction Summary
============================================================================================================
Install  1 Package

Total download size: 254 k
Installed size: 543 k
Downloading packages:
tmux-1.8-4.12.amzn1.x86_64.rpm                                                       | 254 kB  00:00:00     
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  Installing : tmux-1.8-4.12.amzn1.x86_64                                                               1/1 
  Verifying  : tmux-1.8-4.12.amzn1.x86_64                                                               1/1 

Installed:
  tmux.x86_64 0:1.8-4.12.amzn1                                                                              

Complete!

どうやらインストール出来ました!

再度、テストを実行してみると。。。??

ec2-user:~/environment/sample_app (master) $ rails t
Running via Spring preloader in process 19512
Run options: --seed 63909

# Running:

..................

Finished in 0.582820s, 30.8843 runs/s, 61.7686 assertions/s.

18 runs, 36 assertions, 0 failures, 0 errors, 0 skips

エラー文が消え、スッキリとテストを実行できるようになりました!!

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

railsチュートリアル1章 herokuへのデプロイに七転八倒。

railsチュートリアル1章後半のherokuへのデプロイにつまづきまくったので、2度とここでつまづかないようにまとめておきます。

railsチュートリアル1.5で

git push heroku master

を実行した後に生成されたアドレスをブラウザで開こうとしたら、
Application Errorの文字が、、、

下に書いてある
heroku logs --tail
を実行しても、

2019-04-02T08:45:11.178789+00:00 heroku[router]: at=error code=H10 desc="App crashed" method=GET path="/" host=cherry-pudding-18708.herokuapp.com request_id=418c602d-6211-4552-9488-abbfd39fae16 fwd="115.69.237.151" dyno= connect= service= status=503 bytes= protocol=https
2019-04-02T08:45:11.634170+00:00 heroku[router]: at=error code=H10 desc="App crashed" method=GET path="/favicon.ico" host=cherry-pudding-18708.herokuapp.com request_id=7d395db1-345a-40ad-833c-8e770f0b8b7b fwd="115.69.237.151" dyno= connect= service= status=503 bytes= protocol=https
^C

何言ってるか分かりません。笑

ここで
heroku run rails console
を実行。

`Add `gem 'sqlite3'`to your Gemfile (and ensure its version is at the minimum required by ActiveRecord). (Gem::LoadError)

の文字が。

railsチュートリアルのいうことを正確に聞いてきた人には分かりますが、sqlite3はGemfileで実行してますよ!?!?

ここで、またググる。

すると、ここの記事に答えに繋がりそうなことが、、、!
Rails Tutorial

原因

.bundle/config の中身が
BUNDLE_WITHOUT: "production"
になっていることが原因らしい。

あと、database.ymlも変更する必要があるっぽい。


対策

.bundle/configってどこにあるんだと思って探したけど、どこにも見つからなかったので、c9コマンドでそこまで移動。
BUNDLE_WITHOUT: "production"

#BUNDLE_WITHOUT: "production"
に変更。

それでもうまくいかない。
そこで、

datebase.ytml
production:
  <<: *default
  adapter: postgresql
  database: db/production.pg

に変更。

それでもうまくいかない、、、。

一旦、もう一度heroku run rails consoleをしてみると、

Add `gem 'pg'` to your Gemfile (and ensure its version is at the minimum required by ActiveRecord). (Gem::LoadError)

あれ? Add gem 'pg'に変わってる!!
これを解決すれば、デプロイできるぞ、、、と思い、ググるが、答えが見つからない。
そこで、色々いじった後に git push heroku masterを実行してないことに気づき、実行。

これでデプロイが完了した。2日間格闘した甲斐があった。(おそらく、ここまで分からなかったら飛ばして次に行くほうが効率がいい)


まとめ

おそらく、database.ymlの変更が解決法に繋がったと考える。
それと、heroku run rails consoleで確認をすると丁寧にどこが間違っていたのかを教えてくれるので、有効活用していきたい。


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

Qiita初投稿。プログラミングに本腰を入れて1か月でわかってきたこと。

就活を終え、とあるweb系企業に就職することになりました。
webアプリを作ろうと1か月ほどの出来事を初心忘るべからず精神で書き留めることにしました。
この先のアウトプットの第一歩として。
来年の今頃にはしっかりとしたエンジニアの知識を持っていたいなぁ。

就活を終えてまずやったこと

就職までに出来るだけ技術力を身に付けないといけないと思い、Udemyで適当なコースを何個か購入。
Udemy(https://www.udemy.com/)

これできっと自分もかっこいいホームページとか、webアプリケーションとかバンバン作れるぞ!!!と思った

だけど・・・

webページを作る。と言っても色んな事を決めないといけない事に気が付く

  • webページを作るにはそもそも何の言語で作るのかを決めないといけない。
  • なんのwebページを作るかも決まってない
  • なにをしたいかも決まってない

何していいのか、何がベストなのかぜんっぜんわからない!!!

わからない事がわからない問題

そもそもプログラミングと言っても、研究のデータ分析のためにpythonをいじくり回してた程度で、プログラミングが何のために存在しているかわかってない。

恐らく体系的に勉強することが第一歩なのだろうけど...

とりあえずudemyで購入したコースを1個やり遂げよう

出来ない事はとにかく一回やってみようの精神で、こちらのコースをやってみた。

よくわかるRuby on Rails入門-RubyとRailsを基礎から学びWebアプリケーションをネットに公開しよう(https://www.udemy.com/the-ultimate-ruby-on-rails-bootcamp/)

こちらのコースはAWSを使って、RubyとRuby on railsを使って簡単なQアンドAサイトをリリースしてみましょう。という内容。
このコースで学んだことを今度また記事にしてみよう。

このコースを選んだのは、webアプリケーションと調べた時にRubyが便利だよ!みたいな記事を見たから。あとAWSが入社予定の会社で使われているから。今のうちから触ってたら後々重宝するかもしれないと思った。

結果どうなったか

自分で書いたコード(厳密にいえば写経だけど)が実際に動いて、ボタンだとか、質問機能だとかが実装されていくのはとても面白かった。
すげぇええええええ!!これ見たことあるうううううぅぅぅぅ!!ってなった

最終的に実際に誰でもアクセスできるようにして、友達に書き込んでみてもらったりした。
自分以外の人が自分で作ったサイトを使ってくれるのってなんだか不思議な感覚だった。

多分webエンジニアのやりがいってこういうところにあるのかなぁと思ったし、これで喜べるならエンジニアの素養あるんじゃ?ってなった。

今のところわかったこと

  • 簡単なwebアプリケーションの制作
  • webアプリのリリース、デプロイ?(本当はそう呼べないだろうけど、カッコつけのために)
  • Rubyの記法とかもろもろ(まだ全然使いこなせてない)
  • railsを使ってのwebアプリの作り方(MVCアーキテクチャがどう作用するのか)
  • アプリ作りの楽しさ
  • 自分で作ることの大変さ

まとめてみると大事なことを結構得られた気がする

一番わかったのはやることが決まってないとやる気が出ない事

今回はwebアプリを作ることが目的だったけど、何の目標もなしにただ言語だけ学習するのは本当に退屈だと思った。

まだわからないこと

  • サーバー、データベース関連 → SQLとかその辺り。こいつらが何のためにあるのかわかってない
  • AWSの機能について → 今回言われるがままに使ったけど、ローカル環境にわざわざlinux環境を作らなくていいって事くらいしかわかってない
  • linux関連 → 操作方法全然わかんねぇ。そもそも何がわかってないのかがわかってない。優先度どれくらいなんだ
  • railsについて → なんかgemを使うと機能が拡張できるってことは分かった。どんなものがあるかは自分で調べる事っぽい
  • git・Githubについて → コードの編集履歴を残せるってことは知ってる。どう使うかは勉強しないと

最後に。これからやりたいこと

  • 新しいwebアプリケーションの制作 → 就活のための補助アプリとかできないかな
  • HTML5、CSSを使いこなして見栄えがいいものを作れるようになる → まだ色とかリンクとかもわからないから、ハンズオンで身に付けていきたい
  • セキュリティについての勉強 → もしこの先アカウント情報とかを管理するようになった時のために。転ばぬ先の杖
  • 自分のポートフォリオの制作 → ぶっちゃけこれが最優先事項なんじゃないだろうか。

Qiitaでやっていきたいこと

定期的な更新!!!!

せっかく初投稿したんだから定期的に投稿したいし、色んな人に読んでもらいたいし、コメント貰ったり、コメント送りたい

見やすいページとか、いろいろ研究していこうと思います。

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

【Ruby】型変換 "to_?" メソッドのおさらい

Rubyのクラスには、型変換を行うメソッド "to_?"がいくつか存在するので、
どんな物があるのか、どんな使い方なのかを今一度おさらいしたいと思いました。

※下記ソースコードはオンライン実行環境にて実行しておりますが、
 エスケープ等見にくい表現部分は切り取っておりますので実際の結果とは異なる場合がございます。

① to_i 整数変換

p a = 2019.to_i
>> 2019

p a = "2020".to_i
>> 2020

p a = "4/1".to_i
>> 4 # 先頭から数値解釈できる場所まで

p a = "令和元年".to_i
>> 0 # 数値がないため

to_iメソッドは、文字列に限り引数にレシーバの進数の指定ができ(デフォルト10進数)、10進数に変換した整数値を返します。

p a = "ff".to_i(16)
>> 255 # 16進数"ff" -> 10進数255

② to_s 文字列変換

p a = 2019.to_s
>> "2019"

p a = "2020".to_s
>> "2020"

p a = "4/1".to_s
>> "4/1"

p a = "令和元年".to_s
>> "令和元年"

# シンボル
p a = :hello.to_s
>> "hello"

p a = :"hello world".to_s
>> "hello world"

to_sメソッドは、整数に限り、引数に指定した進数に変換した文字列を返すことができます(デフォルト10進数)。
※ to_iとは考え方が逆

p a = 8217.to_s(16)
>> "2019" # 10進数8217 -> 16進数"2019"

③ to_f 浮動小数点数変換

p a = 2019.to_f
>> 2019.0

p a = "3.14".to_f
>> 3.14

p a = "4/1".to_f
>> 4.0 # 先頭から数値解釈できる場所まで

p a = "令和元年".to_f
>> 0.0 # 数値が無いため

④ to_a 配列変換

# 配列
p a = [1,2,3].to_a
>> [1, 2, 3]

# ハッシュ
p a = {"k1"=>"v1", "k2"=>"v2"}.to_a
>> [["k1", "v1"], ["k2", "v2"]]

⑤ to_h ハッシュ変換

# 配列
p a = [[1,2], [3,4]].to_h
>> {1=>2, 3=>4}

# ハッシュ
p a = {"k1"=>"v1", "k2"=>"v2"}.to_h
>> {"k1"=>"v1", "k2"=>"v2"}

⑥ to_sym シンボル変換

p a = "hello".to_sym
>> :hello

p a = :hello.to_sym
>> :hello

p a = "hello world".to_sym
>> :"hello world" # スペースが存在する場合はダブルクォートに囲まれる

#ハッシュ
p a = {:k1=>"v1"}.to_sym
>> NoMethodError # ハッシュに対してなにかできるわけではない

⑦ to_json JSON変換

require "json"

# 配列
p a = [[1,2], [3,4]].to_json
>> [[1,2],[3,4]]

# ハッシュ
p a = {"k1"=>"v1", "k2"=>"v2"}.to_json
>> {"k1":"v1","k2":"v2"}

? 明示的・暗黙的な型変換

???なにそれ???

  • to_ito_int
  • to_sto_str
  • to_ato_ary
  • to_hto_hash
  • (その他)to_ioto_procto_regexp

リファレンスマニュアルにて、長い方(右側)のメソッドを見ると

暗黙の変換が必要なときに内部で呼ばれます。 デフォルトでは定義されていません。

説明のためここに記載してありますが、 このメソッドは実際には Object クラスには定義されていません。
必要に応じてサブクラスで定義すべきものです。

このメソッドを定義する条件は、

・"対象の型"が使われるすべての場面で代置可能であるような、
・"対象の型"そのものとみなせるようなもの

という厳しいものになっています。

class Foo
 def to_int
   1
 end
end

ary = [:a, :b, :c]
p(ary[Foo.new]) # => :b

となにやら難しいことが書いてありますが、例文を見てもわかるように非常にシンプルで、
その型(to_intならint)を”暗黙的に”解釈できる際に使えるよと言っています。
(あまり使い道が思いつかない・・・)

短い方(左側)は暗黙的の対義語として明示的と呼ばれ、使い方は上に書いたとおりです。

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

【sass変換サイト】cssからsassにコンパイルできるサイト

Vue+Webpack+Sass使った開発があるんだがsassが全然わからん(PG歴半年)

alt

sassを使うメリット

・コードがシンプルらしい
・変数の使用が可能らしい
・コードを使い回しができるらしい
・if文・while文が使えるらしい
・四則演算ができるらしい

参考にしたサイト
https://qiita.com/m0nch1/items/b01c966dd2273e3f6abe

                ↓↓↓↓↓こんなん↓↓↓↓↓↓

スクリーンショット 2019-04-02 15.07.50.png

※他にいいのあったら教えてください

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

Ruby InvalidURIError 〜単純な落とし穴〜

はじめに

Qiitaに初めて投稿します。日々研究室で過ごす中で体験したトラブル等、ちょっとしたことを備忘録的に書いて投稿していけたらと思います。
まだまだ勉強を始めたばかりなので、間違いなどがありましたら、ご指摘いただけると幸いです。
それでは本題に入ります。

背景

Web上にあるデータを取得するRubyスクリプトをCrontabにより毎日実行している。
これまではUbuntu14.04LTSを使っていたが、間も無くサーポートが切れるということで、新たにUbuntu18.04をクリーンインストールし、環境構築を行なった。

ちゃんと動かない...

OSのバージョンは大幅に変化したが、今まで通りにスクリプトを実行できる環境に戻した...つもりだった。
が、ここで問題が発生。

順調にデータ取得のログが表示されていたが、スクリプトが途中で止まる...なぜ...??

エラーの内容はこんな感じ。(URI省略)

/usr/lib/ruby/2.5.0/uri/rfc3986_parser.rb:67:in `split': bad URI(is not URI?): https://〜〜 (URI::InvalidURIError)

InvalidURIError...え!!?? URI間違ってる???
OS変えるまでちゃんと動いてたのに?なんで?

プログラム内容

そもそもこのプログラムでは、
取得したい情報のあるURIをCSVファイルにまとめて保存されており、URIの日時部分をスクリプトで変更することにより毎日データを取得している。

取得するデータ数が少しなら手作業でもいいが(よくない)、数百(もっと?)あるので手作業なんてやってられない。

バグ出し

まず...

Ubuntuのバージョンを上げるまではきちんと動いていた。
それに新しい環境下でデータの取得が1つもできないわけではなく、途中でエラーが起きる。

まず考えたのが、突然サイトがなくなった、もしくはURIが違うという可能性。

そこで、エラーで表示されているURIをブラウザにコピペ...

ちゃんとページ表示されるやん...!!

つぎに...

そこで次に考えたのが、Rubyのバージョンが今と前のOSで違う可能性。
しかし、アホな自分は前の環境で使っていたRubyのバージョンを確認していない。

Rubyのバージョンが違うことが影響しているなら、最初からエラーが出てもいい気がするが...

なんて考えながら、URI情報が保存されているCSVファイルを開き、エラーをはいているURIを確認。

ぱっと見何もおかしくない...
空白が入っているようにも見えへんし〜...

ん?URIの末尾に半角スペースがあるぞ...

もしかしてこれのせいか...?

他のURIの行には空白は無く、該当のURIにのみ空白が入っていた。

この空白を削除し、スクリプトを実行。

結果

ちゃんと動いた〜〜(^^)/

やはり、エラーの理由はURI末尾の空白だったようだ。

OSのバージョン変更に伴いRubyのバージョンが変わったことで、Rubyの空白処理が変わったのでしょうか??

何はともあれ、これでまた毎日データを取得できそうでよかった。

反省

今回のトラブルはおそらくRubyのバージョン変更によるものだと考える。
自分の環境管理はしっかりしないといけないと感じた。
バージョンなんてどれでも大概一緒や〜なんて考えないようにしよう...

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

もそ、プログラミングを学ぶ【Ruby & Rails復習編】〜どんどんエラーを解いてみよう!〜

エラーを克服せよ

エラー解決の数をこなしていくうちに、プログラミングスキルが徐々に覚醒しつつある もそ。
前はあまりにエラーが解決できず奇声を発しそうになっていたのですが、だんだんと考え方のコツをつかんで落ち着きを取り戻した今、波紋の呼吸を修得しつつあります。
プログラミングだし、スタンド名はOKコンピューターとかがいいかな〜

無題1180.jpeg

...さっそく話がそれてしまいました。
今回も引き続き、エラーを解決していこうと思います。

エラー発生!名探偵もそ、出動!

まずはエラー画面を見てみましょう。

0403.png

精神を乱さず、まずは冷静に英文を読みます。

1行目を読んでみると「NoMethodError in Tweets#index」と表示されていますよね。
これは、

「tweetsコントローラのindexアクションを読み込んだ時に、メソッドがなかったよ〜」

という内容です。
さらに2行目を見てみると、「部分テンプレートファイルの6行目に記述しているtweet__pathの部分でメソッドが定義されていない」と出ています。

仮説を立ててみる

これまでの経験と勘から、”NoMethodError”と出ているのでメソッドの名前とコントローラのアクションが一致していないのでは?と考えました。

そこで改めて部分テンプレートファイルの記述を確認してみます。
でも、詳細ページを表示するためのHTTPメソッドはgetで合っているはず。

う〜〜む、これは難題な予感です。

無題1178.jpeg

名探偵もそ、ターミナルを捜査(操作)

ここでエラーの原因箇所がビューファイルにない可能性も考え、ターミナルにrake routesコマンドを実行しました。
rake routesすることでルーティング名やルートパス、コントローラとアクションに対応するHTTPメソッドを確認することができます。

ここで実際のrake routes結果を見てみましょう。
0403-1.png

表示されている内容についてカンタンに説明します。
薄きみどり色(もそカラーをイメージして設定してみました)の1列目の表記ですが、

・Prefix...ルーティング名
・Verb...HTTPメソッド
・URI Pattern...ルートパス名
・Controller#Action...コントローラ名#アクション名

を表しています。

画像内の下の方、ブルーボックスで中途半端に選択してしまっている

tweets  GET   /tweets(.:format)  tweets#index

この部分が今回エラーを起こした処理になります。
思ったとおり、HTTPメソッドはやはり間違っていないようです。
原因はどこにあるのか...ターミナルの実行画面を見ていると、もそはハッと気がつきました。

「あれ?ほかのtweetsコントローラ全然表示されてなくない??」

本来であれば、tweetsコントローラのファイルで定義した各アクションが並ぶはずです。
でも実行結果をよく見ると、tweetsコントローラのindexアクションしか表示されていません

エラー解決編

この結果を踏まえて、原因はルーティングにあると断定しました。

コントローラファイルの記述がごっそり抜けている可能性もなくはないのですが、この部分は30弱の記述があります。
この部分がまるっと無くなっていたらさすがに気がつくだろうと考え、今回は先にルーティングを検証することにしました。

さっそくルーティングファイルを開き、記述を確認してみます。

error_routing.rb
Rails.application.routes.draw do
  devise_for :users
  root  "tweets#index" do

resources :tweets 
  resources :comments, only: [:create]
end
  resources :users, only: [:show]
end

よ〜〜く見てみましょう。

...
...!!!
3行目のdoの記述位置、間違ってるやん!!

resourcesメソッドはコントローラとアクションの設定をかんたんに行ってくれる便利なメソッドで、基本となるコントローラの7つのアクション名に対して、ルーティングを自動で生成してくれます。
詳しくはこちらの記事へ

記述の仕方はこんな感じです。↓

resources :コントローラ名 do

さらにここでは、resourcesメソッドをネストという入れ子構造にしています。
そんなわけで、正しい記述はこちら↓

routing.rb
Rails.application.routes.draw do
  devise_for :users
  root  "tweets#index" 

resources :tweets do
  resources :comments, only: [:create]
end
  resources :users, only: [:show]
end

なかなかの難題でしたが、名探偵もそ、見事に解決しました!

無題1185.jpeg

--
エラー解決は難しいですが、解けるとなるほど面白い!もっと頑張ろう!と前向きな気持になれます。
次の目標も決まってきたので(※スタンド発動ではありません)、復習をしつつエラーに挑戦していこうと思います。
...続く。

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

docker-composeで起動しているRubyアプリケーションをデバッグする方法

自分はRubyのデバッグにはpry-byebugを利用しています。
pry-byebugは、デバッグしたいポイントにbinding.pryを仕込むことでその時点でアプリが停止し、デバッグをすることができます。

問題点

フォアグラウンドでdocker-compose upすると、binding.pryを仕込むことで以下のようなログが表示されます。
アプリの実行は中断されますが、あくまでログが表示されているだけなのでこのコンソール上でデバッグはできません。

web_1            |      9: def index
web_1            |     10:   binding.pry
web_1            |  => 11: end
web_1            |

解決方法

docker attachでRubyアプリケーションのコンテナに入るとデバッグできます。

$ docker attach ${container_id}

### エンターキーを押すとデバッグができる
[1] pry(#<HomeController>)>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RailsでSQLite3を複数Thread/Processから同時WriteするときにBusyExceptionを回避

TL;DR

RailsのDBにSQLite3を使ってしまうと,Redis等々で同時WriteするとSQLite3::BusyExceptionが発生する.
database.ymltimeoutも効かぬらしい.
仕方がないので自分でプロセス横断の排他Lockを実装する.

やったこと

ActiveRecord.save/.save!でfalseが返ったり例外発生したりするので,成功するまでRetryするとかしても良かったかもしれませんが,あまり格好良くないので,排他Lock制御します.

Rubyの排他Lockの実装自体はぐぐるといっぱい出てくるので,それらを参考に↓のようなblockを排他実行させるClassを用意しました.

LockBlock.rb
require 'tmpdir'

class LockBlock
  class << self

    def locked(lock_file_name)
      File::open(File::join(Dir::tmpdir, lock_file_name), 'w') { |file|
        begin
          file.flock(File::LOCK_EX)
          yield
        ensure
          file.flock(File::LOCK_UN)
        end
      }
    end

  end
end

この排他Lockで守った状態でActiveRecord.save/.save!するためのMethodをApplicationRecordに用意します.

app/models/application_record.rb
def safe_save!
  LockBlock::locked(`db_lock`) {
    self.save!
  }
end

あとは各Modelの.save/.save!やってるところをsafe_save!に置き換えれば完了.

これでいいのか疑問

ひとまず ↑ の対応を入れてRedis使って平行動作させて様子見してますが,FileのLock/Unlockのアトミック性とかよくわかっておらず,これで100%保証されてるのかどうかが不明...

# そもそも`SQLite3やめいとかは言わないように...
# 最初はシンプル機能のお手軽Appになるはずやったんや...

---///

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

「Long Life RailsApps」というテーマで登壇した話 (前編)

少し前ですが、Reproさんのイベントで登壇させていただきました。
その際に使用したスライドの共有と、深掘りをしたいと思います。

スライド

thumbnail

まずは耳障りの良いKeepからいきます。

Keep

リリースしたり、効果が良かったりすると事業側のメンバーから感謝される

素晴らしく良い雰囲気で仕事できます。何気ない一言で救われる場面が多々あります。
(自分は特にちょろい気がする)

Githubフローをちゃんと回せている

当然の話ではありますが、事実良い習慣なのでKeepしたいです。

開発環境をDockerで構築できるようにした

新しいメンバーが参画する時にめちゃくちゃ楽になりました。特定のバージョンのミドルウェアを使用していたりするとセットアップ手順のアップデートが大変なのですがコマンド数回でセットアップできるようになりました。

効率的にレビューをするために色々ルールを設けた

2名以上のレビュアーに見てもらうというローカルルールがあるのですが、一日10プルリクエストほどレビューしないといけないので気を抜くとすぐ溢れてしまいます。
レビューをする時間を決めたり、レビュアー毎に権限を設け この人のレビューを貰わないとリリースできない というルールを設けました。
権限のあるレビュアーはしっかりと見ますし、通常のメンバーはレビューに対して重い責任を負わずに済みます。
良かったのは メンバーが軽い気持ちでレビューができ、レビュアーはメンバーのレビューを参考にできる という点です。

Jenkinsを使ってデプロイやタスクの実行をできるようにした

DevOps的な。ここはSlackに移行するなどTryが挙げられそうなKeepです。

RspecとSeleniumが主要な機能をカバーした

テストのカバレッジが極端に低かったので、サービス(ビジネス)として障害になってはいけないところのテストを厚くしました。ユーザーの基本フローを網羅することで実装者、事業者が安心して過ごすことができるようになりました。

Reviewdogで自動的にコードの書き方を指摘されるのでロジックに対して議論することができる

導入はぐぐっていただけると助かります。

ドキュメント管理システムを導入して非エンジニアも巻き込んだ

色々なツールを試しましたが、カスタマイズが可能なオープンソースに落ち着きました。
API連携も見据えた動きができています。


ここからは直近のプロジェクトを振り返ってみた結果

プロジェクトのキックオフをすることでコアメンバーの意識を統一できた

なんだかんだキックオフは大事。ゆるゆると始まるプロジェクトはだいたい炎上する気がします。

プロジェクト進行においてシンプルなルールだけ決めて守るようにした

blameしない、前を向く、などプロジェクトマネジメントの基本を守りました。
当然ながら良い習慣だと思います。

何のためのプロジェクトなのかを明確にした

めちゃくちゃKeepです。意思決定のスピードが格段に向上し、方向性が明確なので決定事項について背景を説明することができるので、
メンバーが納得感を持って業務に当たることができます。

ガントチャートをすぐ更新できるように専用のツールを導入した

Backlogを普段使っているのですがガントチャートの更新が非常に多かったのでxPlanというツールを導入しました。
クリティカルパスの設定も簡単にできるのでおすすめです。(有料)

Backlogは基本的にFIXした内容とスケジュールのみ記載するようにした

ガントチャートと分業したので相応の使い方にまとめました。
情報が散らばらないのは良いことです。

Try

KeepからTryに挙げられるのは以下です。

  • Jenkinsで運用しているものはSlackやクラウドサービスへ移行できそう
  • Rspec, Seleniumのカバレッジを増やしたい
    • とは言えテストリソースの上限はあるのでバランスを取るべき
  • 開発用DockerImageを配布できるようにしたい
  • Reviewdogはタイミングによって邪魔な場合があるので任意での実行も視野に入れる
  • ドキュメント管理者が不在なので責任者を据えたい
  • 進行管理の雑務に時間を割くことに対する心理的ハードルを下げたい
    • ガントチャート、Github、Backlog、スプレッドシートを使って進行管理すると情報が分散して効率が下がるポイントがある
    • プロジェクト管理及びドキュメント管理のベストプラクティスを探る

重い話になるProblemは後編で綴っていきたいと思います。

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

Ruby 標準入力から値を受け取る方法

はじめに

Paizaのプログラミング問題で標準入力を使い、値を持ってくる方法がいくつかあったのでメモ

前提

使用する言語はRubyです

一行に一要素だけしか存在しない場合

標準入力
Tokyo
line = gets
p line
出力結果
"Tokyo"

一行に複数要素存在する場合

標準入力
Tokyo Osaka Kyoto
line = gets.split(' ')
p line
出力結果
["Tokyo", "Osaka", "Kyoto"]

splitを使用することで3つの要素をそれぞれ別のものとして配列に格納しています

※splitの使い方

splitはsplit('')のシングルクォート内の文字で文字列を分解する
例えば、Tokyoという文字をそれぞれ一文字ずつ取得したければ、シングルクォートの中に何も入れずにすると下記のように取得できる

標準入力
Tokyo
line = gets.split('')
p line
出力結果
["T", "o", "k", "y", "o"]

複数行に一つずつ要素が存在する場合

標準入力
Tokyo
Osaka
Kyoto
line = readlines
len = line.length
i = 0

while i < len
    line[i] = line[i].chomp
    i += 1
end

p line 
出力結果
["Tokyo", "Osaka", "Kyoto"]

上記のものの簡潔な書き方として、mapを使用したものがあります

標準入力
Tokyo
Osaka
Kyoto
line = readlines.map(&:chomp)
p line 
出力結果
["Tokyo", "Osaka", "Kyoto"]

複数行に複数要素が存在する場合

標準入力
Tokyo Osaka Kyoto
Japan USA China
line = readlines
len = line.length
i = 0

while i < len
    line[i] = line[i].chomp.split(' ')
    i += 1
end

p line
出力結果
[["Tokyo", "Osaka", "Kyoto"], ["Japan", "USA", "China"]]

上記のものの簡潔な書き方として以下のものもあります

標準入力
Tokyo Osaka Kyoto
Japan USA China
lines = []
while line = gets
    lines << line.chomp.split(' ')
end
p lines
出力結果
[["Tokyo", "Osaka", "Kyoto"], ["Japan", "USA", "China"]]

while line = getsとすることで、標準入力の値を全て取得するまで繰り返されます

出力結果を見ると、受け取った値は文字列なので数値を受け取りたい場合は次のようにします

数値として受け取りたい場合

一行に一要素だけしか存在しない

標準入力
123
line = gets.to_i
p line
出力結果
123

一行に複数要素存在する場合

標準入力
1 2 3
line = gets.split(' ')
p line
出力結果
["1", "2", "3"]

このままだと文字列として扱われるので、mapを使用して数値型の配列に変換します

標準入力
1 2 3
line = gets.split(' ').map(&:to_i)
p line
出力結果
[1, 2, 3]

複数行に一つずつ要素が存在する場合

標準入力
1
2
3
line = readlines.map(&:to_i)
p line 
出力結果
[1, 2, 3]

複数行に複数要素が存在する場合

標準入力
1 2 3
4 5 6
lines = []
while line = gets
    lines << line.chomp.split(' ').map(&:to_i)
end
p lines
出力結果
[[1, 2, 3], [4, 5, 6]]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

compass使用時にパスに日本語が含まれてるとエラーが出る

ディレクトリ(フォルダ)名をアルファベットにすれば動く

/Users/hoge/Documents/HTML/アーカイブ/html/src
        ↓
/Users/hoge/Documents/HTML/arcv/html/src

zip圧縮した時にファイル名こうなった模様

以下自分が検索しやすいようにエラー貼り付け

DEPRECATION WARNING on line 87 of /Users/username/.rbenv/versions/2.x.x/lib/ruby/gems/2.x.x/gems/compass-core-1.0.3/stylesheets/compass/css3/_deprecated-support.scss:
#{} interpolation near operators will be simplified in a future version of Sass.
To preserve the current behavior, use quotes:

unquote('"\$moz-"#{\$experimental-support-for-mozilla} "\$webkit-"#{\$experimental-support-for-webkit} "\$opera-"#{$experimental-support-for-opera} "\$microsoft-"#{\$experimental-support-for-microsoft} "\$khtml-"#{\$experimental-support-for-khtml}')

You can use the sass-convert command to automatically fix most cases.

DEPRECATION WARNING on line 92 of /Users/username/.rbenv/versions/2.x.x/lib/ruby/gems/2.x.x/gems/compass-core-1.0.3/stylesheets/compass/css3/_deprecated-support.scss:
#{} interpolation near operators will be simplified in a future version of Sass.
To preserve the current behavior, use quotes:

unquote('"\$ie6-"#{\$legacy-support-for-ie6} "\$ie7-"#{\$legacy-support-for-ie7} "\$ie8-"#{$legacy-support-for-ie8}')

You can use the sass-convert command to automatically fix most cases.

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

Rails初学者が次に覚えるべきルーティング

ルーティングを制するものはRailsを制す!

目次

・rootとなるURLを指定する
・パスごとに個々のルーティングを設定する
・resources
 ・id以外のパスを指定するparamオプション
・単数 resource
・resourcesのネスト
・アクションの絞り込み
 ・onlyオプション
 ・exceprオプション
・アクションの追加
 ・member
 ・collection
・ルーティングをまとめる
 ・namespace
 ・scope
 ・module
・おわりに

rootとなるURLを指定する

routes.rb
root to: "toppages#index"
生成されるパス
Prefix Verb URI Pattern Controller#Action
  root GET  /           toppages#index

パスごとに個々のルーティングを設定する

routes.rb
get 'users/:id', to: 'users#show', as: 'users'
生成されるパス
Prefix Verb URI Pattern          Controller#Action
 users GET  /users/:id(.:format) users#show

asオプションを使うことでルート名(Prefix)を指定することができます。

resources

RESTfulなURLを生成できるresourcesはルーティングの基本です。

routes.rb
resources :users
生成されるパス
   Prefix Verb   URI Pattern               Controller#Action
    users GET    /users(.:format)          users#index
          POST   /users(.:format)          users#create
 new_user GET    /users/new(.:format)      users#new
edit_user GET    /users/:id/edit(.:format) users#edit
     user GET    /users/:id(.:format)      users#show
          PATCH  /users/:id(.:format)      users#update
          PUT    /users/:id(.:format)      users#update
          DELETE /users/:id(.:format)      users#destroy

複数のリソースを同時に定義することもできます。

resources :users, :tweets, :photos

id以外のパスを指定するparamオプション

showやeditなどのidを指定するアクションではparamオプションを使ってid以外の値をパスに指定することもできます。
例えば、idの代わりにuser_nameを使う場合は以下のようになります。

routes.rb
resources :users, param: :user_name
生成されるパス
   Prefix Verb   URI Pattern                      Controller#Action
    users GET    /users(.:format)                 users#index
          POST   /users(.:format)                 users#create
 new_user GET    /users/new(.:format)             users#new
edit_user GET    /users/:user_name/edit(.:format) users#edit
     user GET    /users/:user_name(.:format)      users#show
          PATCH  /users/:user_name(.:format)      users#update
          PUT    /users/:user_name(.:format)      users#update
          DELETE /users/:user_name(.:format)      users#destroy

単数 resource

idの振り分けが不要なページでは単数形のresourceでRESTfulなURLを生成できます。

routes.rb
resource :users
生成されるパス
    Prefix Verb   URI Pattern           Controller#Action
 new_users GET    /users/new(.:format)  users#new
edit_users GET    /users/edit(.:format) users#edit
     users GET    /users(.:format)      users#show
           PATCH  /users(.:format)      users#update
           PUT    /users(.:format)      users#update
           DELETE /users(.:format)      users#destroy
           POST   /users(.:format)      users#create

resourcesのネスト

resourcesブロックの中に別のresourcesをネストさせた場合のルーティングは以下のようになります。usersのRESTfulなURLが生成される他にネストされたtweetsのRESTfulなURLも生成されます。ネストさせる場合も必要に応じて単数resourceを使いましょう。

routes.rb
resources :users do
  resources :tweets
end
生成されるパス
         Prefix Verb   URI Pattern                               Controller#Action

    user_tweets GET    /users/:user_id/tweets(.:format)          tweets#index
                POST   /users/:user_id/tweets(.:format)          tweets#create
 new_user_tweet GET    /users/:user_id/tweets/new(.:format)      tweets#new
edit_user_tweet GET    /users/:user_id/tweets/:id/edit(.:format) tweets#edit
     user_tweet GET    /users/:user_id/tweets/:id(.:format)      tweets#show
                PATCH  /users/:user_id/tweets/:id(.:format)      tweets#update
                PUT    /users/:user_id/tweets/:id(.:format)      tweets#update
                DELETE /users/:user_id/tweets/:id(.:format)      tweets#destroy

          users GET    /users(.:format)                          users#index
                POST   /users(.:format)                          users#create
       new_user GET    /users/new(.:format)                      users#new
      edit_user GET    /users/:id/edit(.:format)                 users#edit
           user GET    /users/:id(.:format)                      users#show
                PATCH  /users/:id(.:format)                      users#update
                PUT    /users/:id(.:format)                      users#update
                DELETE /users/:id(.:format)                      users#destroy

アクションの絞り込み

resourcesを使うとRESTfulなURLを簡単に生成できますが、必ずしも全てのアクションが必要とは限りません。不要なデフォルトアクションはオプションで絞り込みましょう。

only オプション

使用するデフォルトアクションが少ない場合はonly オプションが便利です。

routes.rb
resources :users, only: [:index, :show]
生成されるパス
Prefix Verb URI Pattern          Controller#Action
 users GET  /users(.:format)     users#index
 user GET  /users/:id(.:format) users#show

except オプション

使用するデフォルトアクションが多い場合はexcept オプションが便利です。

routes.rb
resources :users, except: [:show, :destroy]
生成されるパス
   Prefix Verb  URI Pattern               Controller#Action
    users GET   /users(.:format)          users#index
          POST  /users(.:format)          users#create
 new_user GET   /users/new(.:format)      users#new
edit_user GET   /users/:id/edit(.:format) users#edit
     user PATCH /users/:id(.:format)      users#update
          PUT   /users/:id(.:format)      users#update

アクションの追加

Rails標準のindex, show, new, edit, create, update, destroy 以外のアクションを追加する場合にはmemberまたはcollectionを使います。
両者にはmemberではidを伴うパスが生成され、collectionではidのないパスが生成されるという違いがあります。

member

memberはidを伴うパスの生成に使います。

routes.rb
  resources :users do
    member do
      get :search
    end
  end
追加で生成されるパス
     Prefix Verb   URI Pattern                 Controller#Action
search_user GET    /users/:id/search(.:format) users#search
~省略~

collection

idの指定をしないパスの生成にはcollectionを使います。

routes.rb
  resources :users do
    collection do
      get :search
    end
  end
追加で生成されるパス
      Prefix Verb   URI Pattern               Controller#Action
search_users GET    /users/search(.:format)   users#search
~省略~

ルーティングをまとめる

続いてルーティングをまとめていきます。ルーティングをまとめることでアプリケーション全体の構成も整理されます。

namespace

ルーティングを名前空間namespaceのブロックで囲むとパスとコントローラーを指定のディレクトリ配下におくことができます。

routes.rb
namespace :admin do
  resources :users
end
生成されるパス
         Prefix Verb   URI Pattern                     Controller#Action
    admin_users GET    /admin/users(.:format)          admin/users#index
                POST   /admin/users(.:format)          admin/users#create
 new_admin_user GET    /admin/users/new(.:format)      admin/users#new
edit_admin_user GET    /admin/users/:id/edit(.:format) admin/users#edit
     admin_user GET    /admin/users/:id(.:format)      admin/users#show
                PATCH  /admin/users/:id(.:format)      admin/users#update
                PUT    /admin/users/:id(.:format)      admin/users#update
                DELETE /admin/users/:id(.:format)      admin/users#destroy

scope

パスのみを指定のディレクトリ配下におく場合はscopeを使います。

routes.rb
scope '/admin' do
  resources :users
end
生成されるパス
   Prefix Verb   URI Pattern                     Controller#Action
    users GET    /admin/users(.:format)          users#index
          POST   /admin/users(.:format)          users#create
 new_user GET    /admin/users/new(.:format)      users#new
edit_user GET    /admin/users/:id/edit(.:format) users#edit
     user GET    /admin/users/:id(.:format)      users#show
          PATCH  /admin/users/:id(.:format)      users#update
          PUT    /admin/users/:id(.:format)      users#update
          DELETE /admin/users/:id(.:format)      users#destroy

module

コントローラーのみを指定のディレクトリ配下におく場合はmoduleを使います。

routes.rb
scope module: :admin do
  resources :users
end
生成されるパス
   Prefix Verb   URI Pattern               Controller#Action
    users GET    /users(.:format)          admin/users#index
          POST   /users(.:format)          admin/users#create
 new_user GET    /users/new(.:format)      admin/users#new
edit_user GET    /users/:id/edit(.:format) admin/users#edit
     user GET    /users/:id(.:format)      admin/users#show
          PATCH  /users/:id(.:format)      admin/users#update
          PUT    /users/:id(.:format)      admin/users#update
          DELETE /users/:id(.:format)      admin/users#destroy

おわりに

細かいルーティングのオプションは他にもありますが、まずはここで紹介したものが組み合わせて使えると良いと思います。ありがとうございました。

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

Rails ルーティングのすべて

※ルーティングのすべてと言っておきながら「すべて」ではありません。ごめんなさい。

ルーティングを制するものはRailsを制す!

目次

・rootとなるURLを指定する
・パスごとに個々のルーティングを設定する
・resources
 ・id以外のパスを指定するparamオプション
・単数 resource
・resourcesのネスト
・アクションの絞り込み
 ・onlyオプション
 ・exceprオプション
・アクションの追加
 ・member
 ・collection
・ルーティングをまとめる
 ・namespace
 ・scope
 ・module
・おわりに

rootとなるURLを指定する

routes.rb
root to: "toppages#index"
生成されるパス
Prefix Verb URI Pattern Controller#Action
  root GET  /           toppages#index

パスごとに個々のルーティングを設定する

routes.rb
get 'users/:id', to: 'users#show', as: 'users'
生成されるパス
Prefix Verb URI Pattern          Controller#Action
 users GET  /users/:id(.:format) users#show

asオプションを使うことでルート名(Prefix)を指定することができます。

resources

RESTfulなURLを生成できるresourcesはルーティングの基本です。

routes.rb
resources :users
生成されるパス
   Prefix Verb   URI Pattern               Controller#Action
    users GET    /users(.:format)          users#index
          POST   /users(.:format)          users#create
 new_user GET    /users/new(.:format)      users#new
edit_user GET    /users/:id/edit(.:format) users#edit
     user GET    /users/:id(.:format)      users#show
          PATCH  /users/:id(.:format)      users#update
          PUT    /users/:id(.:format)      users#update
          DELETE /users/:id(.:format)      users#destroy

複数のリソースを同時に定義することもできます。

resources :users, :tweets, :photos

id以外のパスを指定するparamオプション

showやeditなどのidを指定するアクションではparamオプションを使ってid以外の値をパスに指定することもできます。
例えば、idの代わりにuser_nameを使う場合は以下のようになります。

routes.rb
resources :users, param: :user_name
生成されるパス
   Prefix Verb   URI Pattern                      Controller#Action
    users GET    /users(.:format)                 users#index
          POST   /users(.:format)                 users#create
 new_user GET    /users/new(.:format)             users#new
edit_user GET    /users/:user_name/edit(.:format) users#edit
     user GET    /users/:user_name(.:format)      users#show
          PATCH  /users/:user_name(.:format)      users#update
          PUT    /users/:user_name(.:format)      users#update
          DELETE /users/:user_name(.:format)      users#destroy

単数 resource

idの振り分けが不要なページでは単数形のresourceでRESTfulなURLを生成できます。

routes.rb
resource :users
生成されるパス
    Prefix Verb   URI Pattern           Controller#Action
 new_users GET    /users/new(.:format)  users#new
edit_users GET    /users/edit(.:format) users#edit
     users GET    /users(.:format)      users#show
           PATCH  /users(.:format)      users#update
           PUT    /users(.:format)      users#update
           DELETE /users(.:format)      users#destroy
           POST   /users(.:format)      users#create

resourcesのネスト

resourcesブロックの中に別のresourcesをネストさせた場合のルーティングは以下のようになります。usersのRESTfulなURLが生成される他にネストされたtweetsのRESTfulなURLも生成されます。ネストさせる場合も必要に応じて単数resourceを使いましょう。

routes.rb
resources :users do
  resources :tweets
end
生成されるパス
         Prefix Verb   URI Pattern                               Controller#Action

    user_tweets GET    /users/:user_id/tweets(.:format)          tweets#index
                POST   /users/:user_id/tweets(.:format)          tweets#create
 new_user_tweet GET    /users/:user_id/tweets/new(.:format)      tweets#new
edit_user_tweet GET    /users/:user_id/tweets/:id/edit(.:format) tweets#edit
     user_tweet GET    /users/:user_id/tweets/:id(.:format)      tweets#show
                PATCH  /users/:user_id/tweets/:id(.:format)      tweets#update
                PUT    /users/:user_id/tweets/:id(.:format)      tweets#update
                DELETE /users/:user_id/tweets/:id(.:format)      tweets#destroy

          users GET    /users(.:format)                          users#index
                POST   /users(.:format)                          users#create
       new_user GET    /users/new(.:format)                      users#new
      edit_user GET    /users/:id/edit(.:format)                 users#edit
           user GET    /users/:id(.:format)                      users#show
                PATCH  /users/:id(.:format)                      users#update
                PUT    /users/:id(.:format)                      users#update
                DELETE /users/:id(.:format)                      users#destroy

アクションの絞り込み

resourcesを使うとRESTfulなURLを簡単に生成できますが、必ずしも全てのアクションが必要とは限りません。不要なデフォルトアクションはオプションで絞り込みましょう。

only オプション

使用するデフォルトアクションが少ない場合はonly オプションが便利です。

routes.rb
resources :users, only: [:index, :show]
生成されるパス
Prefix Verb URI Pattern          Controller#Action
 users GET  /users(.:format)     users#index
 user GET  /users/:id(.:format) users#show

except オプション

使用するデフォルトアクションが多い場合はexcept オプションが便利です。

routes.rb
resources :users, except: [:show, :destroy]
生成されるパス
   Prefix Verb  URI Pattern               Controller#Action
    users GET   /users(.:format)          users#index
          POST  /users(.:format)          users#create
 new_user GET   /users/new(.:format)      users#new
edit_user GET   /users/:id/edit(.:format) users#edit
     user PATCH /users/:id(.:format)      users#update
          PUT   /users/:id(.:format)      users#update

アクションの追加

Rails標準のindex, show, new, edit, create, update, destroy 以外のアクションを追加する場合にはmemberまたはcollectionを使います。
両者にはmemberではidを伴うパスが生成され、collectionではidのないパスが生成されるという違いがあります。

member

memberはidを伴うパスの生成に使います。

routes.rb
  resources :users do
    member do
      get :search
    end
  end
追加で生成されるパス
     Prefix Verb   URI Pattern                 Controller#Action
search_user GET    /users/:id/search(.:format) users#search
~省略~

collection

idの指定をしないパスの生成にはcollectionを使います。

routes.rb
  resources :users do
    collection do
      get :search
    end
  end
追加で生成されるパス
      Prefix Verb   URI Pattern               Controller#Action
search_users GET    /users/search(.:format)   users#search
~省略~

ルーティングをまとめる

続いてルーティングをまとめていきます。ルーティングをまとめることでアプリケーション全体の構成も整理されます。

namespace

ルーティングを名前空間namespaceのブロックで囲むとパスとコントローラーを指定のディレクトリ配下におくことができます。

routes.rb
namespace :admin do
  resources :users
end
生成されるパス
         Prefix Verb   URI Pattern                     Controller#Action
    admin_users GET    /admin/users(.:format)          admin/users#index
                POST   /admin/users(.:format)          admin/users#create
 new_admin_user GET    /admin/users/new(.:format)      admin/users#new
edit_admin_user GET    /admin/users/:id/edit(.:format) admin/users#edit
     admin_user GET    /admin/users/:id(.:format)      admin/users#show
                PATCH  /admin/users/:id(.:format)      admin/users#update
                PUT    /admin/users/:id(.:format)      admin/users#update
                DELETE /admin/users/:id(.:format)      admin/users#destroy

scope

パスのみを指定のディレクトリ配下におく場合はscopeを使います。

routes.rb
scope '/admin' do
  resources :users
end
生成されるパス
   Prefix Verb   URI Pattern                     Controller#Action
    users GET    /admin/users(.:format)          users#index
          POST   /admin/users(.:format)          users#create
 new_user GET    /admin/users/new(.:format)      users#new
edit_user GET    /admin/users/:id/edit(.:format) users#edit
     user GET    /admin/users/:id(.:format)      users#show
          PATCH  /admin/users/:id(.:format)      users#update
          PUT    /admin/users/:id(.:format)      users#update
          DELETE /admin/users/:id(.:format)      users#destroy

module

コントローラーのみを指定のディレクトリ配下におく場合はmoduleを使います。

routes.rb
scope module: :admin do
  resources :users
end
生成されるパス
   Prefix Verb   URI Pattern               Controller#Action
    users GET    /users(.:format)          admin/users#index
          POST   /users(.:format)          admin/users#create
 new_user GET    /users/new(.:format)      admin/users#new
edit_user GET    /users/:id/edit(.:format) admin/users#edit
     user GET    /users/:id(.:format)      admin/users#show
          PATCH  /users/:id(.:format)      admin/users#update
          PUT    /users/:id(.:format)      admin/users#update
          DELETE /users/:id(.:format)      admin/users#destroy

おわりに

細かいルーティングのオプションは他にもありますが、まずはここで紹介したものが組み合わせて使えると良いと思います。ありがとうございました。

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

[Ruby] 真偽値を返すメソッドの実装について

Ruby で true / false を返すメソッドの実装について、いくつか書き方があると思ったのでまとめてみた。

前提

こんな感じの仕様のメソッドを実装するときのことを考える

下記条件を全て満たす場合 "true" を返し、それ以外の場合は "false" を返す、ポイント付与可能かどうかを判定するメソッド
  - ログインユーザーであること
  - ユーザーのステータスコードが "1" であること

<条件を整理する>

logged_in? user.status_code 結果
true 1 true
true 0 false
false 1 false
false 0 false

1. 真面目実装

超真面目に実装するとこうなる。こんな書き方をしたのは久しぶりなので、なんだか気持ち悪い。こういう書き方は Ruby だとあまり見ないかも?

def pointable?
  if logged_in?
    if user.status_code == '1'
      true
    else
      false
    end
  else
    false
  end
end

2. 比較演算子

比較演算子を使って実装する。

def pointable?
  logged_in? && user.status_code == '1'
end

※ Ruby 触り始めは、こんな風に書いていたなー。

def pointable?
  (logged_in? && user.status_code == '1') ? true : false
end

3. ガード節

  • 全て満たす場合に "true" なので、ひとつでも "false" になる条件があったらその時点で return する
  • 結構よく見る
  • ネストが浅くなるので読みやすい
  • 正常系とエラー系の処理が別れていて読みやすい
  • 最後の "true" を書き忘れることがあるが(実際この前書き忘れた)、テストをちゃんと書いていれば気付けるはず
def pointable?
  return false unless logged_in?
  return false if user.status_code != '1'
  true
end

※ if 使うか unless 使うかは好みの問題だと思うけど結構迷う。個人的には下記の2択だったら、前者の方が分かりやすくて好き。

# false を返す、もし user.status_code が '1' でないならば
return false if user.status_code != '1'

# false を返す、そうじゃないなら user.status_code が '1' である
return false unless user.status_code == '1'

4. all? メソッドを使う

こんなのも思いついたけど、どうなんでしょう。

def pointable?
  [logged_in?, user.status_code == '1'].all?
end

まとめ

同じ仕様のメソッドでも、実装の仕方がいくつもあるので考えてみると楽しいです。
個人的には、2. 比較演算子3. ガード節 の書き方をよく見るし読みやすくて好きなのですが、どうなんでしょう。

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

railsでtwitter api ログイン認証を使おうとしたらエラー400でたのでメモっとく

はじめに開発環境は

mac os x
ruby '2.5.2'
'rails', '~> 5.2.3'

を使っています。

何をしたいか

railsでtwitterログイン認証をしたい

どのgemを使ったか?

gem 'omniauth'
gem 'omniauth-twitter'

作業したこと

/.env
API_KEY = "xxxxxxxxxxxxxxxxxxxxxxxxxxx"
API_SECRET = "xxxxxxxxxxxxxxxxxxxxxxxxx"
config/initializers/omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
  provider :twitter, ENV["API_KEY"], ENV["API_SECRET"]
 end

を書いて rails s でrails サーバー起動したら 400が出た

どうやって直したか

400のエラーはそもそもENVで

ENV["API_KEY"], ENV["API_SECRET"]

が取れていないことが問題らしいなのでこれを書いた。

/Gemfile.
gem 'omniauth'
gem 'omniauth-twitter'

でbundle installしたらenvが正常に動作して、エラーが消えた

終わり

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