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

【Rails】destroyメソッドを使用しようとしたら"ArgumentError (wrong number of arguments (given 0, expected 1)):"

はじめに

Railsのdestroyメソッドを使用しようとしたら以下のように引数が1つ必要なのに見つかりませんとエラーが発生しました。
凡ミスですが、記録として残します。

ArgumentError (wrong number of arguments (given 0, expected 1)):

環境

OS: macOS Catalina 10.15.1
Ruby: 2.6.5
Rails: 6.0.2.1

結論:解決法

今回のコードは以下のようになっていました。

def destroy
  posts = Post.where(user_id: 1)
  posts.destroy #ここでエラー発生
end

def destroy
  post = Post.find_by(user_id: 1)
  post.destroy #これは通る
end

このように、find_byにすると通ります。

もしくは、該当データが複数ある場合は

def destroy
  Post.destroy_by(user_id: 1) #これも通る
end

のようにdestroy_byメソッドを使えば該当データをまとめて削除することが出来ます。

原因:destroyは配列を処理できない

whereだと該当するデータが1件であっても配列で返すようになっています。

そのため、配列を処理できないdestroy引数が見つかりませんとエラーを吐いてしまっていたんですね:sweat_smile:

おわりに

最後まで読んで頂きありがとうございました:bow_tone1:

どなたかの参考になれば幸いです:relaxed:

参考にさせて頂いたサイト(いつもありがとうございます)

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

はじめてAWSでデプロイする方法⑦(EC2にgemをインストール)

EC2のメモリを増強する

Swap(スワップ)領域を設定

Swapは、EC2のメモリが限界に達したとき、補う形でメモリの容量を増やす機能です。
デフォルトではSwap領域が設定されていないので、設定しましょう

手順

ホームディレクトリに移行

[ec2-user@ip-172-31-25-189 ~]$ cd 

下記のコマンドを実行

[ec2-user@ip-172-31-25-189 ~]$ sudo dd if=/dev/zero of=/swapfile1 bs=1M count=512

うまくいくと、下記の表示が出ます

512+0 レコード入力
512+0 レコード出力
536870912 バイト (537 MB) コピーされました、 5.19011 秒、 103 MB/秒

次は権限に制限をかけましょう(chmodコマンド)

[ec2-user@ip-172-31-25-189 ~]$ sudo chmod 600 /swapfile1

スワップ(swap)領域を作成する - mkswap

[ec2-user@ip-172-31-25-189 ~]$ sudo mkswap /swapfile1

#成功すると下記の表示が出ます
スワップ空間バージョン1を設定します、サイズ = 524284 KiB
ラベルはありません, UUID=74a961ba-7a33-4c18-b1cd-9779bcda8ab1

スワップ(swap)領域を有効化する - swapon

[ec2-user@ip-172-31-25-189 ~]$ sudo swapon /swapfile1

エラーが出なければ成功です。
エラー「swapon: /swapfile1: スワップヘッダの読み込みに失敗しました: 無効な引数です」が表示された場合は、一つ前の手順に戻ってmkswapコマンドを実施してください。

下記のコマンドを実施してください。
?長いので、気をつけてください

[ec2-user@ip-172-31-25-189 ~]$ sudo sh -c 'echo "/swapfile1  none        swap    sw              0   0" >> /etc/fstab' 

これで完了

参考になる記事:linux スワップ(swap)領域の作成

gemのインストール

まずは、EC2にダウンロードしたWEB Appを開く

[ec2-user@ip-172-31-23-189 www]$ cd  /var/www(作成したディレクトリ)/アプリ名

Rubyのバージョンを確認する

[ec2-user@ip-172-31-23-189 <アプリ名>]$ ruby -v

指定したrubyのバージョンが表示されれば成功です。

ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-linux]

ローカル上のターミナルでbundlerのバージョンを確認する

EC2ではなく、ローカル環境のWEB Appを開き、下記コマンドを実施

$ bundler -v

するとバージョンが表示されます

Bundler version 2.0.2

これと同じバージョンをEC2で入れます。
?下記のバージョンをそのまま (2.0.1)を入れるとエラーが発生するので注意

[ec2-user@ip-172-31-23-189 <アプリ名>]$ gem install bundler -v 2.0.1

EC2でbundle installをして、gemをインストール

[ec2-user@ip-172-31-23-189 <アプリ名>]$ bundle install

エラーがなければ、gemのインストール完了です。

エラーが発生した場合

下記のエラーが表示された場合、インストールするべき bundler -vが間違っています

Traceback (most recent call last):
    2: from /home/ec2-user/.rbenv/versions/2.5.1/bin/bundle:23:in `<main>'
    1: from /home/ec2-user/.rbenv/versions/2.5.1/lib/ruby/2.5.0/rubygems.rb:308:in `activate_bin_path'
