20210103のRailsに関する記事は30件です。

モジュールとMixin メモ

調べながらメモする

モジュールとは

Rubyの基本概念は、クラスがありオブジェクトがある
ある一連の振る舞いを一箇所にまとめたものがモジュール(Module)

しかし、クラスとは違いオブジェクトを生成することはできない

モジュールは、あくまで
一連の振る舞い(メソッドの集まり)、または処理をまとめたもの

それをクラスで読み込んで使用する

基本形は、ほぼクラスと同じ
メソッドだけでなく、定数やクラスを宣言することも可能

使いかた

使用したいクラスにて、取り込む(includeする)
そうすると、作成したオブジェクトからメソッドを読み込むことができる

色々なクラスにて同じ振る舞いのメソッドを
簡単に追加することができる

同じ名前をつけても衝突しない
それぞれの世界を名前空間といい
名前空間が異なれば、同じ名前でも良い

Mixin

クラスにモジュールを取り込むことをMixinと呼ぶ

多重継承もどき

Rubyは、Javaなどと同じように多重継承を許していない言語
つまり2つの親クラスを同時に継承できない

よって、
1つのクラスを継承してしまった後、
変更を加えるには子クラスに手を加えるしかない

ところが
Mix-inを使うと、あたかも親クラスのように振る舞うことができる

また1度モジュールを定義すれば、他のクラスでも使えるのでとても便利

まとめ

・クラス→オブジェクトを生成することができる
・モジュール→オブジェクトを生成することができない
・モジュールはいわばメソッドの集まりのようなもの
様々なクラスにて読み込むことで使用することができる
複数のモジュールを読み込んで使用することも可能
・クラスにモジュールを取り込むことをMixinと呼ぶ
・Mixinすることで、多重継承のような動きに見せることができる

参考記事

現場Rails
https://style.potepan.com/articles/7667.html

動作確認の際に参考にしようと思う記事
https://qiita.com/tanaka-t/items/32a3329fbc1eb0840ea0
https://qiita.com/pink_bangbi/items/2c2f17516cd3a7b4eeac
https://medium.com/@jiraffestaff/ruby-mixin%E3%81%AE%E4%BD%BF%E3%81%84%E3%81%A9%E3%81%93%E3%82%8D-%E8%A8%AD%E8%A8%88%E7%9A%84%E3%81%AA%E8%80%83%E3%81%88%E6%96%B9-9b8d2e529669

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

[Rails] pry-rails の導入〜 binding.pryのデバック用法

はじめに

この記事は下記を参考させていただきました。
https://pikawaka.com/rails/pry

Pryとは、Rubyのirbのようにrailsのコンソールでメソッドなどを使えることができるようになる機能です。

pry-railsの導入

pry-railsはコンソールを立ち上げたとき、irbでなくpryを起動させることができるgemです。
デフォルトでは、rails cコマンドでコンソールモードにした際、irbが起動します。
下記のようにgemを追加しbundle installを実行することで、pryを使えるようになります。

Gemfile
# 省略

gem 'pry-rails`

binding.pry

記述した箇所で処理を止めることができます。
自分はデバックとテストに用いています。
デバッグとは、任意でない処理(バグ)を見つけることです。

books_controller.rb
 def create
    @book = BookOrder.new(bookorder_params)
    binding.pry
    if @book.save
      redirect_to root_path
    else
      render :index
    end
  end

binding.pryで処理を止めると、ターミナルが下記のようになりました。
コンソールが起動し、入力待ちの状態です。
ここで「params」「@book」と入力し、中身を確認することができます。
Image from Gyazo


下記はbinding.pry実行中に使えるコマンドの一例です。
良かったら公式も見てみてください。
https://github.com/rweng/pry-rails

コマンド 内容
step ステップイン
next ステップオーバー
finish ステップアウト
continue デバッグを終了する(中断していた処理を続行)
[変数名] 変数の中身を出力
$ [メソッド名] メソッドの定義をみる
show-stack スタックとレースをみる(要 pry-stack-explorer)
!!! プロセスを終了する。 その後に何個 binding.pry があっても抜けられるが rails s などは終了する
show-routes 現在のルーティングを確認
show-models 現在のモデルを確認
show-source クラスやモジュール、メソッドの定義を確認
show-doc クラスやモジュール、メソッドのドキュメントを確認

おわりに

Railsでは、「MVCの流れ」と、「今自分は何の為に何をやっているのか」を常に把握しておくことが伸びるコツだなあと日々感じております。
初学者ならではの辛さもあるかと思いますが、プログラミングは楽しいし、
未来の自分にとって最高級の投資だと思いますので、
学びを楽しんでいきましょう!

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

Railsのパンくずリストで2種類以上のトップページを実装する方法

こんばんは
アロハな男、やすのりです!

今日はポートフォリオで使用しているパンくずリスト用Gemgretelを導入時に少し躓いた、『トップページを2種類以上実装する方法』を説明していきたいと思います!

もし僕と同じことを実装したい方がいらっしゃったら参考にしてください!!

結論

2種類以上のトップページを実装する場合は、

config/breadcrumbs.rb
crumb :root do
  link "Home", root_path
end

という初期設定を

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

crumb :mypage_top do |user|
  link "マイページトップ", user_path(user.id)
end

という様な記述に修正し、rootという設定を無くす!!

なぜトップページが2種類以上必要?

まずそもそもなぜ通常だと1つでいいトップページが2種類以上必要なのかについてですが、これは私のポートフォリオの作り方で『その方が見栄えが良さそうだったから』です。

...ここに関しては理論立てた説明はありません

例えば、ポートフォリオでの検索結果ページ詳細ページに遷移した際はもちろんトップページからの遷移歴が表示されます。
Image from Gyazo

しかし、マイページ内のユーザー情報編集ページに遷移した場合の遷移歴は、
Image from Gyazo
この様に、トップページへのリンクはなく、マイページトップへのリンクしかありません。

これがもし遷移歴にトップページも表示させる様にすると、
Image from Gyazo
となってしまい、少し表示が多く見づらくなってしまうなと思いました。
※トップページへのリンクはヘッダー部分に設置してあるため、マイページから出られなくなる等といったことはありません。

トップページを2種類用意するには?

gretelを使用する際に基本は遷移先のリンクと表示用文字列だけを記述し、親ページを設定したい時は別途設定を記述します。

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

crumb :mypage_top do |user|
  link 'マイページトップ', user_path(user.id)
end

crumb :mypage_edit do |user|
  link '会員情報の確認・変更', edit_user_registration_path
  #ユーザー情報編集ページがマイページトップの中に位置する様に設定している。
  parent :mypage_top, user
end

ただし、何も設定をしていない場合でもrootの設定と紐づいてしまいます。
そのためユーザー情報編集ページまでの遷移歴を表示する際に紐づいているトップページまで表示されてしまっているわけです。

『なら、rootとの紐付けを切り離すために、名前を変えちゃえばいいんだ!!』
ということで冒頭でお見せしたコードの出番ですが、

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

crumb :mypage_top do |user|
  link "マイページトップ", user_path(user.id)
end

crumb :mypage_edit do |user|
  link '会員情報の確認・変更', edit_user_registration_path
  #ユーザー情報編集ページがマイページトップの中に位置する様に設定している。
  parent :mypage_top, user
end

というコード構成になります。
このコード構成にして再度ユーザー情報編集ページを表示させることで、紐づいていないトップページの表示が消えてマイページトップからの遷移歴になるわけです。

注意点

この実装方法をした場合に、1つ注意しなければならないことがあります。

それは、検索結果ページに親ページを設定する必要がある』ということです。

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

crumb :search_word do |search_word|
  link "'#{search_word}' の検索結果", search_path
end

元々は検索結果ページに何も設定しなくてもrootに紐づいていたので自動的にトップページを表示してくれていましたが、名前を変更(今回であればtop_page)に変更してしまっているので、紐付けが解除され親ページが存在しない状態になってしまっています。

なので、

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

crumb :search_word do |search_word|
  link "'#{search_word}' の検索結果", search_path
  #新たに設定したtop_pageというページを親ページとして設定する。
  parent :top_page
end

とコードを修正することで、元通りのパンくずリスト表示にすることができます。

最後に

トップページを2種類以上実装するということ自体そんなに頻繁にすることではないと思いますが、もし実装する際にこの記事が参考になれば幸いです!

この件に関するご指摘・アドバイスはどんどんいただきたいと思っていますので、コメントお待ちしています!!

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

RSpecにてmodelのテストを書く

modelテストの準備

今回はPostモデルにタイトルカラムがあるかどうかと文字数のテストをする。

app/models/post.rb
class Post < AplicationRecord
  belongs_to :user
  validates :title, presence: true, length: {maximum: 20}
end

ファイルの作成

①spec配下にmodelsフォルダとfactoriesフォルダを作成し、テストしたいモデルのファイルを作成する。今回は spec/models/post_spec.rbを作成

②FactroyBotを使用可能にする。
使用すると user = create(:user)のようにDB登録やモデルのビルドができるようになる。
spec配下にsupportフォルダとfactory_bot.rbファイルを作成し、下記を記述。
ユーザーがログインしている状態でないと投稿出来ないことをテストするためpost_spec.rb user_spec.rbの2ファイルを作成する。

spec/support/factroy_bot.rb
RSpec.configure do |config|
  config.include FactroyBot::Syntax::Methods
end

③spec/rails_helper.rbの編集

spec/rails_helper.rb
require 'spec_helper'
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../config/environment', __dir__)
abort("The Rails environment is running in production mode!") if Rails.env.production?
require 'rspec/rails'
require 'support/factory_bot'

begin
  ActiveRecord::Migration.maintain_test_schema!
rescue ActiveRecord::PendingMigrationError => e
  puts e.to_s.strip
  exit 1
end
RSpec.configure do |config|
  config.fixture_path = "#{::Rails.root}/spec/fixtures"
  config.use_transactional_fixtures = true
  config.infer_spec_type_from_file_location!
  config.filter_rails_from_backtrace!


  #今回の記事では関係ないがsystem_specにおいてヘッドレスモードでchromeを起動するための記述
  config.before(:each) do |example|
    if example.metadata[:type] == :system
      if example.metadata[:js]
        driven_by :selenium_chrome_headless, screen_size: [1400, 1400]
      else
        driven_by :rack_test
      end
    end
  end

  #capybaraに関する記述
  config.include Capybara::DSL
end

ダミーデータの作成

spec/factories/user.rb
FactoryBot.define do
  factory :user do
    name {Faker::Name.name}
    email {Faker::Internet.email}
    password {'password'}
    password_confirmation {'password'}
  end
end
spec/factories/post.rb
FactoryBot.define do 
  factory :post do
    body {Faker::Lorem.characters(number: 20)}
    user
  end
end

テストコードの作成

spec/models/post_spec.rb
require 'rails_helper'

RSpec.describe 'post model test', type: :model do
  describe 'validation test' do
    #factoriesで作成したダミーデータを使用する
    let(:user) {FactoryBot.create(:user)}
    let!(:post) {build(:post, user_id: user.id)}
    #test_postを作成し、空欄での登録ができるか確認
    subject {test_post.valid?}
    let(:test_post) {post}

    it 'titleカラムが空欄でないこと' do
      test_post.title = ''
      is_expected.to eq false;
    end

    it '21文字以上であればfalse' do
      post.title = Faker::Lorem.characters(number: 21)
      expect(post).to eq false;
    end
  end
end

RSpecの実行

$ bin/rspec spec/models/post_spec.rb
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】 PG::DuplicateTable: ERROR: relation "users" already existsの解消法

PG::DuplicateTable: ERROR: relation "users" already existsエラー

Railsでデータベースをmigrateしようとした時に出たエラーです。
エラーの内容は、「usersテーブルが既に存在してるよ!」ということでした。
はじめはよく理解していなかったので、マイグレーションファイルを削除して、
もう一回作成してみたりしてみたのですが、また同じエラーが出ました。

rails db:migrate:resetを実行

エラー内容を検索してみた結果、リセットするのが一番良いようだったので、
rails db:migrate:reset
を実行。

Dropped database 'rspec_sample_development'
Dropped database 'rspec_sample_test'
Created database 'rspec_sample_development'
Created database 'rspec_sample_test'
== 20210103133942 CreateUsers: migrating ======================================
-- create_table(:users)
   -> 0.0317s
-- add_index(:users, :email, {:unique=>true})
   -> 0.0051s
== 20210103133942 CreateUsers: migrated (0.0371s) =============================

このような表示が出て、みごとにデータベースのリセットが出来ました。
再度、rails db:migrateでデータベースを反映させる事ができました。

参考資料

[Rails5] PG::DuplicateTable: ERROR~の解決方法

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

Don't know how to build task 'credentials:edit'

問題

$ rails credentials:edit
rails aborted!
Don't know how to build task 'credentials:edit' (see --tasks)

解決策

redentials.yml.encは、Rails5.2から追加されたので問題なければ5.2にしましょう。

gem 'rails', '~> 5.2'
bundle update

情報源

https://fuzzyblog.io/blog/rails/2020/09/14/deploying-to-hatchbox-with-rails-5-x.html

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

個人アプリ開発日記 #3

目次

1.はじめに
2.内容
3.おわりに

1. はじめに

前回の個人アプリ開発日記#2では、このアプリを開発したいと思ったきっかけについて述べました。

そして3記事目の今回は、私が現在作っている「フレンドマッチングアプリ」と
既存のアプリについて比較します!理由は、将来的にリリースすることも視野に入れているので、
せっかく作ったものの「これって○○と似てない?」「○○と何が違うの?」
というような残念なことにしたくないからです。

とは言えまだまだ詰めが甘い点は多いですが、自分なりに差別化を図りながら制作しているつもりなので、
思考整理も兼ねて投稿します。

2. 内容

まずは、既存のアプリを友人、他人、1:1、1:nの4つの切り口で、簡単なマトリックスにしてみました。