/home/ec2-user/.rbenv/versions/2.5.1/lib/ruby/2.5.0/rubygems.rb:289:in `find_spec_for_exe': can't find gem bundler (>= 0.a) with executable bundle (Gem::GemNotFoundException)

エラーがある場合は、アプリ側の bundlerのバージョンを確認してください

$ bundler -v
>Bundler version 2.0.2

[ec2-user@ip-172-31-23-189 <アプリ名>]$ bundler -v
>Bundler version 2.0.1
>バージョンが違うので、エラーがおきます

インストール完了後、下記の表示があった場合

Post-install message from chromedriver-helper:

  +--------------------------------------------------------------------+
  |                                                                    |
  |  NOTICE: chromedriver-helper is deprecated after 2019-03-31.       |
  |                                                                    |
  |  Please update to use the 'webdrivers' gem instead.                |
  |  See https://github.com/flavorjones/chromedriver-helper/issues/83  |
  |                                                                    |
  +--------------------------------------------------------------------+

gem'chromedriver-helper'のサポートが終了しているので、代わりとなるgem 'webdrivers'をインストールすることを推奨しているメッセージとなります。

 group :test do
   # ...
-  gem 'chromedriver-helper'
+  gem 'webdrivers'

参考記事

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

【初心者向け】超簡単ドリンク注文アプリケーション(ドリンクに値段をつけるver)

超簡単ドリンク注文アプリケーションに値段をつけてみる

こちらは前回の続きです。
前回のアプリケーションはこちら
https://qiita.com/pontarou194/items/708452b47464bfcad122

条件
 ・実行時、どのようなドリンクがあるかを表示させる
 ・ドリンクには番号をつける
 ・選ばれた番号と同じドリンク名を表示させる
 ・ドリンクに値段をつける
 ・ドリンクの注文数を訪ねて、入力された値に対して合計額を表示させる

擬似コード

def 
お茶が選ばれた場合、値段を表示
注文数を入力
合計額を表示
end

def 
コーヒーが選ばれた場合、値段を表示
注文数を入力
合計額を表示
end

def 
ビールが選ばれた場合、値段を表示
注文数を入力
合計額を表示
end

def
サイダーが選ばれた場合、値段を表示
注文数を入力
合計額を表示
end

#それぞれのドリンクメニューの値段を残す
otya = 100
coffee = 200
beer = 300
saida = 400

#ここから先、変数をつけたの以外は前回と一緒なので、そのまま記述します
  puts "何を飲みたいですか?"
    drink_menu = ["お茶","コーヒー","ビール","三ツ矢サイダー"]
    drink_menu.each.with_index(1) do |drink_name, number|
      puts "#{number}:#{drink_name}"
    end

  case gets.to_i
    when 1
      otya(otya)
    when 2
      coffee(coffee)
    when 3
      beer(beer)
    when 4
      saida(saida)
    else
      puts "無効な入力値です"
    end

実際にコーディングしてみる

def otya(otya)
  puts "あなたが選んだのはお茶"
  puts "お茶の値段は100円です"
  puts "いくつ注文しますか?"
  order_quantity = gets.to_i
  total_price = order_quantity * otya
  puts "合計#{total_price}円です"
end

def coffee(coffee)
  puts "あなたが選んだのはコーヒー"
  puts "コーヒーの値段は200円です"
  puts "いくつ注文しますか?"
  order_quantity = gets.to_i
  total_price = order_quantity * coffee
  puts "合計#{total_price}円です"

end

def beer(beer)
  puts "あなたが選んだのはビール"
  puts "ビールの値段は300円です"
  puts "いくつ注文しますか?"
  order_quantity = gets.to_i
  total_price = order_quantity * beer
  puts "合計#{total_price}円です"
end

def saida(saida)
  puts "あなたが選んだのは三ツ矢サイダー"
  puts "サイダーの値段は300円です"
  puts "いくつ注文しますか?"
  order_quantity = gets.to_i
  total_price = order_quantity * saida
  puts "合計#{total_price}円です"
end

otya = 100
coffee = 200
beer = 300
saida = 400

  puts "何を飲みたいですか?"
    drink_menu = ["お茶","コーヒー","ビール","三ツ矢サイダー"]
    drink_menu.each.with_index(1) do |drink_name, number|
      puts "#{number}:#{drink_name}"
    end

  case gets.to_i
    when 1
      otya(otya)
    when 2
      coffee(coffee)
    when 3
      beer(beer)
    when 4
      saida(saida)
    else
      puts "無効な入力値です"
    end

変数及び、引数の勉強になるかと思って使ってみました。
otya(otya)が呼び出された際に、otya(otya)メソッドに飛んで、otya(otya)メソッドではotyaの値段を参照しているため、
otyaの値段と入力された数をかけた合計額が表示される。

動作確認

何を飲みたいですか?
1:お茶
2:コーヒー
3:ビール
4:三ツ矢サイダー
3
あなたが選んだのはビール
ビールの値段は300円です
いくつ注文しますか?
4
合計1200円です

今回は以上となります。
引数便利ですよね。

ここは違う、このようにしたほうがセンスが良い等ございましたら、ご指摘いただけますと幸いです。

最後までみていただきありがとうございます。

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

ハッシュから値だけを取り出し、配列にしてください。(valuesメソッドは使用しない)

問題

以下のハッシュから値だけを取り出し、配列にしてください。ただしhashクラスのvaluesメソッドは利用しないものとする。
attr = {name: "小島", age: 32, height: 175, weight: 66}

回答

ハッシュ
1  values = []
2  attr.each do |key, value|
3    values << value
4  end

解説

1行目でvaluesという配列が作成される。
2,3行目で配列の中身をキーと値に分け、順番に値のみをvaluesに入れる処理をする。
4行目でeachメソッドを終了する

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

【実践】SolidusでRails製ECサイトを作ってみる

SolidusはSpreeの後継のRails製オープンソースECサイトのプロジェクト。
OSSを使わないとしても、ECに最低限の機能が入ってるので、自分たちで機能の洗い出しするより、これを参考にした方が早い。

準備

以下をインストールしていない人はインストール

Rubyはバージョンが低いとfiniteのエラーが出ます。
テスト環境ではv2.5.1を使用しました。

brew install sqlite3 imagemagick

1. Railsアプリを作成

rails new myshop --skip_webpack_install
cd myshop

2. Gemfileに以下を追加

Gemfile
gem 'solidus'
gem 'solidus_auth_devise'

'solidus'をrequireすると、以下がまとめてインストールされます。個別にインストールすることも可能なよう。
solidus_core
solidus_api
solidus_frontend
solidus_backend
solidus_sample

bundle install

3. gemのinitialize & マイグレーション

bundle exec rails generate spree:install
# ここでadmin / パスワードを設定します

bundle exec rails generate solidus:auth:install
bundle exec rake railties:install:migrations

bundle exec rake db:migrate

spreeはsolidusの元のOSS eCommerceプロジェクトです。
マイグレーションも作成されます。

4.Viewをオーバーライドする

bundle exec rails generate solidus:views:override

# bundle exec rails generate solidus:views:override --only products/show
# 一部だけカスタマイズする場合はこのようなコマンド

起動してみる

bundle exec rails server

管理画面について

adminユーザーでログインし、/adminにアクセスすると、管理画面が表示されます。

スクリーンショット 2020-01-13 16.41.11.png

APIについて

APIリファレンスはこちら
https://solidus.docs.stoplight.io/

商品のレスポンス例:


{
    "count": 18,
    "total_count": 18,
    "current_page": 1,
    "pages": 1,
    "per_page": 25,
    "products": [
        {
            "id": 1,
            "name": "Ruby on Rails Tote",
            "description": "Soluta sed error debitis repellendus et. Voluptates unde enim qui velit. Libero earum tenetur nulla similique temporibus quod repellendus quibusdam.",
            "available_on": "2020-01-13T07:32:59.433Z",
            "slug": "ruby-on-rails-tote",
            "meta_description": null,
            "meta_keywords": null,
            "shipping_category_id": 1,
            "taxon_ids": [
                3,
                10,
                13,
                20
            ],
            "meta_title": null,
            "total_on_hand": 10,
            "price": "15.99",
            "display_price": "$15.99",
            "has_variants": false,
            "master": {
                "id": 1,
                "name": "Ruby on Rails Tote",
                "sku": "ROR-00011",
                "weight": "0.0",
                "height": null,
                "width": null,
                "depth": null,
                "is_master": true,
                "slug": "ruby-on-rails-tote",
                "description": "Soluta sed error debitis repellendus et. Voluptates unde enim qui velit. Libero earum tenetur nulla similique temporibus quod repellendus quibusdam.",
                "track_inventory": true,
                "cost_price": "17.0",
                "price": "15.99",
                "display_price": "$15.99",
                "options_text": "",
                "in_stock": true,
                "is_backorderable": true,
                "total_on_hand": 10,
                "is_destroyed": false,
                "option_values": [],
                "images": []
            },
            "variants": [],
            "option_types": [],
            "product_properties": [
                {
                    "id": 25,
                    "product_id": 1,
                    "property_id": 9,
                    "value": "Tote",
                    "property_name": "Type"
                },
                {
                    "id": 26,
                    "product_id": 1,
                    "property_id": 10,
                    "value": "15\" x 18\" x 6\"",
                    "property_name": "Size"
                },
                {
                    "id": 27,
                    "product_id": 1,
                    "property_id": 11,
                    "value": "Canvas",
                    "property_name": "Material"
                }
            ],
            "classifications": [
                {
                    "taxon_id": 3,
                    "position": 1,
                    "taxon": {
                        "id": 3,
                        "name": "Bags",
                        "pretty_name": "Categories -> Bags",
                        "permalink": "categories/bags",
                        "parent_id": 1,
                        "taxonomy_id": 1,
                        "taxons": []
                    }
                },
                {
                    "taxon_id": 10,
                    "position": 1,
                    "taxon": {
                        "id": 10,
                        "name": "Rails",
                        "pretty_name": "Brand -> Rails",
                        "permalink": "brand/rails",
                        "parent_id": 2,
                        "taxonomy_id": 2,
                        "taxons": []
                    }
                },
                {
                    "taxon_id": 13,
                    "position": 1,
                    "taxon": {
                        "id": 13,
                        "name": "Bags",
                        "pretty_name": "Categories -> Bags",
                        "permalink": "categories/bags",
                        "parent_id": 1,
                        "taxonomy_id": 1,
                        "taxons": []
                    }
                },
                {
                    "taxon_id": 20,
                    "position": 1,
                    "taxon": {
                        "id": 20,
                        "name": "Rails",
                        "pretty_name": "Brand -> Rails",
                        "permalink": "brand/rails",
                        "parent_id": 2,
                        "taxonomy_id": 2,
                        "taxons": []
                    }
                }
            ]
        },
...

まとめ

詳しくはもう少しいじって追記していく予定。

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

rails: 絶対に分かるhas_one :through関連付ける方法

前提

ユーザー・予約・住所の3つのレコードでhas_one :throughを使用し、アソシエーション関係を作りたいと思います。

実際のコード

class Booking < ApplicationRecord
# ユーザーテーブルに対して一対一の関係を示している
  has_one :user
# has_one :addressはユーザーテーブルにある has_one :addressのことである。
# throughは上記に書かれているhas_one :userのことである
  has_one :address, through: :user
end
class User < ApplicationRecord
  has_one :address, dependent: :destroy
end
class Address < ApplicationRecord
  belongs_to :user
end

要約

要約すると、has_one throughhas_oneはアソシエーション先のhas_one :addressのことであり、throughは自モデル内に書かれているhas_one :userのことである

出力結果

@booking = Booking.find_by(id: 1)
@booking.address.zip_code
# => 150-0002
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初心者向けVue.js × Railsでのアプリ作成(ToDoリスト編)

はじめに

最近、Vue.jsとRailsでアプリを作っているのですが、Vue.jsとRailsでアプリを作る記事が少なく、勉強するのに少し不便でした。

Vue.js × Railsの記事が少ないと言っても探せばそれなりに見つかるのですが、私みたいなフロントエンドの事よくわかってない人間には、理解するのに時間がかかったりします。

ネットで記事をあさったり、そもそもJavaScriptが良くわかってないので、JavaScriptから勉強し直してみたり、Vue.js × Railsでアプリを作るだけにしては非常に遠回りしてしまいました。

この記事について

この記事は、私みたいにVue.js × Railsのアプリ作成で遠回りな勉強をしている人をなくす事を目的としています。

初心者向けにVue.js × Railsでアプリを作る記事を書いて、実装のイメージを掴んでもらえれば、私のような遠回りはなくなるはず。。。1度小さいアプリを作ってしまえば、理解度がグッと上がり、他の記事も読みやすくなるのできっと大丈夫!

また、この記事は私がRubyエンジニアなので、Rubyエンジニアから見てVue.jsをどう実装しようかという視点になってます。

この記事を読む対象のレベル

Vue.js × Rails両方ともチュートリアルをやったくらいのレベルを対象としています。
Vue.jsもRailsもだいたいこんな感じというのがわかっていれば作れると思います。

どんなものを作るか? Vue.js × Railsそれぞれの役割とは?

この記事では、Vue.js × RailsでミニマムなToDoリストアプリを作っていきます。定番ですね。

私はToDoアプリを作ろうとした時、Vue.js × Railsがそれぞれどんな役割をしているのかよくわからず、実装のイメージが掴めなかったのですが、いろんな記事を読んだ結果、RailsでAPIを作り、APIへのリクエストと返ってきたデータの表示をVue.jsで行うというのが多かったです。今回もこの役割分担でアプリを作っていきます。

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

WEB業界での経験が浅い、もしくはこれからWEB業界を目指す方はAPIのイメージがつかみにくいかもしれませんが、一言で言うとURLのリクエストを受けたら、URLに応じたデータを返すものです。

この役割のイメージが分かればRailsの部分をFirebaseに置き換えようとか、Vue.jsをReactに置き換えようとか応用が効くような気がします。

(注)私は現時点でFirebaseもReactも詳しくないので応用が効く気がするというだけ。。。詳しい方がいたら教えて下さると助かります。

完成後のイメージ

スクリーンショット 2020-01-13 14.31.32.png

  • テキストボックスにタスクを入力して追加ボタンを押すとリストに追加されて表示される。
  • チェックボックスをチェックすると取り消し線が引かれる
  • 削除ボタンを押すと削除

実際に作ってみよう

RailsでAPIを作る

では、実際にアプリを作ってきます。まずはRailsでAPIを作るところから。
以下のコマンドで、Railsアプリを作ります。--webpack=vueをするとwebpackとVue.jsのインストールが出来ます。

rails new todo_list --webpack=vue

「webpackとかまた新しい言葉出すなよ!」って方は以下のリンクを見てください!
【5分でなんとなく理解!】Webpack入門
Webpackとは、js、cssなどフロントで作るファイルをバンドリングしてくれるものです。

ToDoリストを表示するHome画面を作成

ToDoを表示するHome画面を作るため、コントローラーを作成します。

rails g controller home

作成したコントローラーにindexだけ追加しておきましょう。

/app/controllers/home_controller.rb
class HomeController < ApplicationController
  def index
  end
end

viewからVue.jsが呼び出せるか試しますために追加します。

/app/views/home/index.erb
<%= javascript_pack_tag 'hello_vue' %>

routes.rbに以下を追加。

/config/routes.rb
Rails.application.routes.draw do
  root to: 'home#index'
end

rails sしてlocalhost:3000にアクセスします。
以下のような画面が表示されてればOKです。

スクリーンショット 2020-01-02 23.00.33.png

ちなみにVue.jsを変更したら

bin/webpackで更新してあげる必要があります。(重要)

APIの処理を作る

まずは、ToDoリストにタスクを追加するためにモデルを作っていきます。

rails generate model Task name:string is_done:boolean

ルーティングに以下を追加します。
表示用のhomeとデータを返すAPI用のapi::tasksを追加。

/config/routes.rb
Rails.application.routes.draw do
  root to: 'home#index'

  namespace :api, format: 'json' do
    resources :tasks, only: [:index, :create, :destroy, :update]
  end
end

リクエストを受けたらデータを返すため、Tasksのコントローラーを作ります。

rails g controller Api::Tasks

マイグレーションします。

rails db:migrate

コントローラーの中身は以下のような感じ。

/app/controllers/api/tasks_controller.rb
module Api
  class TasksController < ApplicationController
    skip_before_action :verify_authenticity_token

    def index
      @tasks = Task.order('created_at DESC')
    end

    def create
      @task = Task.new(task_params)

      if @task.save
        render json: @task, status: :created
      else
        render json: @task.errors, status: :unprocessable_entity
      end
    end

    def destroy
      Task.find(params[:id]).destroy!
    end

    def update
      Task.find(params[:id]).toggle!(:is_done)
    end

    private def task_params
      params.require(:task).permit(:name, :is_done)
    end
  end
end

APIを返す時は、htmlではなくJSONで返してあげたいので以下を追加します。
自分は実際にWEB業界に入るまでJSONに馴染みがなかったのですが、以下の形で書きます。

/app/views/api/tasks/index.json.jbuilder
json.set! :tasks do
  json.array! @tasks do |task|
    json.extract! task, :id, :name, :is_done, :created_at, :updated_at
  end
end

APIの動作確認

DBにデータを入れて確認してみましょう。
コンソールを立ち上げます。

rails c

Taskモデルにデータを追加してみましょう。

Task.create(name: 'テスト用タスク')

もう一度サーバー立ち上げ

rails s

以下のアドレスで追加したデータがJSONでデータが返ればOK。

http://localhost:3000/api/tasks.json

スクリーンショット 2020-01-02 23.40.55.png

Vue.jsでフロント作成

コンポーネント

コンポーネントとは、名前付きの再利用可能な Vue インスタンスです。
再利用出来そうなパーツごとにコンポーネントを区切って実装するのが、どうやら重要らしい。
今回、最小限の構成でアプリを構成するためコンポーネントについては省こうか迷ったのですが、重要なので組み込みます。

わかりやすいイメージで言うと、ヘッダー、フッター、サイドナビ等は色んなページで再利用するのでコンポーネントを分けて実装するといった感じでしょうか。
今回もヘッダーとToDoリストを表示するボディ部分でコンポーネントを分けて実装したいと思います。

では、まず以下のようにToDoリスト表示部分を作って下さい。

/app/views/home/index.erb
<div id="app">
  <navbar></navbar>
</div>

<%= javascript_pack_tag 'todo' %> # todoに変更する事に注意

はヘッダーのコンポーネントを表示します。
<%= javascript_pack_tag 'todo' %>でapp/javascript/packs配下のtodo.jsファイルを読み込みます。

ヘッダーの作成

まずはヘッダー部分のコンポーネントを用意します。

/app/javascript/packs/components/header.vue
<template>
  <h1>ToDoリスト</h1>
</template>

次に/app/views/home/index.erbから呼び出されているapp/javascript/packs/todo.jsにコンポーネントの設定をしていきます。
以下のように書くとapp/views/home/index.erb<navbar></navbar>/app/javascript/packs/components/header.vueをマウントして表示してくれるようです。

/app/javascript/packs/todo.js
import Vue from 'vue/dist/vue.esm.js'
import Header from './components/header.vue'

var app = new Vue({
  el: '#app',
  components: {
    'navbar': Header
  }
});

Vue.jsの読み込み設定

webpackはVue.jsの読み込み方がわからないので以下を実行します(重要)

/config/loaders/stylus.js
module.exports = {
  test: /\.styl$/,
  use: [
    'style-loader', 'css-loader', 'stylus-loader'
  ]
}
/config/webpack/environment.js
const { environment } = require('@rails/webpacker')
const { VueLoaderPlugin } = require('vue-loader')
const vue = require('./loaders/vue')
const stylus = require('../loaders/stylus') // 作ったstylusをrequireする

environment.plugins.prepend('VueLoaderPlugin', new VueLoaderPlugin())
environment.loaders.prepend('vue', vue)
module.exports = environment
environment.loaders.prepend('stylus', stylus) // 作ったstylusをロード

webpackを再読み込みしてからサーバーを再起動しましょう(重要)

bin/webpack
rails s

rails sしてヘッダーが表示されて入ればOKです

スクリーンショット 2020-01-13 14.10.00.png

ToDoリストを表示するボディ部分

axiosというライブラリを使って、フロントエンドからHTTPリクエストをします。
以下のコマンドでyarnでaxiosを追加して下さい。

yarn add axios

ToDoアプリのメイン部分の実装です。解説は後ほど詳しく説明します。

/app/javascript/packs/components/index.vue
<template>
  <div>
    <div>
      <input v-model="newTask" placeholder="to doを追加して下さい">
      <div v-on:click="createTask">
        <i>追加</i>
      </div>
    </div>
    <ul>
      <li v-for="(task, index) in tasks">
        <input type="checkbox" v-model="task.is_done" v-on:click="update(task.id, index)">
        <span v-bind:class="{done: task.is_done}">{{ task.name }}</span>
        <button v-on:click="deleteTask(task.id, index)">削除</button>
      </li>
    </ul>
  </div>
</template>

<script>
  import axios from 'axios';

  export default {
    data: function () {
      return {
        tasks: [],
        newTask: ''
      }
    },
    mounted: function () {
      this.fetchTasks();
    },
    methods: {
      fetchTasks: function () {
        axios.get('/api/tasks').then((response) => {
          for(let i = 0; i < response.data.tasks.length; i++) {
            this.tasks.push(response.data.tasks[i]);
          }
        }, (error) => {
          console.log(error, response);
        });
      },
      createTask: function () {
        if(this.newTask == '') return;

        axios.post('/api/tasks', { task: { name: this.newTask } }).then((response) => {
          this.tasks.unshift(response.data);
          this.newTask = '';
        }, (error) => {
          console.log(error, response);
        });
      },
      deleteTask: function (task_id, index) {
        axios.delete('/api/tasks/' + task_id).then((response) => {
          this.tasks.splice(index, 1);
        }, (error) => {
          console.log(error, response);
        });
      },
      update: function (task_id) {
        axios.put('/api/tasks/' + task_id).then((response) => {
        }, (error) => {
          console.log(error);
        });
      }
    }
  }
</script>

作ったindex.vueを読み込んであげましょう。

/app/javascript/packs/todo.js
import Vue from 'vue/dist/vue.esm.js'
import Header from './components/header.vue'
import Index from './components/index.vue' // 追加

var app = new Vue({
  el: '#app',
  components: {
    'navbar': Header,
    'contents' : Index // 追加
  }
});

ヘッダーのしたにindex.vueを表示するため、<navbar></navbar>の下に<contents></contents>を追加します。

/app/views/home/index.erb
<div id="app">
  <navbar></navbar>
  <contents></contents>
</div>

<%= javascript_pack_tag 'todo' %>

チェックボックスが押されたら取り消し線を表示するためcss追加。

app/assets/stylesheets/home.scss
#app li > span.done {
  text-decoration: line-through;
}

rails sして動くか確認してみて下さい。
実際にToDoリストを追加してみましょう。

スクリーンショット 2020-01-13 16.31.25.png

ToDoアプリのコード解説メモ

学習し始めだと、Vue.jsのどの行が何をやっているのかわからない事があったのでメモ付きのコードをのせます。

まずはtemplate

<template>
  <div>
    <div>
      <input v-model="newTask" placeholder="to doを追加して下さい">
      # 追加ボタンを押すとcreateTaskを実行する
      <div v-on:click="createTask">
        <i>追加</i>
      </div>
    </div>
    <ul>
      # fetchしたタスク一覧(tasks)から一つずつtaskとindexを取り出す処理
      <li v-for="(task, index) in tasks">
        # チェックボックスが押されたらv-modelのis_doneを変更して取り消し線を引く
        # updateでAPI側のデータも更新
        <input type="checkbox" v-model="task.is_done" v-on:click="update(task.id, index)">
        # タスクの表示。v-bind:classでis_doneを参照して取り消し線が引かれるかどうか判定
        <span v-bind:class="{done: task.is_done}">{{ task.name }}</span>
        # 削除ボタンを押すとdeleteTaskを実行
        <button v-on:click="deleteTask(task.id, index)">削除</button>
      </li>
    </ul>
  </div>
</template>
<script>
  import axios from 'axios';

  export default {
    data: function () {
      return {
        tasks: [],
        newTask: ''
      }
    },
    mounted: function () {
      this.fetchTasks();
    },
    methods: {
      // APIからタスク一覧を取得
      fetchTasks: function () {
        axios.get('/api/tasks').then((response) => {
          for(let i = 0; i < response.data.tasks.length; i++) {
            this.tasks.push(response.data.tasks[i]);
          }
        }, (error) => {
          console.log(error, response);
        });
      },
      // 新しいタスク作成
      createTask: function () {
        // テキストボックスが空の場合はreturnして終了
        if(this.newTask == '') return;

        // apiへ追加リクエスト
        axios.post('/api/tasks', { task: { name: this.newTask } }).then((response) => {
          // unshiftで現在のtasksの先頭にタスクを追加
          this.tasks.unshift(response.data);
          // 追加したらテキストボックスを空にする
          this.newTask = '';
        }, (error) => {
          console.log(error, response);
        });
      },
      // タスク削除
      deleteTask: function (task_id, index) {
        // apiへ削除リクエスト
        axios.delete('/api/tasks/' + task_id).then((response) => {
          this.tasks.splice(index, 1);
        }, (error) => {
          console.log(error, response);
        });
      },
      // タスク更新。今回はis_doneのみ更新だが、タスク名とか色々更新するようカスタムしても良いと思う
      update: function (task_id) {
        // apiへ更新リクエスト
        axios.put('/api/tasks/' + task_id).then((response) => {
        }, (error) => {
          console.log(error);
        });
      }
    }
  }
</script>

まとめ

小さいアプリをとりあえず作ってみると理解度がかなり深まると思うので、今回のようなToDoアプリを作ってみると良いと思います。
かけ足で記事を書いてしまったのですが、これで私みたいな人間を救えるのか...???
今後も私のようにVue.js × Railsでアプリを作りたい人向けに記事を改訂して行きたいです。

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

【Rails】Rails g model書き方

Rails g model書き方の備忘録です。

書き方

ターミナル
rails g model モデル名 カラム:データ型

モデル名

  • 大文字で始める
  • 単数形

カラム:データ型

半角スペースで連続で記載できる

ターミナル
rails g model Post content:text image:string

データ型

string:255文字までの文字列
text:255文字以上の文字列
integer:整数

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

Ruby on Rails APIモードでいいね機能を実装する【初学者のReact×Railsアプリ開発 第6回】

やったこと

  • ログイン中のユーザーが、ポスト(投稿)に対して、「好き」「嫌い」「興味ない」を投票でき、更新もできるようにした。
  • likesテーブルをpostsテーブル、usersテーブルとリレーションさせた。
  • counter_cultureを使って、関連レコードの集計(postsテーブルのsuki_countなど)を行った
  • PostgreSQL 12の新機能であるGenerated Columnを使用して、関連レコードの集計結果の計算(postsテーブルのall_count, suki_percent)を行わせた。

完成したデータベースのイメージ(dbconsoleを使って確認)

app_development=# select * from likes;
 id | user_id | post_id | suki |         created_at         |         updated_at         
----+---------+---------+------+----------------------------+----------------------------
  1 |       1 |       2 |    1 | 2020-01-12 23:54:28.193197 | 2020-01-12 23:54:28.193197
(1 row)

app_development=# select * from posts;
 id | content | user_id |        created_at        |        updated_at        | suki_count | kirai_count | notinterested_count | all_count | suki_percent 
----+---------+---------+--------------------------+--------------------------+------------+-------------+---------------------+-----------+--------------
  2 | bbbbd   |       1 | 2020-01-12 12:41:03.4942 | 2020-01-12 12:41:03.4942 |          1 |           0 |                   0 |         1 |          100

実装手順

counter_cultureのインストール

counter_cultureは、各ポストに対して何件の「好き」「嫌い」が投票されたのか、など関連レコード数の集計に使うモジュールです。

Gemfile
gem 'counter_culture'

gemfileにcounter_cultureを追加。

$ docker-compose build --no-cache

モデルとコントローラーの作成

$ docker-compose run api rails g model like suki:integer
$ docker-compose run api rails g controller api/v1/likes
XXX_create_likes.rb
class CreateLikes < ActiveRecord::Migration[5.2]
  def change
    create_table :likes do |t|
      t.integer :user_id, null: false
      t.integer :post_id, null: false
      t.integer :suki, null: false

      t.timestamps

      t.index :user_id
      t.index :post_id
      t.index [:user_id, :post_id], unique: true
    end
  end
end
  • suki:0ならば「嫌い」、suki:1ならば「好き」、suki:2なら「興味なし」とする。
  • user_idとpost_idの組み合わせがユニークであることを書く。重複データを避けるため。

追加のマイグレーションファイルの作成

  • 関連レコードの集計のためのカラムやそれをパーセント表記するための列を追加します。(あるポストに対して「好き」が何票か、「嫌い」が何票か、「好き」と「嫌い」の合計は何票か、「好き」は何%か)
$ docker-compose run api rails g migration AddLikesCountToPosts
$ docker-compose run api rails g migration AddAllCountToPosts
$ docker-compose run api rails g migration AddSukipercentToPosts
XXX_add_likes_count_to_posts.rb
class AddLikesCountToPosts < ActiveRecord::Migration[5.2]
  class MigrationUser < ApplicationRecord
    self.table_name = :posts
  end

  def up
    _up
  rescue => e
    _down
    raise e
  end

  def down
    _down
  end

  private

  def _up
    MigrationUser.reset_column_information

    add_column :posts, :suki_count, :integer, null: false, default: 0 unless column_exists? :posts, :suki_count
    add_column :posts, :kirai_count, :integer, null: false, default: 0 unless column_exists? :posts, :kirai_count
    add_column :posts, :notinterested_count, :integer, null: false, default: 0 unless column_exists? :posts, :notinterested_count
  end

  def _down
    MigrationUser.reset_column_information

    remove_column :posts, :suki_count if column_exists? :posts, :suki_count
    remove_column :posts, :kirai_count if column_exists? :posts, :kirai_count
    remove_column :posts, :notinterested_count if column_exists? :posts, :notinterested_count
  end
end
  • suki_count, kirai_count, nointerested_countというカラムをpostsテーブルに追加しています。counter_cultureを使って、likesテーブルに「好き」「嫌い」「興味なし」が投票されたときに、+1され、likesテーブルのデータが更新されたら-1、+1がされます。
XXX_add_all_count_to_posts.rb
class AddAllCountToPosts < ActiveRecord::Migration[5.2]
  def up
    execute "ALTER TABLE posts ADD COLUMN all_count real GENERATED ALWAYS AS (suki_count+kirai_count) STORED;"
    add_index :posts, :all_count, unique: false
  end

  def down
    remove_column :posts, :all_count
  end
end
  • postsテーブルにall_countカラムを追加。
  • PostgreSQL 12の新機能「Generated Column」を使って、suki_countとkirai_countの合計をall_countに自動計算させるように記述しています。

Generated Columnとは

“generated column” を使うと、”(同じテーブル内の) 他の列の値を利用した計算結果” を、特定の列に格納することが可能になります。https://tech-lab.sios.jp/archives/17098

XXX_add_sukipercent_to_posts.rb
class AddSukipercentToPosts < ActiveRecord::Migration[5.2]
  def up
    execute "ALTER TABLE posts ADD COLUMN suki_percent real GENERATED ALWAYS AS (
      CASE WHEN (suki_count+kirai_count) = 0 THEN NULL
      ELSE suki_count*100/(suki_count+kirai_count) END
      ) STORED;"
    add_index :posts, :suki_percent, unique: false
  end

  def down
    remove_column :posts, :suki_percent
  end
end
  • suki_percentカラムをpostsテーブルに追加。
  • ここでもgenerated columnを使って、「好き」の票数の「好き」と「嫌い」の合計に対するパーセンテージを求めてsuki_percentに格納するように記述しています。

モデルの編集

user.rb
class User < ActiveRecord::Base
  has_many :posts, dependent: :destroy
  has_many :likes, dependent: :destroy

  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable,
         :omniauthable, omniauth_providers: [:twitter]
  include DeviseTokenAuth::Concerns::User
end
  • テーブル間のリレーションシップの追加
post.rb
class Post < ApplicationRecord
  belongs_to :user
  has_many :likes, dependent: :destroy
end
  • テーブル間のリレーションシップの追加
like.rb
class Like < ApplicationRecord
  belongs_to :user
  belongs_to :post
  validates :user_id, presence: true
  validates :post_id, presence: true
  counter_culture :post, column_name: -> (model) {"#{model.like_type_name}_count"}

  def like_type_name
    if suki == 1 then
      return 'suki'
    elsif suki == 0 then
      return 'kirai'
    elsif suki == 2 then
      return 'notinterested'

    end      
  end
end
  • counter_cultureの実装はここで行っている。
  • likesテーブルにデータがcreateされたとき「好き(suki==1)」ならばsuki_countを+1するようにしている...のような処理を記述している。

likesコントローラーの編集

likes_controller
module Api
  module V1
    class LikesController < ApplicationController
      before_action :authenticate_api_v1_user!
      before_action :set_like, only: [:show, :destroy, :update,]

      def index
        likes = Like.order(created_at: :desc)
        render json: { status: 'SUCCESS', message: 'Loaded posts', data: likes }
      end

      def show
        if @like.nil? then
            data = {
              updated_at: 3,
              suki: 3
            }
          render json: { status: 'SUCCESS', message: 'Loaded the like', data: data }
        else 
          render json: { status: 'SUCCESS', message: 'Loaded the like', data: @like }
        end

      end


      def create
        like = Like.new(like_params)
        if like.save
          @post = Post.find(params[:post_id])
          @user = @post.user
          @like = Like.find_by(user_id: @user.id, post_id: params[:post_id])
          json_data = {
            'post': @post,
            'user': {
              'name': @user.name,
              'nickname': @user.nickname,
              'image': @user.image
            },
            'like': @like
          }
          render json: { status: 'SUCCESS', data: json_data}
        else
          render json: { status: 'ERROR', data: like.errors }
        end
      end

      def destroy
        @like.destroy
        render json: { status: 'SUCCESS', message: 'Delete the post', data: @like}
      end

      def update
        data = {
          'user_id': @user.id,
          'post_id': params[:post_id],
          'suki': params[:suki]
        }
        if @like.update(data)
          @post = Post.find(params[:post_id])
          @user = @post.user
          json_data = {
            'post': @post,
            'user': {
              'name': @user.name,
              'nickname': @user.nickname,
              'image': @user.image
            },
            'like': @like
          }
          render json: { status: 'SUCCESS', message: 'Updated the post', data: json_data }
        else
          render json: { status: 'SUCCESS', message: 'Not updated', data: @like.errors }
        end
      end


      private

      def set_like
        @user = User.find_by(id: current_api_v1_user.id)
        @like = Like.find_by(user_id: @user.id, post_id: params[:post_id])
      end

      def like_params
        params.require(:like).permit(:post_id, :user_id, :suki)
      end

    end
  end
end
  • 一般的なCRUD用のAPIの記述かな...
  • showでは、該当するlkeのデータが無かったときはエラーになるのを避けるために「suki: 3」として返している。まだ投票していないのかアクセスエラーなのかを区別するために...

Postmanを使ってテスト

スクリーンショット 2020-01-13 10.08.25.png

  • likeをPOSTしたときに、suki_countが+1され、自動的にsuki_percent, all_countが計算されているのがわかる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

はじめてAWSでデプロイする方法⑤(EC2の環境構築、Ruby, MySQL)

前回までの記事

はじめてAWSでデプロイする方法①(インスタンスの作成)
はじめてAWSでデプロイする方法②(Elastic IPの作成と紐付け)
はじめてAWSでデプロイする方法③(AWSセキュリティグループの設定)
はじめてAWSでデプロイする方法④(EC2インスンタンスにSSHログイン)

EC2インスタンス(サーバー)を作成し、パブリックIPをElastic IPで固定。
一般ユーザーがアクセスできるように、セキュリティグループの設定を追加(入り口を作成)
IDとPWを使って、EC2にログイン

ざっくり説明すると、こんなところです。

今回の内容

ssh接続でログインをして、環境構築をする。

[ec2-user@ip-172-31-25-189 ~]$ sudo yum -y update
[ec2-user@ip-172-31-25-189 ~]$ sudo yum -y install git make gcc-c++ patch libyaml-devel libffi-devel libicu-devel zlib-devel readline-devel libxml2-devel libxslt-devel ImageMagick ImageMagick-devel openssl-devel

Node.jsをインストール

[ec2-user@ip-172-31-25-189 ~]$ sudo curl -sL https://rpm.nodesource.com/setup_6.x | sudo bash -
[ec2-user@ip-172-31-25-189 ~]$ sudo yum -y install nodejs

rbenvとruby-buildをインストール

[ec2-user@ip-172-31-25-189 ~]$ git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
[ec2-user@ip-172-31-25-189 ~]$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
[ec2-user@ip-172-31-25-189 ~]$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
[ec2-user@ip-172-31-25-189 ~]$ source .bash_profile

上記をしないと エラー『 -bash: rbenv: コマンドが見つかりません 』が表示

[ec2-user@ip-172-31-25-189 ~]$ git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
[ec2-user@ip-172-31-25-189 ~]$ rbenv rehash

Rubyをインストール

[ec2-user@ip-172-31-25-189 ~]$ rbenv install 2.5.1

ここでエラースクリーンショット 2020-01-11 20.57.41.png
「-bash: rbenv: コマンドが見つかりません」が表示された場合、

rbenvとruby-buildをインストールを見直してください。

スクリーンショット 2020-01-11 20.55.27.png
インストールには時間がかかります。
このまま待ちましょう

[ec2-user@ip-172-31-25-189 ~]$ rbenv global 2.5.1
[ec2-user@ip-172-31-25-189 ~]$ rbenv rehash
[ec2-user@ip-172-31-25-189 ~]$ ruby -v

バージョンが表示されれば、
スクリーンショット 2020-01-11 21.00.04.png

無事にインストールされています

MySQLをインストール

[ec2-user@ip-172-31-25-189 ~]$ sudo yum -y install mysql56-server mysql56-devel mysql56

(mysql56は、MySQLのバージョン5.6をインストールすることを意味)

MySQLを起動

[ec2-user@ip-172-31-25-189 ~]$ sudo service mysqld start

MySQLの起動確認

[ec2-user@ip-172-31-25-189 ~]$ sudo service mysqld status

スクリーンショット 2020-01-11 21.07.34.png
runningが表示されていれば、稼働しています。

MySQLのrootパスワードの設定

この操作はPW設定をします
>PWを入力してから、コマンドを実行してください

[ec2-user@ip-172-31-25-189 ~]$ sudo /usr/libexec/mysql56/mysqladmin -u root password 'PWを入力'

ここでエラーが表示された場合
EC2 MySQL 初期設定 root にパスワードの設定

MySQLに接続(ターミナルでコマンド操作可能)

[ec2-user@ip-172-31-25-189 ~]$ mysql -u root -p

ここでPWが要求されます。
>登録したPWを入力してみて、無事に開かれるか確認しましょう!

下記が表示されたら、成功です
スクリーンショット 2020-01-11 21.43.51.png

PWが正しく登録できたことを確認できたので、

$ exit

でMySQLの接続を解除しましょう

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

#Ruby or #Rails で unixtimestamp の数値を Time.zone の日時に変換する

In Ruby need require

 require 'active_support/core_ext'

Time

Time.at(1580655600)
# => 2020-02-02 15:00:00 +0000

Timezone UTC

Time.use_zone('UTC') { Time.zone.at(1580655600) }
# => Sun, 02 Feb 2020 15:00:00 UTC +00:00

Timezone JST

Time.use_zone('Tokyo') { Time.zone.at(1580655600) }
# => Mon, 03 Feb 2020 00:00:00 JST +09:00

Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/2937

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

#Ruby on #Rail / Convert unix timestamp to date with Time.zone ( UTC / JST Tokyo examample )

In Ruby need require

 require 'active_support/core_ext'

Time

Time.at(1580655600)
# => 2020-02-02 15:00:00 +0000

Timezone UTC

Time.use_zone('UTC') { Time.zone.at(1580655600) }
# => Sun, 02 Feb 2020 15:00:00 UTC +00:00

Timezone JST

Time.use_zone('Tokyo') { Time.zone.at(1580655600) }
# => Mon, 03 Feb 2020 00:00:00 JST +09:00

Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/2936

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

Rails6 rails consoleからカラムのデータ型を確認する

目的

  • rails consoleからのカラムのデータ型の確認方法をまとめる

実施方法

  1. railsアプリ名フォルダ直下で下記コマンドを実行してrails consoleを起動する。

    $ rails console
    
  2. rails console上で下記コマンドを実行してカラムのデータ型を確認する。

    >モデルの名前.columns_hash['カラムの名前'].type
    
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初心者によるプログラミング学習ログ 209日目

100日チャレンジの209日目

twitterの100日チャレンジ#タグ、#100DaysOfCode実施中です。
すでに100日超えましたが、継続。

100日チャレンジは、ぱぺまぺの中ではプログラミングに限らず継続学習のために使っています。

209日目は

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