友人 他人
1:1 チャット(LINE, WeChat, Snapchat) マッチング(Tinder, Pairs, with, タップル)
1:n SNS(Facebook, Instagram, Twitter) メディア(YouTube, TikTok, SHOWROOM

LINEのオープンチャット、Twitterの趣味垢、InstagramやFacebookのDM/
メッセンジャーなどを含めると、もっと細分化してしまいますが、
ざっくりしたところでは上記のように大別できるのではないでしょうか。

私のフレンドマッチングアプリは、1:1で他人がターゲットなのでマッチング系に属することになります。
しかし、既存のマッチング系は顔写真でスワイプで好みを選択したり、恋愛・婚活寄りが多いと感じています。
「つながりを、もっと身近に」というコンセプトは以前の記事にも書いたのですが、あくまで年齢や出身地、
血液型などの項目別もしくは、ハッシュタグ検索を主としています。

また「友達が欲しいけど作る機会に恵まれていない人」をペルソナに設定しているため、友達や雑談相手
探しに利用することをイメージしています。ただし、フレンドマッチングアプリ自体は当然先行がいて
("Tantan"というアプリ名で顔写真でスワイプする点はTinderと同一)、マッチングアプリ世界王者の
Tinderも当初の恋愛系からライフスタイル全般のマッチングに裾野を広げていますので、
独自性を追求すべくこれからも動きを注視したいと考えています。

5. おわりに

アプリ制作は初めてのため初心者ですが、中身の細かい実装をしながらでも、
視野が狭くなりすぎないように適度にアプリ全体を俯瞰することを意識しています!

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

Microposts機能(rails)

この記事では、Micropostsにおける投稿機能について、簡単に理解したのでその確認として理解を深めることを目的としている。

投稿機能は以下の手順デコーディングを行う

  1. Micropostモデル、contentカラム、user_idカラム(外部キー)を作る
  2. UserテーブルとMicropostテーブルを関連付ける
  3. UsersコントローラーのshowアクションにMicropostの中身を表示させるための「@microposts」を記載する
  4. Usersフォルダーのviewのshow.html.erbにも@micropostsを記載する
  5. Micropostsコントローラーを作り、_micropost.html.erbにmicropostテーブルのcontentカラムの中身を表示させるファイルを作る
  6. Homeページをアップデートする

1.Micropostモデル、contentカラム、user_idカラム(外部キー)を作る

$ rails g model Micropost content:string user_id:integer

$ rails db:migrate:reset db:seed

↑データベースをいじったので一度migrateファイルをリセットしておく

Microposは誰の投稿化を知るために必ずuser_idを持たなければいけないので以下のコードを打ち込む

class Micropost < ActiveRecord::Base
***    validates :user_id, presence: true
end

UserテーブルとMicropostテーブルを関連付けをする

models/micropost.rb
class Micropost < ApplicationRecord
***  belongs_to :user
  validates :user_id, presence: true
end
models/user.rb
class User < ApplicationRecord
***  has_many :microposts, *dependent: :destroy
  ...
end

最新の投稿が投稿欄の一番上に表示されるようにする

class Micropost < ApplicationRecord
 belogns_to :user
*** default_scope -> { order('created_at DESC')}
 validates :user_id, presence: true
end

*dependent: :destroyを使うことによりuserが退会した場合にそのuserが投稿した内容も同時に削除されるようにする

投稿文字数をTwitterのように140文字以内でしか投稿できないようにする

class Micropost < ApplicationRecord
 belongs_to :user
 default_scope -> {order('created_at DESC')}
*** validates :content, presence: true, length: { maximum: 140 }
    validates :user_id, presence: true
end

3. UsersコントローラーのshowアクションにMicropostの中身(content)を表示させるための「@microposts」を記載する

class UsersController < 
ApplicationController

///
   def show
        @user = User.find(params[:id])
        @microposts = @user.microposts.paginate(page: params[:page], per_page: 12)
        #userに紐づくすべての投稿をpaginateで取得する。
   end
views/users/show.html.erb
<% provide(:title, @user.name)%>
<div class="container pt-4">
<div class="row">
    <aside class="span">
        <section>
            <h1>
                <%= gravatar_for @user %>
                <%= @user.name%>
            </h1>
        </section>
    </aside>

<div class="col-9">
    <div>
        <% if @user.microposts.any?%>
   #モデルにデータが存在する→true/存在しない→falseを返すメソッド

        <h3>Microposts (<%= @user.microposts.count %>)</h3>
        <ol class="microposts">
            <%= render @microposts%> 
        </ol>
        <%= will_paginate @microposts%>
        <% end %>
    </div>
    </div>

5. Micropostsコントローラーを作り、_micropost.html.erbにmicropostテーブルのcontentカラムの中身を表示させるファイルを作る

terminal
rails g controller Microposts

=>createアクションとdestroyアクションを作るためにmicropostsコントローラーを作る

views/microposts/_micropost.html.erb
<li>
   <h4><%= micropost.content%></h4>
   #micropostテーブルのcontentカラムの内容を表示
   <span class="timestamp">
        Posted <%= time_ago_in_words(micropost.created_at) %> ago.
    #created_atで作成日時を表示する際に、Twitterのように「~前」と表示する方法 ※ex:約19時間前
   </span>
</li>

=> showページでmicroposts投稿内容と作成日を表示させる
microposts.JPG

▼Fakerでダミーデータを作成する

db/seeds.rb
  users = User.order(:created_at).take(7)
  50.times do
    content = Faker::Lorem.sentence(3)
    users.each { |user| user.microposts.create!(content:content) }
  end
$rails db:migrate:reset db:seed

=> データベースの中身を変更したのでリセットする

ダミー.JPG

createとdestroyのルーティングをresourcesメソッドを使って定義する

config/routes.rb
///
resources :users

resources :sessions, only: [:new, :create, :destroy]

resources :microposts, only: [:create, :destroy]
///

6. Homeページをアップデートする

views/static_pages/home.html.erb
<% provide(:title, 'Home')%>
<% if logged_in?%>
    <div class="row">
        <aside class="col-3">
            <section>
                <%= render 'shared/user_info' %>
            </section>
            <section>
                <%= render 'shared/micropost_form' %>
            </section>
        </aside>


     <!-- Microposts here -->

    </div>
    <% else %>
    <h1 style="text-align:center;">Awesome Blog</h1>
        <p>
            Welcome to Awesome Blog!<br>
        </p>
        <p>
            This Blog is made for all people who want to get good shape of the body!!
        </p>

=>userがログインしているときは投稿機能とユーザーの情報を表示するがログインしていないときはAwesome Blogを表示するようにする

views/shared/_user_info.html.erb
   <%= link_to gravatar_for(@user, size: 52), @user%>
   <h1>
      <%= @user.name%> #usersデーブルのnameカラム
   </h1>
   <span>
        <%= pluralize(@user.microposts.count, "micropost")%>
     #pluralizeヘルパー...最初の引数に整数があると、それに基づいて2番目の引数である英単語が複数形に変化したものが渡される。
   </span>
views/shared/_micropost_form.html.erb
 <%= form_for(@micropost) do |f| %> #特定のモデルに特化したフォームを作りたい → form_for
    <div>
        <%= f.text_area :content, placeholder: "Compose new micropost...", class: "w-100"%>
    </div>
    <%= f.submit "Post", class: "btn btn-block btn-primary" %>
 <% end %>

=>form_forメソッドを使うとモデルのインスタンスを渡すだけで自動的にRailsが処理してくれる(RESTfulなリソースを使っている場合)
form_for(@micropost)の@micropostがすでに保存されていた場合にはupdateアクション、未保存の時はcreateアクションを、Railsは自動的に選んでくれる※@micropostは後でhomeコントローラーで定義する

user_info.JPG

micropostsコントローラーのcreate,destroyアクションにアクセスできるのはログインを正しくしたユーザだけに制限する

controllers/microposts_controller.rb
 before_action :only_loggedin_users, only: [:create, :destroy]

 def create
 end

 def destroy
 end

=>before_actionをすることによりURLから直接アクセスされることを防ぐ(ログインしていなければログインページにとぶ)

micropostsコントローラーの中身を記載していく

controllers/microposts_controller.rb
 before_action :only_loggedin_users, only: [:create, :destroy]

 def create
    @micropost = current_user.microposts.build(micropost_params)
     #microposts_paramsとは下のprivateで定義したもの
     #.build≒.new
    if @micropost.save
        flash[:success] = "Successfully saved!"
        redirect_to root_url
    else
        flash[:danger] = "Invalid content. Try again."
        redirect_to root_url
    end
 end

 def destroy
 end

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

=>投稿内容を@micropostに格納し保存するに成功したらflashメッセージを表示させ(flash[:success]=)homeページに投稿を反映させる(redirect_to root_url)

controllers/static_pages_controller
 class StaticPagesController < ApplicationController
    def home
        @micropost = current_user.microposts.build if logged_in? #1対多の場合では、「関連付けメソッド名.build」となる
    end

    def about
    end

    def contact
    end
 end

=>現在ログインしているユーザーモデルに紐づく投稿のオブジェクトを作成する

▼すべての投稿を表示する

views/static_pages/home.html.erb
<% if logged_in?%>
    <div class="row">
        <aside class="col-3">
            <section>
                <%= render 'shared/user_info' %>
            </section>
            <section>
                <%= render 'shared/micropost_form' %>
            </section>
        </aside>


     <!-- Microposts here -->
     <div class="col-9">
        <h3>Micropost Feed</h3>
***        <%= render 'shared\feed'%>
     </div>

    </div>
    <% else %>

=>_feed.html.erbに投稿一覧を表示するようにする

controllers/static_pages_controller
 def home
     #@micropost = current_user.microposts.build if logged_in?
     if logged_in?
          @micropost = current_user.microposts.build
          @feed_items = current_user.feed.paginate(page: params[:page], per_page: 12)
          #user.rbで定義したfeedメソッドをの取得結果を@feed_itemsに格納する
          @user = current_user
     end
 end

=>homeアクションのリファクタリングを行う。

models/user.rb#feed
///
 def feed
      Micropost.where("user_id = ?", id)
 end
///

=>この部分がいまいち理解できない

views/shared/_feed.html.erb
    <% if @feed_items.any? %>
       <ol>
           <%= render partial: 'shared/feed_item', collection: @feed_items %>
       </ol>
       <%= will_pagenate @feed_items %>
    <% end %>
views/shared/_feed_item.html.erb
   <li class="col-12 my-4">
      <%= link_to gravatar_for(feed_item.user), feed_item.user %>
      <span class="user">
          <%= link_to feed_item.user.name, feed_item.user %>
      </span>
      <h4><%= feed_item.content %></h4>
      <span class="timestamp">
          Posted <%= time_ago_in_words(feed_item.created_at) %> ago.
      </span>
   </li>

feed.JPG
?
feed_item.JPG

controllers/microposts_controller.rb#create
///
def create
    @micropost = current_user.microposts.build(micropost_params)
    if @micropost.save
        flash[:success] = "Successfully saved!"
        redirect_to root_url
    else
***        @feed_items = []
        flash[:danger] = "Invalid content. Try again."
        redirect_to root_url
    end
 end
///

=>@feed_items = []を加えることで@feed_itemsの中に何も入っていない(nil)の時に発生するエラーを防ぐ

削除機能を作る

views/microposts/_microposts.html.erb
<li>
   <h4><%= micropost.content%></h4>
   <span class="timestamp">
        Posted <%= time_ago_in_words(micropost.created_at) %> ago.
   </span>
***
   <% if current_user?(micropost.user) %>
       <%= link_to "delete", micropost, method: :delete,
                                        data: { confirm:"Are you sure?"} 
                                        title: micropost.contentn %>
   <% end %>
***
</li>

=>ログインしているユーザーのshowページにだけ削除機能があらわれるようにする

views/shared/_feed_item.html.erb
<li class="col-12 my-4">
      <%= link_to gravatar_for(feed_item.user), feed_item.user %>
      <span class="user">
          <%= link_to feed_item.user.name, feed_item.user %>
      </span>
      <h4><%= feed_item.content %></h4>
      <span class="timestamp">
          Posted <%= time_ago_in_words(feed_item.created_at) %> ago.
      </span>
***
      <% if current_user?(feed_item.user) %>
          <%= link_to "delete", feed_item, method: :delete,
                                           data: { confirm: "You sure?"}
                                           title: feed_item.content %>
       <% end %>
***
 </li>

destroyアクションの中身を記載する

controllers/microposts_controller.rb#destroy
 def destroy
     Micropost.find(params[:id]).destroy
     redirect_to root_url
 end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Ruby】購入機能実装〜ログイン中・販売済商品に対しての分岐〜

解決したいこと

①出品者が自分の出品商品を購入できてしまう
②出品者が完売商品に対して商品の編集・削除ができてしまう
③完売商品に対してもう一度購入ができてしまう

if文の分岐すごく苦手なんです。
どこをどう考えたらいいのかわからないので、まずは紙に書き出してみる。

qiita.png

出品者

自分の出品物の販売中商品だけ編集&削除OK
購入は全てNG

購入者

販売中商品のみ購入OK
編集&削除は全てNG

だいぶ絞れたので実際にコードにしてみます。

1<% if user_signed_in? %>
2 <% if current_user.id == @item.user_id %>
3  <% unless @item.purchase.present? %>
4   <%= link_to '商品の編集', edit_item_path(@item.id), method: :get, class: "item-red-btn" %>
5   <p class='or-text'>or</p>
6   <%= link_to '削除', item_path(@item.id), method: :delete, class:'item-destroy' %>
7  <% end %>
8 <% end %>

1行目
まずはログイン済ユーザーかどうかをチェック。

2行目
そのユーザーが出品者かどうかをチェック。

3行目
完売商品ではないかをチェック。

4〜6行目
3行目がunlessなので完売商品ではなければ(偽)編集&削除ボタンを表示させる。

時間がかかったのはこの後、どうしても購入ボタンが表示されてしまう。
elsifで分岐・・・いや違う。
もう一度if文で分岐・・・これも違う。
そもそも最初の分岐で売れてるか売れてないかを判断したらいいんじゃないか・・・?
そう思い、生み出した奇跡のコードがこちら。

<% if current_user.id == @item.user_id && @item.purchase.present? %> 

わけがわからないですね?

もう一度書いた図を見てみる。
OKなのは販売中商品のみということか。
しかし販売中商品に購入ボタンをつけてしまうと出品者にも購入ボタンが表示されてしまう。
そこでハッと気付く。

2行目
そのユーザーが出品者かどうかをチェック。

ここでelseを使えばユーザーではあるけど出品者ではない=購入者ということではないのかな?

1<% if user_signed_in? %>
2 <% if current_user.id == @item.user_id %>
3  <% unless @item.purchase.present? %>
4   <%= link_to '商品の編集', edit_item_path(@item.id), method: :get, class: "item-red-btn" %>
5   <p class='or-text'>or</p>
6   <%= link_to '削除', item_path(@item.id), method: :delete, class:'item-destroy' %>
7  <% end %>
8 <% else %>
9  <% unless @item.purchase.present? %>
10   <%= link_to '購入画面に進む', item_orders_path(@item.id),class:"item-red-btn"%>
11  <% end %>
12 <% end %>
13<% end %>

8行目
ログイン中ユーザーだけど出品者じゃない分岐。

9行目
3行目と同じく出品物が完売ではないかをチェック。

10行目
9行目がunlessなので完売商品ではなければ(偽)購入ボタンを表示させる。



うまいこといきました。
自己解決できるとまた一つ階段を昇れた気がします。
※違っている点、もっと簡単に書けるよというものがあればご指摘頂ければ幸いです。

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

【Ruby】購入機能実装〜ログイン中・完売商品に対しての分岐〜

解決したいこと

①出品者が自分の出品商品を購入できてしまう
②出品者が完売商品に対して商品の編集・削除ができてしまう
③完売商品に対してもう一度購入ができてしまう

if文の分岐すごく苦手なんです。
どこをどう考えたらいいのかわからないので、まずは紙に書き出してみる。

qiita.png

出品者

自分の出品物の販売中商品だけ編集&削除OK
購入は全てNG

購入者

販売中商品のみ購入OK
編集&削除は全てNG

だいぶ絞れたので実際にコードにしてみます。

1<% if user_signed_in? %>
2 <% if current_user.id == @item.user_id %>
3  <% unless @item.purchase.present? %>
4   <%= link_to '商品の編集', edit_item_path(@item.id), method: :get, class: "item-red-btn" %>
5   <p class='or-text'>or</p>
6   <%= link_to '削除', item_path(@item.id), method: :delete, class:'item-destroy' %>
7  <% end %>
8 <% end %>

1行目
まずはログイン済ユーザーかどうかをチェック。

2行目
そのユーザーが出品者かどうかをチェック。

3行目
完売商品ではないかをチェック。

4〜6行目
3行目がunlessなので完売商品ではなければ(偽)編集&削除ボタンを表示させる。

時間がかかったのはこの後、どうしても購入ボタンが表示されてしまう。
elsifで分岐・・・いや違う。
もう一度if文で分岐・・・これも違う。
そもそも最初の分岐で売れてるか売れてないかを判断したらいいんじゃないか・・・?
そう思い、生み出した奇跡のコードがこちら。

<% if current_user.id == @item.user_id && @item.purchase.present? %> 

わけがわからないですね?

もう一度書いた図を見てみる。
OKなのは販売中商品のみということか。
しかし販売中商品に購入ボタンをつけてしまうと出品者にも購入ボタンが表示されてしまう。
そこでハッと気付く。

2行目
そのユーザーが出品者かどうかをチェック。

ここでelseを使えばユーザーではあるけど出品者ではない=購入者ということではないのかな?

1<% if user_signed_in? %>
2 <% if current_user.id == @item.user_id %>
3  <% unless @item.purchase.present? %>
4   <%= link_to '商品の編集', edit_item_path(@item.id), method: :get, class: "item-red-btn" %>
5   <p class='or-text'>or</p>
6   <%= link_to '削除', item_path(@item.id), method: :delete, class:'item-destroy' %>
7  <% end %>
8 <% else %>
9  <% unless @item.purchase.present? %>
10   <%= link_to '購入画面に進む', item_orders_path(@item.id),class:"item-red-btn"%>
11  <% end %>
12 <% end %>
13<% end %>

8行目
ログイン中ユーザーだけど出品者じゃない分岐。

9行目
3行目と同じく出品物が完売ではないかをチェック。

10行目
9行目がunlessなので完売商品ではなければ(偽)購入ボタンを表示させる。



うまいこといきました。
自己解決できるとまた一つ階段を昇れた気がします。
※違っている点、もっと簡単に書けるよというものがあればご指摘頂ければ幸いです。



2021 1/4
一部修正。

1<% if user_signed_in? && @item.purchase == nil %>                                  
2 <% if current_user.id == @item.user_id %>                                        
3  <%= link_to '商品の編集', edit_item_path(@item.id), method: :get, class: "item-red-btn" %>
4  <p class='or-text'>or</p>
5  <%= link_to '削除', item_path(@item.id), method: :delete, class:'item-destroy' %>
6 <% else %>                                                                       
7  <%= link_to '購入画面に進む', item_orders_path(@item.id),class:"item-red-btn"%>
8 <% end %>
9<% end %>

1行目
unlessで繰り返されていたものを1行目に追記。
@item.purchase == nilで、「商品の購入情報がnil(まだ購入されていない)」と表現。

4行ほど短く、コンパクトにすることができました。
生み出した奇跡のコードにちょっと近いかな?!と自分を褒め称えました。
奥が深い。

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

renderまたはredirect_toの指定パスに対して、引数が必要な場合

はじめに

現在、フリマサイトのwebアプリを、railsを用いて実装中です。
その中で、「商品を保存できたら商品一覧画面へ遷移する」という機能を実装する際に、redirect_toとrenderのURI(パス)に引数をつける必要性が分からなくなってしまった為、メンターさんに聞きました。
その回答内容を以下に整理します。

1.redirect_toとrenderの違い

redirect_toとrenderのそれぞれの意味を、実際の使用例を参考にまとめます。
下部の参考サイトでは図でまとめている為、より理解しやすいかと思います。

1-1. redirect_to : 「アクションの実行」

使用例は、商品を削除したら商品一覧画面へ遷移する(※今回root_pathは商品一覧画面)機能です。

items_controller.rb
def index
  @items = Item.all.order('created_at DESC')
end

def destroy
  item = Item.find(params[:id])
  item.destroy
  redirect_to root_path  #上記indexアクションを実行
end

redirect_toは指定したURIのアクションの実行である為、今回、
「root_pathを実行する」
=「routes.rbに指定したrootを実行する」
=「itemsコントローラーのindexアクションを実行する」

この場合、indexアクションを実行するため、indexアクション内の@itemsは再度生成されます。

もしここでrenderを用いてしまうと、@itemsは再度生成されない。削除することによって、@itemsに含まれるデータが変更されているが、変更前の@itemsを用いてviewへ遷移しているため、下の画像のようなNoMethodErrorが発生します。
renderを用いた場合のエラー画面

1-2. render : 「viewの呼び出し」

使用例は、商品を保存したらredirect_toで商品一覧画面へ遷移し、保存できなかったらrenderで新規投稿画面を表示する(= new.html.erb を呼び出す)機能です。

items_controller.rb
def new
  @item = Item.new
end

def create
  @item = Item.new(item_params)
  if @item.save
    redirect_to root_path
  else
    render :new
  end
end

renderは指定したviewの呼び出しである為、今回、
「new.html.erbを呼び出す」

この場合、createアクション内でnew.html.erbを呼び出しており、newアクションは実行されません。即ち、newアクション内の@itemは生成されません。renderを用いる理由は、データの保存に失敗した場合に、入力していたデータを残したまま入力画面を再度表示できることです。
ちなみに、「createアクション内でnew.html.erbを呼び出している」ことは、商品の保存をあえて失敗すると、ドメイン表示からnewが消えるため、確認できます。

もしここでredirect_toを用いると、@itemが再度生成されます。そのため、保存に失敗した場合、再度newアクションを実行するため、先ほど入力したデータが更新され、空になってしまいます。

2.URIに引数が必要な場合

2-1.redirect_toで引数が必要な場合

redirect_toを用いる際には、パスに引数を与える必要がある場合があります。
以下画像は、ターミナルでrais routesコマンドを実行した際の一例です。
この青部分の様に、URI Patternにidが含まれている場合、パスに引数を与える必要があります。

rails routes

その理由は、redirect先のパスが多数存在する場合は、引数によりアクションを実行する対象のパスを一つ指定しなければならないためです。
Ex) リダイレクト先がツイートのパスで、ツイートが100個ある場合、どのツイートかを引数により指定してあげる必要がある。

2-2.renderで引数が必要な場合

renderで引数が必要な場合は、基本的には必要ありません。
その理由は、renderはviewの使い回しであり、コントローラのアクションは同一であるため、すでに生成したインスタンスを、render先のviewでも用いることができるからです。

3.まとめ

  • redirect_toは再度アクションを実行する
  • renderはviewを切り出す
  • redirect_toを用いる際に、URIにidが含まれている場合は、引数を設定する必要がある。
  • renderでは基本的に引数は必要ない。

参考サイト

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

renderまたはredirect_toの違い、引数の必要性

はじめに

現在、フリマサイトのwebアプリを、railsを用いて実装中です。
その中で、「商品を保存できたら商品一覧画面へ遷移する」という機能を実装する際に、redirect_toとrenderのURI(パス)に引数をつける必要性が分からなくなってしまった為、メンターさんに聞きました。
その回答内容を以下に整理します。

1.redirect_toとrenderの違い

redirect_toとrenderのそれぞれの意味を、実際の使用例を参考にまとめます。
下部の参考サイトでは図でまとめている為、より理解しやすいかと思います。

1-1. redirect_to : 「アクションの実行」

使用例は、商品を削除したら商品一覧画面へ遷移する(※今回root_pathは商品一覧画面)機能です。

items_controller.rb
def index
  @items = Item.all.order('created_at DESC')
end

def destroy
  item = Item.find(params[:id])
  item.destroy
  redirect_to root_path  #上記indexアクションを実行
end

redirect_toは指定したURIのアクションの実行である為、今回、
「root_pathを実行する」
=「routes.rbに指定したrootを実行する」
=「itemsコントローラーのindexアクションを実行する」

この場合、indexアクションを実行するため、indexアクション内の@itemsは再度生成されます。

もしここでrenderを用いてしまうと、@itemsは再度生成されない。削除することによって、@itemsに含まれるデータが変更されているが、変更前の@itemsを用いてviewへ遷移しているため、下の画像のようなNoMethodErrorが発生します。
renderを用いた場合のエラー画面

1-2. render : 「viewの呼び出し」

使用例は、商品を保存したらredirect_toで商品一覧画面へ遷移し、保存できなかったらrenderで新規投稿画面を表示する(= new.html.erb を呼び出す)機能です。

items_controller.rb
def new
  @item = Item.new
end

def create
  @item = Item.new(item_params)
  if @item.save
    redirect_to root_path
  else
    render :new
  end
end

renderは指定したviewの呼び出しである為、今回、
「new.html.erbを呼び出す」

この場合、createアクション内でnew.html.erbを呼び出しており、newアクションは実行されません。即ち、newアクション内の@itemは生成されません。renderを用いる理由は、データの保存に失敗した場合に、入力していたデータを残したまま入力画面を再度表示できることです。
ちなみに、「createアクション内でnew.html.erbを呼び出している」ことは、商品の保存をあえて失敗すると、ドメイン表示からnewが消えるため、確認できます。

もしここでredirect_toを用いると、@itemが再度生成されます。そのため、保存に失敗した場合、再度newアクションを実行するため、先ほど入力したデータが更新され、空になってしまいます。

2.URIに引数が必要な場合

2-1.redirect_toで引数が必要な場合

redirect_toを用いる際には、パスに引数を与える必要がある場合があります。
以下画像は、ターミナルでrais routesコマンドを実行した際の一例です。
この青部分の様に、URI Patternにidが含まれている場合、パスに引数を与える必要があります。

rails routes

その理由は、redirect先のパスが多数存在する場合は、引数によりアクションを実行する対象のパスを一つ指定しなければならないためです。
Ex) リダイレクト先がツイートのパスで、ツイートが100個ある場合、どのツイートかを引数により指定してあげる必要がある。

2-2.renderで引数が必要な場合

renderで引数が必要な場合は、基本的には必要ありません。
その理由は、renderはviewの使い回しであり、コントローラのアクションは同一であるため、すでに生成したインスタンスを、render先のviewでも用いることができるからです。

3.まとめ

  • redirect_toは再度アクションを実行する
  • renderはviewを切り出す
  • redirect_toを用いる際に、URIにidが含まれている場合は、引数を設定する必要がある。
  • renderでは基本的に引数は必要ない。

参考サイト

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

RSpecの準備 

この投稿は、自己学習の備忘録です。

railstutorialを一通り学習し、テストをminitestからRSpecへ書き換えようと思いたったことがこの記事を執筆しようとしたキッカケです。

私自身にとって初めての投稿ですが、もしこの投稿がご覧頂いた方のヒントになっていれば幸いです。

rails new時のオプション

$ rails new --skip-test --skip-bunlde

デフォルのminitestとbundle install をスキップしておく。

gemの追加

Gemfile
group :development, :test do
  gem 'rspec-rails'
  gem 'spring-commands-rspec'
  gem 'capybara'
  gem 'factory_bot_rails'
  gem 'faker'
  gem 'selenium-webdriver'
  gem 'database_cleaner'
end
$ bundle install --without production --path=vender/bundle

↑PCないのシステムのgemと混同しないようにするため

RSpec用の初期ファイルのインストール

$ rails g rspec:install
Running via Spring preloader in process 9045
      create  .rspec
      create  spec
      create  spec/spec_helper.rb
      create  spec/rails_helper.rb

Springを使用したRSpecの導入

springを使用することでRSpecの実行時間を短縮することができる。
そのために先ほど追加したgem 'spring-commands-rspec'を使用する。
RSpecのstubファイル(./bin/rspec)を作成する。

$ bundel exec spring binstub rspec

bin/rspecファイルが作成され、RSpecを実行するとspringが使えるようになる。

*'spring-commands-rspec':bin/rspecのコマンドを実行するときに必要となる。RSpecはbin/コマンドをつけることでSpringと言うRailsに組み込まれているアプリを起動させて処理を高速化できる。RSpec自体はrspecと入力するだけで使用可能。

config/environments/test.rbの編集

config/environments/test.rb
config.active_support.deprecation = :stderr
         ↓非推奨レポートをどこに出力するかの設定に変更 
config.active_support.deprecation = :silence

とりあえずrails db:migrate RAILS_ENV=testを実行

$ rails db:migrate RAILS_ENV=test

.rspecファイルの編集

.rspec
--require spec_helper
--format documentation :テストを通過した文言が表示されるようになる
--color

今回は以上!
Qiitaでの記事の書き方結構独特なんですね
最低でも月に2記事は投稿できるように頑張ります!
頑張るぞ、2021年!

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

[Rails]deviseのカスタマイズ。ユーザー登録、編集後のリダイレクトページを変える方法と、編集時のパスワード入力をスキップする方法

駆け出しエンジニアの備忘録です。初投稿です。
またいつか「あれどうやるんだっけ?」となった時に再度調べるのも効率が悪いので、ここに残していこうと思います。
間違いなどがあればご報告をお願い致します。

デフォルトはrootパス

僕の環境だとrootはトップページにリダイレクトしてしまうので、どちらもユーザーの詳細画面に遷移するようにしたい。
deviseの導入などの準備は割愛いたします。

1.コントローラーの作成

$ rails g devise:controllers users

いくつか作成されたコントローラーの中で今回使うのはregistrations_controller.rb

ここに追記する

controllers/users/registrations_controller.rb
class Users::RegistrationsController < Devise::RegistrationsController

  # 以下を追記
  protected

  def after_sign_up_path_for(resource)
    flash[:notice] = '登録完了!編集ボタンからプロフィール画像と自己紹介文を登録できます。'
    user_path(resource) #ここにリダイレクト先のパスを指定
  end

  def after_update_path_for(resource)
    user_path(resource) #ここにリダイレクト先のパスを指定
  end
end

これでコントローラーの準備はok

2.routes.rbの編集

作成したコントローラーに
class Users::RegistrationsController < Devise::RegistrationsController
という記述があるように、元々deviseで使われているDevise::RegistrationsControllerを継承した新しいUsers::RegistrationsControllerを作成した、ということなので、ここに記述をしてくことで追加の設定を反映していくことができる。
(継承してるということに全然気付いておらず、「コントローラーを新しく作るってことはこのコメントアウトされてるnewアクションとかupdateアクションとか全部自分で触る必要あるのか!?」って焦ったけどそのままでも大丈夫だった)
ただしこのままではUsers::RegistrationsControllerを使うことができないので、
以下を記述して使うことを明示する必要がある。

routes.rb
devise_for :users, controllers: { registrations: 'users/registrations' }

これにより作成したコントローラが使えるようになりました。
ちゃんと指定したパスにリダイレクトされるはずです。

編集時のパスワードの入力をスキップする設定

デフォルトだと更新しようとしても設定中のパスワードを入力するように要求されてしまう。
パスワードの入力無しで更新できるようにします。
先程と同様に、コントローラーに以下を追記します。

controllers/users/registrations_controller.rb
  def update_resource(resource, params)
    resource.update_without_password(params)
  end

あとはviewにあるcurrent_passwordのフォーム部分を削除すればokです。

参考にさせていただいた記事(参考というより殆どそのまま)
https://qiita.com/Tatty/items/a9759755e562ac4693ec
https://note.com/ruquia7/n/n4838547cb054
https://qiita.com/machamp/items/f6a7b003fcda3f04094a

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

【Rails】validatesエラー文を日本語化する方法

https://qiita.com/satreu16/items/a072a4be415f30087ed7

https://qiita.com/Ushinji/items/242bfba84df7a5a67d5b

上記2つの記事を参考にさせていただくことで、無事に日本語化することが出来ました。

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

【CircleCI】ECS自動デプロイ - CircleCI編 -

参考文献

CircleCIコマンド

◆ config.ymlチェック
$ yamllint .circleci/config.yml
◆ CircleCI jobチェック
$ circleci orb validate .circleci/config.yml
$ circleci local execute -c .circleci/config.yml --job build
$ circleci build --job rspec .circleci/config.yml

.circleci/config.yml

.circleci/config.yml
version: 2.1
orbs:
  aws-ecr: circleci/aws-ecr@6.12.2
  aws-ecs: circleci/aws-ecs@1.3.0


/// executors: ジョブのステップ実行する環境を定義 ///
executors:
  default:
    docker:
      - image: circleci/ruby:2.7.1-node-browsers-legacy
        environment:
          BUNDLE_JOBS: 3
          BUNDLE_RETRY: 3
          BUNDLE_PATH: vendor/bundle
          RAILS_ENV: test
          DATABASE_HOST: '127.0.0.1'
          DB_USERNAME: 'root'
          DB_PASSWORD: 'XXXXXX'
      - image: circleci/mysql:5.7
        environment:
          MYSQL_DATABASE: sample_dev
          MYSQL_USER: 'root'
          MYSQL_ROOT_PASSWORD: 'XXXXXX'
  docker_build:
    machine:
      docker_layer_caching: true


/// commands: ジョブ内で実行する一連のステップをマップとして定義 ///
commands:
  bundle_install_rspec:
    steps:
      - run:
          name: Which bundler?
          command: bundle -v

      /// ジョブのキャッシュを復元することで、ジョブ高速化 ///
      - restore_cache:
          keys:
            - cache-gem-{{ checksum "Gemfile.lock" }}
            - cache-gem-
      - run:
          name: Bundle Install
          command: bundle check || bundle install
      - save_cache:
          key: cache-gem-{{ checksum "Gemfile.lock" }}
          paths:
            - vendor/bundle
      - run:
          name: Database create
          command: DISABLE_SPRING=true bin/rake db:create --trace
      - run:
          name: Database setup
          command: DISABLE_SPRING=true bin/rake db:schema:load --trace
      - run:
          name: Run rspec
          command: |
            TZ=Asia/Tokyo \
              bundle exec rspec --profile 10 \
                                --out test_results/rspec.xml \
                                --format progress \
                                $(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)


  /// Vueインストール ///
  vue-installation:
    steps:
      - restore_cache:
          keys:
            - cache-yarn-{{ checksum "yarn.lock" }}
            - cache-yarn-

      - run:
          name: Yarn Install
          command: yarn install

      - save_cache:
          key: cache-yarn-{{ checksum "yarn.lock" }}
          paths:
            - node_modules


/// jobs:実行処理 ///
jobs:
  rspec:
    working_directory: ~/rspec
    executor: default
    steps:
      - checkout
      - bundle_install_rspec
      - vue-installation

  deploy_app:
    working_directory: ~/app
    executor: default
    steps:
      - setup_remote_docker
      - checkout



/// workflows:全てのジョブのオーケストレーション ///
workflows:
  version: 2
  build-and-deploy:
    jobs:
      - rspec
      - deploy_app:
          requires:
            - rspec
      - aws-ecr/build-and-push-image:
          requires:
            - deploy_app
          account-url: AWS_ECR_ACCOUNT_URL
          aws-access-key-id: AWS_ACCESS_KEY_ID
          aws-secret-access-key: AWS_SECRET_ACCESS_KEY
          region: AWS_DEFAULT_REGION
          repo: "${AWS_RESOURCE_NAME_PREFIX}"
          dockerfile: docker/dev/app/Dockerfile
          tag: "${CIRCLE_SHA1}"

      - aws-ecs/deploy-service-update:
          requires:
            - aws-ecr/build-and-push-image
          aws-region: AWS_DEFAULT_REGION
          family: "${AWS_RESOURCE_NAME_PREFIX}-service"
          cluster-name: "${AWS_RESOURCE_NAME_PREFIX}-cluster"
          container-image-name-updates: "container=${AWS_RESOURCE_NAME_PREFIX}-container,image-and-tag=${AWS_ECR_ACCOUNT_URL}/${AWS_RESOURCE_NAME_PREFIX}:${CIRCLE_SHA1}"

      - aws-ecs/run-task:
          requires:
            - aws-ecs/deploy-service-update
          cluster: "${AWS_RESOURCE_NAME_PREFIX}-cluster"
          aws-region: AWS_DEFAULT_REGION
          task-definition: "${AWS_RESOURCE_NAME_PREFIX}-task-definition"
          count: 1
          launch-type: FARGATE
          awsvpc: true
          subnet-ids: subnet-XXXXXX,subnet-XXXXXX
          security-group-ids: sg-XXXXXX,sg-XXXXXX
          overrides: "{\\\"containerOverrides\\\":[{\\\"name\\\": \\\"${AWS_RESOURCE_NAME_PREFIX}-container\\\",\\\"command\\\": [\\\"bundle\\\", \\\"exec\\\", \\\"rake\\\", \\\"db:migrate\\\", \\\"RAILS_ENV=test\\\"]}]}"
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Sidekiq-limit_fetchが仕事しない

ruby on railsの開発において並列処理の実行数を管理したかったが、うまくいかなかったのでその原因のお話

Sidekiq-limit_fetchとは

Sidekiq-limit_fetchはsidekiqでの並列処理の実行数を管理するためのgemです。

https://github.com/brainopia/sidekiq-limit_fetch

Gemfile
gem 'sidekiq-limit_fetch'
sidekiq.yml
:queues:
  - queue1
  - queue2

:limits:
  queue1: 2
  queue2: 5

上記の場合、queue1は並列実行可能数が2、queue2は並列実行可能数が5となります。

Sidekiq-limit_fetchが仕事しなかったお話

上記のようにsidekiq-limit_fetchのgemを使用しているにも関わらず並行実行数が指定数を超えることが起きました。

この原因としては他のgemによるlimitの設定関数のオーバーライドでした。

Gemfile
gem 'serverspec'

上記のgemが原因でSidekiq-limit_fetchに記載されたset関数が正常に処理されませんでした。
serverspecはサーバーのテストを自動化するためのgemです。
そのため、serverspecをGemfileから削除することでsidekiq-limit_fetchは仕事をしてくれるようになりました。

しかし、並列実行数が稀に超過する現象は引き続き発生したため、自身でジョブの実行数を管理するコードを書く方が安全だということがわかりました。

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

【Docker】ECS自動デプロイ - Docker編 -

参考文献

ツリー図

docker/dev
├── app
│   ├── Dockerfile
│   ├── nginx
│   │   ├── app.conf
│   │   └── nginx.conf
│   └── supervisor
│       ├── app.conf
│       └── supervisord.conf
├── db
│   ├── data
│   └── mysql_init

Dockerコマンド

◆ dockerコンテナ削除
$ docker rm -f `docker ps -a -q`
◆ dockerイメージ削除
$ docker rmi `docker images -q`
◆ dockerイメージ作成
$ docker build -f docker/dev/app/Dockerfile -t sample_dev .
◆ ECRプッシュコマンド
$ docker tag XXXXXX sample/dev  //Docker hubリポジトリ
$ docker images
$ docker push sample/dev
$ docker tag sample/dev:latest XXXXXX.dkr.ecr.XXXXXX.amazonaws.com/ecs-sample:latest
$ docker push XXXXXX.dkr.ecr.XXXXXX.amazonaws.com/ecs-sample:latest
◆ dockerコンテナ内実行コマンド
$ bundle install
$ bundle exec rake db:create db:migrate

// supervisor起動
$ /usr/bin/supervisorctl restart app  

// production用データベース作成 
$ bundle exec rails db:migrate RAILS_ENV=production

// アセットプリコンパイル
$ bundle exec rake assets:precompile RAILS_ENV=production
◆ database.ymlコマンド
$ export RAILS_DATABASE_USERNAME=test
$ export RAILS_DATABASE_PASSWORD=password
$ export RAILS_DATABASE_HOST=rds.XXXXXX.XXXXXX.rds.amazonaws.com
$ export RAILS_DATABASE_PORT=3306
◆ nginx && pumaコマンド
// ポート確認
$ ps -ef | grep nginx
$ ps aux | grep nginx

// nginx停止コマンド
$ nginx -s stop

// PID関連コマンド
$ touch /var/run/nginx.pid
$ touch /run/nginx.pid

// ポート占有確認
$ sudo lsof -i:80
$ ps ax | grep rails

// puma起動
$ bundle exec puma -C config/puma.rb
$ bundle exec pumactl start
◆ supervisorコマンド
// supervisor起動
$ /etc/init.d/supervisor start
$ supervisord -c /etc/supervisor/supervisord.conf

// supervisorのsocketコマンド
$ sudo touch /var/run/supervisor.sock
$ sudo chmod 777 /var/run/supervisor.sock
$ supervisorctl help stop

// supervisordの設定の「/var/run」を「/dev/shm」 に変更する
$ sed -i "s/\/var\/run/\/dev\/shm/g" /etc/supervisor/supervisord.conf

Dockerfile

Dockerfile
FROM ruby:2.7.1
ENV APP_ROOT /var/www/sample_dev
ENV LANG C.UTF-8
ENV TZ Asia/Tokyo


/// ディレクトリ作成 ///
RUN mkdir -p $APP_ROOT
RUN mkdir -p /root/tmp
WORKDIR $APP_ROOT


/// Node.js、Nginx, supervisorインストール ///
RUN apt-get update -y && \
    apt-get upgrade -y && \
    apt-get install -y --no-install-recommends \
    bash \
    build-essential \
    git \
    libcurl4-openssl-dev \
    libghc-yaml-dev \
    libqt5webkit5-dev \
    libxml2-dev \
    libxslt-dev \
    libyaml-dev \
    linux-headers-amd64 \
    default-mysql-client \
    nginx \
    nodejs \
    openssl \
    ruby-dev \
    ruby-json \
    tzdata \
    vim \
    supervisor \
    zlib1g-dev && \
    apt-get clean -y && \
    rm -rf /var/cache/apt/archives/* /var/lib/apt/lists/*


/// supervisor用logディレクトリ作成 ///
RUN mkdir -p /var/log/supervisor


/// nginx.conf, conf.d/app.conf作成 ///
COPY app/nginx/nginx.conf /etc/nginx/nginx.conf
COPY app/nginx/app.conf /etc/nginx/conf.d/app.conf


/// supervisord.conf, conf.d/app.conf作成 ///
COPY app/supervisor/supervisord.conf /etc/supervisor/supervisord.conf
COPY app/supervisor/app.conf /etc/supervisor/conf.d/app.conf


/// シンボリックリンク(ヘルスチェックログ) ///

/// 各nginxアクセスログ ///
RUN ln -sf /dev/stdout /var/log/nginx/access.log
RUN ln -sf /dev/stdout /var/log/nginx/app.access.log
RUN ln -sf /dev/stderr /var/log/nginx/app.error.log


/// アプリケーションログ(pumaログ) ///
RUN ln -sf /dev/stdout $APP_ROOT/log/development.log
RUN ln -sf /dev/stdout $APP_ROOT/log/production.log


CMD [ "/usr/bin/supervisord" ]
docker-compose.yml
version: '3'
services:
  app:
    build:
      context: .
      dockerfile: app/Dockerfile
    volumes:
      - ~/sample_dev:/var/www/sample_dev


    /// ホスト側で80番ポートの許可が必要 ///
    /// nginxでバーチャルホストを設定する ///
    ports:
      - 80:80
    environment:
      MYSQL_ROOT_PASSWORD: XXXXXX
    depends_on:
      - db
    tty: true
    stdin_open: true

  db:
    image: mysql:5.7
    ports:
      - 3306:3306
    volumes:
      # mysql初期化
      - ./db/mysql_init:/docker-entrypoint-initdb.d
      - ./db/data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: XXXXXX

Nginx

nginx.conf
user  root;
worker_processes  1;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;

    keepalive_timeout  65;

    include /etc/nginx/conf.d/*.conf;
}
app.conf
/// アクセスログ、エラーログ設定 ///
  access_log /var/log/nginx/access.log main;
  error_log /var/log/nginx/error.log warn;

    upstream app {
        server unix:///var/www/sample_dev/tmp/sockets/puma.sock;
    }
    server {
        listen 80;
        server_name dev.sample.com;

         /// URLのパス設定 ///
        location / {

        /// リバースプロキシ設定 ///
        proxy_pass http://app;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header Host $http_host;
                proxy_redirect off;
    }
}

puma

config/puma.rb
threads_count = ENV.fetch('RAILS_MAX_THREADS') { 5 }.to_i
threads threads_count, threads_count
# ポートを開放しておかないと、socketのlistenが行われない
#port        ENV.fetch('PORT') { 3000 }
environment ENV.fetch('RAILS_ENV') { 'development' }
plugin :tmp_restart

app_root = File.expand_path('../..', __FILE__)
# nginxのhttpディレクティブでsocket通信を行う
bind "unix://#{app_root}/tmp/sockets/puma.sock"

stdout_redirect "#{app_root}/log/puma.stdout.log", "#{app_root}/log/puma.stderr.log", true

supervisor

supervisord.conf
/// supervisor.sock作成 ///
[unix_http_server]
file=/var/run/supervisor.sock


[supervisord]
/// nodaemon=true: supervisorがforground(最前面)プロセスで振舞う ///
nodaemon=true


logfile=/var/log/supervisor/supervisord.log
pidfile=/var/tmp/supervisord.pid


/// rpcinterfaceを有効にすると、supervisorctlが有効になる ///
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface


/// supervisorctlを使ってプロセス管理を可能にする ///
[supervisorctl]
serverurl=unix:///var/run/supervisor.sock

[include]
files = /etc/supervisor/conf.d/*.conf
app.conf
[program:app]
command=bundle exec puma -C ./config/puma.rb
autostart=true
autorestart=true
stopsignal=TERM
user=root
directory=/var/www/sample_dev/
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0


[program:nginx]
command=/usr/sbin/nginx -g "daemon off;"
autostart=true
autorestart=true
stopsignal=TERM
user=root
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】DeviseとOmniauthを使ってGoogle、Twitter、Facebook認証

DeviseとOmniauthを使って、Railsアプリにソーシャルログイン機能を実装する方法をまとめます。
例えばGoogleログインだけを使いたい場合は、TwitterとFacebook用の記述は無視してください。

Deviseをインストール

Gemfile
gem 'devise'
$ bundle install
$ rails g devise:install
config/environments/development.rb
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
app/views/layouts/application.html.erb
<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>

Modelを作成

$ rails g devise User
$ rails db:migrate
config/routes.rb
Rails.application.routes.draw do
  devise_for :users # 自動で追加される
end

Viewを作成

$ rails g devise:views users

ログインしないとアクセスできないようにする

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :authenticate_user!
end

ここまでできたらサーバーを再起動。
ログインページや新規登録ページ以外にアクセスしたらログイン画面に飛ばされること、
/users/sign_upにアクセスして、ユーザー登録ができることを確認。

環境変数を設定

Gemfile
gem 'dotenv-rails'
$ bundle install
$ touch .env
.env
HOST='hogehoge.com'
TWITTER_API_KEY=
TWITTER_API_SECRET=
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
FACEBOOK_KEY=
FACEBOOK_SECRET=

IDやSECRET等はあとで取得するので、今は空でOK。

omniauth系のgemをインストール

Gemfile
gem 'omniauth-facebook'
gem 'omniauth-twitter'
gem 'omniauth-google-oauth2'
$ bundle install

Userテーブルにomniauth用のカラムを追加

$ rails g migration AddOmniauthToUsers provider:string uid:string
$ rails db:migrate

initializerを編集

config/initializers/devise.rb
config.omniauth :facebook, ENV['FACEBOOK_KEY'], ENV['FACEBOOK_SECRET'], scope: 'email', info_fields: 'email', callback_url: "#{ENV['HOST']}/users/auth/facebook/callback"
config.omniauth :twitter, ENV['TWITTER_API_KEY'], ENV['TWITTER_API_SECRET'], scope: 'email', oauth_callback: "#{ENV['HOST']}/users/auth/twitter/callback"
config.omniauth :google_oauth2, ENV['GOOGLE_CLIENT_ID'], ENV['GOOGLE_CLIENT_SECRET'], scope: 'email', redirect_uri: "#{ENV['HOST']}/users/auth/google_oauth2/callback"
OmniAuth.config.logger = Rails.logger if Rails.env.development?

OmniauthCallbacksControllerを作成

$ mkdir app/controllers/users/
$ touch app/controllers/users/omniauth_callbacks_controller.rb
controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def facebook
    callback_for(:facebook)
  end

  def twitter
    callback_for(:twitter)
  end

  def google_oauth2
    callback_for(:google)
  end

  # common callback method
  def callback_for(provider)
    @user = User.from_omniauth(request.env["omniauth.auth"])
    if @user.persisted?
      sign_in_and_redirect @user, event: :authentication #this will throw if @user is not activated
      set_flash_message(:notice, :success, kind: "#{provider}".capitalize) if is_navigational_format?
    else
      session["devise.#{provider}_data"] = request.env["omniauth.auth"].except("extra")
      redirect_to new_user_registration_url
    end
  end

  def failure
    redirect_to root_path
  end
end

Userモデルを編集

app/models/user.rb
class User < ApplicationRecord
  # デフォルトの設定に、:omniauthable以下を追加
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable,
         :omniauthable, omniauth_providers: %i[facebook twitter google_oauth2]
  # omniauthのコールバック時に呼ばれるメソッド
  def self.from_omniauth(auth)
    where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
      user.email = auth.info.email
      user.password = Devise.friendly_token[0,20]
    end
  end
end

ルーティングを編集

config/routes.rb
Rails.application.routes.draw do
  devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks' }
  #省略
end

ログイン画面、サインアップ画面を確認

ソーシャルログイン用のリンクが追加されていることを確認します。
 2020-12-25 12.27.45.png
 2020-12-25 12.27.51.png

プラットフォームごとの設定

Twitter

以下の記事等を参考に、Twitter DevelopersでAPI keyAPI key secretを取得して、.envにセット。
https://dev.classmethod.jp/articles/twitter-api-approved-way/

.env
TWITTER_API_KEY='hogetwkeyhoge'
TWITTER_API_SECRET='fugatwsecretfuga'

そしてEdit authentication settingsをいじって、アプリのドメイン/users/auth/twitter/callbackをセットして完成。
 2020-12-30 18.21.57.png

Google

この辺の記事を見ながらGCPでアプリを作成し、クライアント IDクライアント シークレットを取得して、.envにセット
https://qiita.com/nakanishi03/items/2dfe8b8431a044a01bc6#google

.env
OOGLE_CLIENT_ID='hogehogehoge.apps.googleusercontent.com'
GOOGLE_CLIENT_SECRET='dgpV8aMmmDzfugafuga'

承認済みのリダイレクト URIドメイン/users/auth/google_oauth2/callbackをセット。
 2020-12-30 18.38.52.png

Facebook

AppIDAppSecretを取得して環境変数にセット。

.env
FACEBOOK_KEY='foobarsdsfe'
FACEBOOK_SECRET='bazdejnerf'

Valid OAuth Redirect URIsドメイン/users/auth/google_oauth2/callbackをセット。

参考

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

【教材】Tailwind CSS & Rails6でQiitaを模写してみた【バックエンド演習に利用可】

この教材はこんな人におすすめ

  • Tailwind CSSが流行っていると知っているけど学習が面倒くさい人
  • Tailwind CSSを使ったビューの完成コードが見たい人
  • フロント完成コードを元に機能実装(バックエンド)演習をしてみたい人
  • 駆け出しエンジニアなので現役エンジニアが書いているコードが見たい人

完成コードをどうぞ

https://github.com/Fumiya-Soeno/tailwind

  • git cloneして下さい
  • Rails6が必要になるので入れて下さい。エラー出たら解決してね
  • フロント志望の方はコード眺めるだけでも割と勉強になると思います

完成図

スクリーンショット 2021-01-03 14.09.52.png

Tailwind CSSチートシート

こちらも一緒に眺めましょう。分からないクラス名はここで検索すれば載ってます。
https://nerdcave.com/tailwind-cheat-sheet

Font Awesomeチートシート

fa fa-checkみたいなクラス名はTailwindCSSではなくFontAwesomeという別のCSSフレームワークです。アイコン表示に使ってます。
https://fontawesome.com/cheatsheet

これを教材として使う方法(インフラ・バックエンド向け)

問1:以下のQiita内部の仕組みを追加実装せよ

  • 記事投稿/閲覧/編集/削除/LGTM(いいね)/記事コメント
  • 記事キーワード検索/記事タグ付与/記事タグ検索
  • ユーザー登録/プロフィール編集/ユーザーフォロー
  • フォロー/記事コメント/LGTM(いいね)の通知
  • 記事に画像添付/プロフィール画像設定
  • 下書き保存/編集/削除
  • 外部SNSリンク(API)

上記repoのようなビューのみコードを渡され『機能実装の部分を担当してね』みたいな仕事が現場によくあります。なので、上記の機能をMySQLやらActiveRecordなりで頑張って実装して下さい。該当箇所にはダミーコードがたくさん埋まってるのでこれをMySQLのデータに紐付ける作業です。

市場相場でいうとこの仕事で大体単価20~50万円くらいです。クラウドソーシングで受ければ多分20万円&納期1ヶ月だと思われます。経験1年のエンジニアなら20日くらいあれば納品可能な規模だと思います。これができれば平日稼働のみで月収20万はいけます多分。

問2:機能実装後、本番環境で動作確認せよ

インフラ学習したい方は、上記の機能実装後、これをAWSなりレンタルサーバFTPなどにあげてみると勉強になりそうです。画像投稿機能(AWSならバケット周り)もしっかり実装できていれば素晴らしいです。EC2のLinuxインスタンスとかdockerあたり使うと色々楽になるのでやってみましょう。こんだけできれば食うには困りません。

問3:広告(実物)を表示せよ
ここはおまけですが、画面右側の広告がダミー(画像)になっているので、これを実際の広告表示にできればあなたが個人サイトを開発すれば広告運用の練習にもなります。このスキルは独立に大変役立つので、余裕があればA8.netやGoogleアフィリエイトなどの広告表示に取り組んでみましょう。

注意事項

  • トラブル回避したいので、コードの再配布・自作発言はお控え下さい。

Railsを学びたい人向け

Mediumにてこの記事の筆者が学習ロードマップを無料公開中です

そもそもRailsが分からんという方は、上記を読んで頂ければ上に挙げた機能実装スキルは網羅できます。なお、Medium記事のclapボタンを50連打して頂けたら筆者は泣いて喜びます。

筆者連絡先

https://twitter.com/soeno_onseo

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

【無料教材】Tailwind CSS & Rails6でQiitaを模写してみた【バックエンド演習に利用可】

こんな人におすすめ

  • Tailwind CSSが流行っていると知っているけど学習が面倒くさい人
  • Tailwind CSSを使ったビューの完成コードが見たい人
  • フロント完成コードを元に機能実装(バックエンド)演習をしてみたい人
  • 駆け出しエンジニアなので現役エンジニアが書いているコードが見たい人

完成コードをどうぞ

https://github.com/Fumiya-Soeno/tailwind

  • git cloneして下さい
  • Rails6が必要になるので入れて下さい。エラー出たら解決してね
  • フロント志望の方はコード眺めるだけでも割と勉強になると思います

完成図

スクリーンショット 2021-01-03 14.09.52.png

これを教材として使う方法(インフラ・バックエンド向け)

問1:以下のQiita内部の仕組みを追加実装せよ

  • 記事投稿/閲覧/編集/削除/LGTM(いいね)/記事コメント
  • 記事キーワード検索/記事タグ付与/記事タグ検索
  • ユーザー登録/プロフィール編集/ユーザーフォロー
  • フォロー/記事コメント/LGTM(いいね)の通知
  • 記事に画像添付/プロフィール画像設定
  • 下書き保存/編集/削除
  • 外部SNSリンク(API)

上記repoのようなビューのみコードを渡され『機能実装の部分を担当してね』みたいな仕事が現場によくあります。なので、上記の機能をMySQLやらActiveRecordなりで頑張って実装して下さい。該当箇所にはダミーコードがたくさん埋まってるのでこれをMySQLのデータに紐付ける作業です。

市場相場でいうとこの仕事で大体単価20~50万円くらいです。クラウドソーシングで受ければ多分20万円&納期1ヶ月だと思われます。経験1年のエンジニアなら20日くらいあれば納品可能な規模だと思います。これができれば平日稼働のみで月収20万はいけます多分。

問2:機能実装後、本番環境で動作確認せよ

インフラ学習したい方は、上記の機能実装後、これをAWSなりレンタルサーバFTPなどにあげてみると勉強になりそうです。画像投稿機能(AWSならバケット周り)もしっかり実装できていれば素晴らしいです。EC2のLinuxインスタンスとかdockerあたり使うと色々楽になるのでやってみましょう。こんだけできれば食うには困りません。

問3:広告(実物)を表示せよ
ここはおまけですが、画面右側の広告がダミー(画像)になっているので、これを実際の広告表示にできればあなたが個人サイトを開発すれば広告運用の練習にもなります。このスキルは独立に大変役立つので、余裕があればA8.netやGoogleアフィリエイトなどの広告表示に取り組んでみましょう。

Tailwind CSSチートシート

こちらも一緒に眺めましょう。分からないクラス名はここで検索すれば載ってます。
https://nerdcave.com/tailwind-cheat-sheet

Font Awesomeチートシート

fa fa-checkみたいなクラス名はTailwindCSSではなくFontAwesomeという別のCSSライブラリです。アイコン表示に使ってます。
https://fontawesome.com/cheatsheet

注意事項

  • トラブル回避したいので、コードの再配布・自作発言はお控え下さい。

Railsを学びたい人向け

Mediumにてこの記事の筆者が学習ロードマップを無料公開中です

そもそもRailsが分からんという方は、上記を読んで頂ければ上に挙げた機能実装スキルは網羅できます。なお、Medium記事のclapボタンを50連打して頂けたら筆者は泣いて喜びます。

筆者連絡先

https://twitter.com/soeno_onseo

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

【買付代行サービス個人開発 - No.012】ドロップダウンをJSで実装

Pull Request

概要

右上のドロップダウンメニューが常に表示されているので、ユーザーアイコンをクリック後、ドロップダウンメニューが表示されるようにする。

image

参考

tailwind ui

https://tailwindui.com/components/application-ui/elements/dropdowns

tippy.js

https://atomiks.github.io/tippyjs/v6/all-props/

https://on-ze.com/archives/7310

ToDo内容

Terminal
# npm
npm i tippy.js

# Yarn
yarn add tippy.js

yarn add turbolinks
app/javascript/src/js/dropdown.js
import tippy from 'tippy.js'
import 'tippy.js/animations/perspective.css';

document.addEventListener('turbolinks:load', init)

function init () {
  tippy('[data-dropdown]', {
    content (ref) {
      if (!ref.dataset.dropdownTemplate) {
        const id = ref.getAttribute('data-dropdown')
        const template = document.getElementById(id)
        ref.dataset.dropdownTemplate = template.innerHTML
        template.remove()
      }
      return ref.dataset.dropdownTemplate
    },
    trigger: 'click',
    allowHTML: true,
    interactive: true,
    appendTo: () => document.body,
    animation: 'perspective',
    duration: 100,
    placement: 'bottom-end'
  })
}
app/components/layout/navbar_component.html.slim
button.flex.text-sm.border-2.border-transparent.rounded-full.focus:outline-none.focus:border-gray-300.transition.duration-150.ease-in-out(
  type="button"
  class="group h-full flex items-center px-4 text-left focus:outline-none focus:rounded-md"
  data-dropdown="navbar-dropdown"
)
  i.fas.fa-user-circle.fa-2x.text-gray-500
#navbar-dropdown.hidden
  .origin-top-right.absolute.right-0.mt-2.w-48.rounded-md.shadow-lg
    .py-1.rounded-md.bg-white.shadow-xs
      = link_to [:orgs], class: 'block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition duration-150 ease-in-out' do
        | 会社切替
      / TODO:リンクを@orgに書き換える
      = link_to [@org], class: 'block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition duration-150 ease-in-out' do
        | 会社詳細

動作確認

準備

Terminal-1
bin/rails s
Terminal-2
bin/webpack-dev-server

受入基準

  • 画面右上のドロップダウンメニューが、ユーザーアイコンのクリック後、表示される
db48b1940ae84a018aa47c8312f8716c
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails] 初心者がコーディングする際の事前知識(コントローラー作成、アクション作成、アクション以外のクラス作成、ルーティング設定)

背景

Rails環境構築ができた後のコーディングの進め方をまとめました。

また、Ruby自体初心者の方は下記の記事に目を通しておくといいと思います。
Ruby基礎文法

コントローラー作成

コントローラーは基本的にモデル単位(DBのテーブル単位)で作成します。コントローラー名は複数形にします。
下記コマンドでコントローラーとコントローラーに対応するViewファイルをまとめて作成できます。
ちなみに、Railsの命名規則でファイル名がスネークケース、クラス名はキャメルケースとなっています。

rails generate controller コントローラー名

上記コマンド実行時のコントローラー名はスネークケース、キャメルケースのどちらでも作成されるファイル名はスネークケースになります。
また、上記のコマンドを使わず手動でコントローラーを作成することもできます。その際はコントローラーに対応するViewファイルも作成しましょう。

アクション作成

アクションとはController内のメソッドのことです。
アクション内で行う処理がない場合も空のメソッドを作成します。
下記のようにすると、コマンドでコントローラー作成と同時にアクションを作成できます。

rails generate controller コントローラー名 アクション名1 アクション名2

新しく作ったアクションにさせたい処理が既存アクションですでに実装されている場合、renderを使用することで既存アクションを使いまわすことができます。下記は例です。

def create
  #他アクションに対応するViewを返す
  render action: :new
  #他コントローラーのアクションを返す
  render template: "hoge/new"
  #jsonを返す
  render json: @hoge
  #テキストを返す
  render text: "hoge"
  #xmlを返す
  render xml: @hoge
end

アクションではActiveRecordを介してデータベースを操作し、Viewに値を渡します。
下記は7つのアクションについての記事です。
[Ruby on rails]7つのアクション以外のアクションの設定

アクション以外のクラス作成

プロジェクト内の任意の場所にディレクトリを作成し、そこにクラスを作りましょう。(app/commonclass/Hoge.rbやlib/Hoge.rbなど)
作ったらコントローラーから下記のように読み込ませます。

class HogeController < ApplicationController
  #クラス読み込み
  require './app/commonclass/hoge'
  def index
  end
end

上記のようにrequireを使う以外にも、config/application.rbに下記を追記すれば読み込ませることができます。

config.autoload_paths += %W(#{config.root}/読み込ませたいディレクトリ #{config.root}/読み込ませたいディレクトリ)

ルーティング設定

下記の記事を参考にしました。
Rails ルーティング 基礎 まとめ

蛇足

下記は命名規則についての記事です。
Railsにおける命名規則

特に下記の記事を参考にさせていただきました。
他言語経験者による初めてのrails
【Rails】クラスファイルを用意してcontrollerから呼び出したい

最後に、参考にさせていただいた記事の投稿者の皆様、ありがとうございます。
私の記事に不備などありましたらご指摘いただけると幸いです。

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

VirtualBox,Vagrant, cyberduck を用いたRuby on Rails の環境構築

はじめに

本格的にプログラミングを始めるために新しくMacbookproを買い環境構築を行ったので、次に行うときのための備忘録として記事を書かせていただきます。
初心者のため間違えているところなどある可能性があります(ご指摘いただけると非常にありがたいです)。その点ご留意の上お読みください。

全体の流れ

以下のような流れで行いました。

  1. VirtualBoxとVagrantで仮想環境構築
  2. 1で構築した仮想環境にrbenvをインストールし、rubyとrailsを導入
  3. cyberduckを用いて仮想環境に接続、vscodeでファイル編集を可能にする

使用したツールのバージョン

参考までに僕が使用したマシンのスペックおよびツールのバージョンを記載します。

マシンスペック
macOS Cataline 10.15.7
MacBook Pro (13-inch, 2020, Four Thunderbolt 3 ports)
プロセッサ: 2 GHz クアッドコアIntel Core i5
メモリ: 16 GB 3733 MHz LPDDR4X

各種ツール
VirtualBox: 6.1.16
Vagrant: 2.2.14
cyberduck: 7.7.2

VirtualBox,およびVagrantの導入

https://www.sejuku.net/blog/39936 を参考に行いました。Vagrantにcentos7を導入するところまでは全く同様で行けました。

rbenv, ruby, rails の導入

次に、rbenvを導入しました。初めはxcode, homebrew を導入したのち、homebrew管理下でrubyをインストールする、という方法を参考にしていたのですが、xcodeコマンドラインツールを導入しているときにエラーが出て解決法が分からず詰んだので直接rbenvを導入する方法に切り替えました。
初めの方法の際に参考にしていたサイトは以下に貼っておきます。

homebrewを用いる方法
https://qiita.com/TAByasu/items/47c6cfbeeafad39eda07

rbenvを用いる方法は、Vagrantの導入で参考にした上記サイトの続きをそのまま用いました。これで、Railsを導入するところまでは問題なく行けました。しかし、仮想環境上における作業領域(僕の場合は home/vagrant/app/score_app)で以下のコマンド

home/vagrant/app/score_app
bin/rails s

を用いてもローカルサーバーへの接続が行えませんでした。これは以下の方法で解決しました。

仮想環境から出て、仮想マシンを設置しているフォルダ(僕の場合は、/ユーザー/ユーザー名/MyVagrant/centos7)で、以下のコマンド

/user/user_name/MyVagrant/centos7
vi Vagrantfile

を実行し、Vagrantfileを編集。ipアドレスの設定を行いました。内容は以下のサイトの行った解決方法①、②を参照。

https://qiita.com/Ago0727/items/325df5e39e3406fa16d2

①のみでは接続できなかったのですが、②で接続できました。ただし、①でも書かれていますがVagrantを用いる場合は接続の際にオプションとして以下を設定しないと接続できないようです。

user/user_name/MyVagrant/centos7
bin/rails s -b 0.0.0.0

cyberduckへの接続

ここも基本的に次のサイトを参考に行えました。

https://qiita.com/Lassieena/items/603fe89df26b59ca06f7

ただし、SSH private key の設定、送信方式をSFTPにすることに気を付けておけばいけます。最後に、cyberduckの環境設定タブ(macであれば画面上部に表示される)から外部エディタにインストールしたvscodeを指定します。
以上で完了です!

最後に

環境構築は様々な方法があり、適切な方法で行うのは初心者にはかなりハードルが高かったです(これで完全にできているのかも不安です)。今回僕がこの方法にたどりつくまでに2回ほど失敗して、どうしたら良いかわからず仮想環境ごと廃棄するのを2回行ったので、もしどうしようもなくなったらもう一度仮想環境から作り直すのもありかもしれません。また、一通りしてみて感じたことは、コマンドの詳細などは理解できないところも多いとは思いますが自分がどこのフォルダにいて、どこに各パッケージをインストールすべきか(しているのか)を把握して構築することが大切だと感じました。仮想環境の廃棄に関してはvagrant コマンド などで検索すれば出てくるので、もし使うときは調べてみるといいかもです。

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

「え!?いつのまに新刊出てたの!?」を解消するアプリを作りました【ポートフォリオ解説】

はじめに

実務未経験者がどうやって・どんなポートフォリオを制作したのか
ポートフォリオの解説→意識したこと・苦労したことの順で解説します。

よければ読んでみてください

コンセプト

「え!?いつのまに新刊出てたの!?」を解消する

好きなマンガや小説など・好きな作家を登録しておくと新刊の発売3日前にメールでお知らせします
ちなみにアプリ名はブクシルです。「ブック」+「(新刊の発売を)知る」ですね。
我ながら気に入ってます。寒くないです。だれですか寒いって言ったの。

使用イメージ

書籍の検索ができます(楽天API使用)

search

お気に入り登録ができます(作品のシリーズと作者名)

favorite

発売3日前にメールでお知らせします

output_0000.png

開発背景

せっかくポートフォリオをつくるんだったら、だれかの役に立つものが作りたいと思い、以下の流れで作り始めました

  1. 周囲の知人・友人に「どんなサービスがあったら便利か」「日常生活で不便に感じていることはないか」とヒアリング
  2. 「気づいたら新刊が発売されていることがあるから事前に知りたい」との意見を聞く
  3. 自分自身、読書が趣味で同じ悩みを持っていたこともあり、それを解決できるアプリを作りたいとの想いで製作開始

使用技術

フロントエンド
HTML/CSS(Sass)
Bootstrap 4.5.2
jQuery 3.5.1

バックエンド
Ruby 2.6.6
Ruby on Rails 6.0.3.3

API
楽天書籍ブックスAPI、楽天ジャンル検索API

DBMS
PostgreSQL 13.1

インフラ
AWS(VPC・EC2・RDS・S3・IAM・Route53・ACM)
独自ドメイン
HTTPS化

DB設計

一番苦労したのがDB設計です。
開発前に軽くER図を作ってからスタートしましたが、当初の構想ではうまくリレーションできず、何回も作り直すことになりました。
最終的に下図の通りになりました。
booksiru_ER_diagram

開発で意識したこと

1. エラー解決方法

実務に入ったときに先輩社員や上司の時間を無駄に奪うことにならないように
まずは自分の力で解決することを強く意識しました。
具体的には、前後することもありますがおおよそ次の順番です。

  1. エラーメッセージやログをよく読む
  2. どこの段階でエラーが出ているか推測する
  3. bindnig.pryでデバッグする
  4. エラー内容を調べる。(なるべく一次情報から読み解くことを意識。)
  5. どうしても解決できなければメンターの方に質問する

2. 質問の仕方

Slackを使ってだいたい以下の構成で質問していました。
なるべく少ないやり取りで済み、相手の人の時間を無駄に奪わない内容にすることを意識しました。

  1. やりたいこと・困っていることを端的に表現
  2. 自分なりに試したこと (書いたコード・コマンド)
  3. 試した結果 (エラーメッセージ)
  4. わからないなりに怪しいと思っている部分を述べる
    ・・・違っている場合にメンターの方から意見を頂ける
    →理解が深まり、以降の質問を減らせる
  5. GitHubのソースコードを記載・・・思わぬところにエラーの原因があることも多いため

実務において1人でずっと悩み続けることはかえって損失になってしまうと思うので
上記のステップである程度独力でやって解決できなかった場合には、端的かつ過不足ない内容で質問することで時間的な損失を最小限にすることを心がけていきたいと思います。

3. 模擬チーム開発

実務を想定して模擬チーム開発を意識しました。
・ Git, GitHubを用いたソース管理
・ 必要要件をIssuesに列挙し、タスク消化
・ featureやfixのブランチを切ってプルリクベースで開発

ただ、Issuesはプルリクと結びつけられると後で知ったので、使い方が甘かったなと反省しています。

開発で特に苦労したところ

DB設計
上でも述べましたが一番苦労しました。
リレーションについてわかったつもりでも、いざ自分のアプリに落とし込むとなるとうまくいかず、何度も紙に書いては関係性を整理しました。

楽天書籍ブックスAPIの適用
公式ドキュメントだけではどう扱うのかのイメージがつかず、調べた限りでは使用例も少なかったです。
少ない情報の中からどう自分のアプリに落とし込むのか、使用例からどこが一般化できるのか、実際にコードを書きながら試行錯誤を繰り返しました。

感想

エラー解決がめちゃくちゃ楽しい。
エラーを乗り越えたときに両手を突き上げるほど興奮したことが何回かあります。
ゲームで強いボスを倒したときみたいな感じですね。
負けては次の手を考え実行する、を繰り返して最終的に勝つみたいな。

プログラミング学習をはじめたころはエラーが怖かったと思うのですが、成長したのかなあと。
あとは仮設と検証を繰り返す作業が現職の研究業務と似ているなあと思ったりしていました。

課題/今後実装したいこと

やりたいことはまだ残っているので、ひとつずつ良くしていきたいと思います。

・レスポンシブWebデザイン(スマホ対応)
・お気に入り登録/解除時にフラッシュ表示
・お気に入り登録/解除時の非同期処理
・LINEログイン
・LINEのMessaging APIを使用してLINEで通知

さいごに

長い文章をここまで読んでくださってありがとうございました
良かったと思った方はLGTMして頂けると嬉しいです!
どなたかの参考になれば幸いです!

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

uninitialized constant LikesControllerのエラーが出たときの対処法

Railsを使って"いいね"ボタンを実装している際に、こちらのエラーに遭遇しました。

原因は、likeコントローラを自作したときに、likes.controller.rbと記述したためです。

正解はlikes_controller.rbでした。

小さなミスがエラーにつながるので、慌てず、一つずつ確実に対処していきます。

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

ログイン要求処理の実装方法(Rails・初心者向け)

ログイン要求処理とは、ログインしていないユーザーに対して特定のページを見せないようにするための処理です。
この記事では、ログイン要求処理の実装方法を簡単に解決しています。

$ rails g helper sessionsでhelperを用意して、current_userlogged_in?を実装します。

app/helpers/sessions_helper.rb
module SessionsHelper
  def current_user
    @current_user ||= User.find_by(id: session[:user_id])
  end

  def logged_in?
    !!current_user
  end
end

def current_userは、現在ログインしているユーザーを取得するメソッドです。

@current_user ||= User.find_by(id: session[:user_id])
@current_userに現在のログインユーザーが代入されている場合 → 何もしません。
@current_userに現在のログインユーザーが代入されていない場合 → User.find_by(...)からログインユーザーを取得し、@current_userに代入します。

def logged_in?はユーザーがログインしている時はtrueを、ログインしていなければfalseを返します。

コントローラーについて考えていきます。

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base

  include SessionsHelper

  private

  def require_user_logged_in
    unless logged_in?
      redirect_to login_url
    end
  end
end

ApplicationControllerに書いたメソッドは、すべてのControllerで使用できるようになります。

require_user_logged_inメソッドを定義します。
require_user_logged_inメソッドはログインの状態を確認して、ログインしていれば何もせず、ログインしていなければ強制的にログインページに戻します。
include SessionsHelperを書いた理由としては、Helperで定義したlogged_in?メソッドを使えるようにするためです。

ではrequire_user_logged_inメソッドをUsersControllerで使ってみます。

app/controllers/users_controller.rb
class UsersController < ApplicationController
  before_action :require_user_logged_in, only: [:index, :show]

  #以下省略

before_actionで指定されたindexとshowは、事前処理としてrequire_user_logged_inメソッドが実行されます。

例えばある人がusers#indexにアクセスした際に、その人がログインしいればusers#indexが見れます。
しかし、ログインしていなければログインページに飛ばされます。
このようにして、会員制のサービスなどを作ることが出来ます。
以上です。

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

Rails5でSprocketsを使いつつ、npmを利用する

何をしたか

今までに作ったRailsアプリは

  • Sprockets + JSのライブラリはCDNでレイアウトファイルから読み込み
  • Webpacker + JSのライブラリはnpmで読み込み

のパターンしか作ったことがなかったのですが、この度、

  • Sprockets + JSのライブラリはnpmで読み込み

というアプリを作ることになり、やり方をメモしようと思います。
具体的にはhttps://swiperjs.com/というライブラリの導入プロセスを紹介していきます。

npm + Rails の初め方

npm自体は、node.jsをダウンロードするとついてきます。Railsの環境構築時にnode.jsはインストールされているはずなので、

$ npm -v 

でバージョンが表示されたらOKです。

ライブラリのインストール

インストールしたいライブラリをnpmで導入します。

$ npm install swiper

npmバージョン4からは--saveオプションをつけなくてもpackage.jsonを更新してくれます。

アセットパイプラインにパスを追加

node_modules配下のパスを見ながら、アセットパイプラインにパスを追加します。今回のswiperの場合、必要なファイルswiper-bundle.jsは下記の位置にありました。

node_modules
└── swiper
    └── swiper-bundle.js

なので、application.jsには、以下の様に記入します。

assets/javascripts/application.js
//= require rails-ujs
//= require activestorage
//= require swiper/swiper-bundle.js  # 追記
//= require_tree .

初期化に必要なファイルを追加

初期化に必要なコードがあれば、assets/javascripts内に別ファイルを作るか、application.js内に、記載します。今回はapplication.js内に記しました。

assets/javascripts/application.js
$(function() {
  new Swiper('.swiper-container', {
    loop: true,

    // If we need pagination
    pagination: {
      el: '.swiper-pagination',
    },

    // Navigation arrows
    navigation: {
      nextEl: '.swiper-button-next',
      prevEl: '.swiper-button-prev',
    },

  })
})

完成

これで、無事swiperが動作する様になりました^^

Image from Gyazo

感想・参考記事等

RailsとJSの最近の流れを振り返る形になって楽しかったです^^
以下、参考にした記事です。

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

Dockerfileについて

Dockerfile

dockerfileはdockerの新しいイメージを作成する際に使用するもので
この設定ファイルにはrailsアプリケーション実行に必要なファイルやパッケージを
イメージに含めるための定義が書かれている。

Dockerfile.
1 FROM ruby:2.4.5
2 RUN apt-get update -qq && apt-get install -y build-essential nodejs
3 RUN mkdir /app
4 WORKDIR /app
5 COPY Gemfile /app/Gemfile
6 COPY Gemfile.lock /app/Gemfile.lock
7 RUN bundle install
8 COPY . /app

1 :から前の部分をリポジトリとよぶ。:から後の部分をタグという。
 この場合rubyリポジトリの2.4.5タグを示している。

2 ruby 2.4.5のイメージからコンテナを起動してコンテナ内で実行するコマンドを定義している
ubuntuのパッケージ管理システムであるapt-getでbuild-essential nodejsをインストールしてる
Railsの動作に必要

3 ルートディレクトリにappディレクトリを作成

4 作業用ディレクトリをappディレクトリに移動

5,6 PC上にあるGemfileとGemfilelockをappディレクトリにコピー

7 gemのインストールコマンドを実行

8 dockerファイルの置いてあるフォルダの内容を全ての内容をappディレクトリにコピー

dockerfile→build→dockerimageが作成される

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

Ruby on Rails 基礎学習 ①

まず開発環境について

dockerを使うメリット
実際の現場では,Dockerを仮想環境として使っているのがほとんど。
dockerは起動スピードが早い。
AWSにはコンテナを実行するサービスがあるためサービスの公開が容易。

dockerを使用した開発環境

dockerをインストールすると軽量なリナックス(Moby Linux)がインストールされる。
dockerでrubyのコンテナを立ち上げるとMoby Linux上でRubyの実行環境コンテナが立ち上がる。
コンテナの基になるものをimageという。

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