- 投稿日:2020-03-23T21:46:16+09:00
チャットアプリにAWSのS3(ストレージサービス)を導入
- 投稿日:2020-03-23T21:31:12+09:00
Railsにユーザーフォロー機能を実装する
何をしたいのか.
- twitterに見られるような,あるユーザーが,あるユーザーをフォローするという機能を実装する時,DBのテーブルはどういった構成にしたらいいのか
- それをrailsで実現するにはどうしたらいいのか
を,このページ(https://umaweb1.hatenablog.com/entry/2019/10/26/120623) を参考にやってみて, 詳しく手順を書いたのが, 本記事である.
それでは早速, 実装開始
- あるユーザーが, あるユーザーをフォローするのだから,userAテーブルとuserBテーブルが必要...なんだけど
- userテーブルは2つ作ると複雑になりそう...
- なので, userテーブルは1つなんだけど, その1つのテーブルにフォローする側のユーザーと, フォローされる側のユーザーの役割を持たせる!
- 一見難しそうだけど, 以下を読んでいけば大丈夫だと思います.
テーブル構成を考える
- テーブルは2つ
- users テーブルと
- relationships テーブルを作成する.
- relationshipsテーブルのカラムを user_id, follow_idにするが, followsテーブルは作成しない. あくまでusersテーブルとrelationshipsテーブルの2つで完結させる!
modelの作成
$ rails generate model user name:string
$ rails generate model relationship
この時,relationshipの migrationファイルには, timestampしか書かれていない.(何も指定せずgenerate modelしたので)
- relationshipの migrationに以下を追記
def change create_table :relationships do |t| t.references :user, foreign_key: true t.references :follow, foreign_key: { to_table: :users } t.timestamps t.index [:user_id, :follow_id], unique: true end endそれぞれの意味
t.references :user, foreign_key: true
- => 外部キーを設定する(true)といえば, idに対して設定する(多分). なので,(仮想的)カラム名は user_id になる.
t.references :follow, foreign_key: { to_table: :users }
- => 外部キーをusersテーブルに求め, followカラムを設定する.(かつ 外部キーを設定するので. (仮想的)カラム名は follow_id になる(多分))
※外部キーを設定する = 外部キー側のテーブルに存在しない値, を入れようとすると, エラーになるという事.
migration実行
$ rails db:migrate
- この状態で, relationshipsのテーブル内はどういったカラムができているかを確認
- user_id, follow_id のカラムができている.
- この状態で, user テーブルにデータを入れてみる(modelへの記述を反映する為, sqlじかにではなく, rails上で)
$ rails console
$ User.create(name: 'name_1')
- relationshipsテーブル(モデル)の中を見てみる
- 何も入っていない
- relationshipテーブルにデータを入れてみる
$ Relationship.create(user_id: 1)
- user_id 1のデータが入った
- もう一度relationshipテーブルにデータを入れてみる
$ Relationship.create(user_id: 2)
- エラー. user_id 2などないから,(userテーブルにid 2 のデータがないから) insertできないと言っている.
- userテーブルとrelationshipテーブルが, 連動している(relationshipテーブルが, userテーブルを参照している)ことが確認できた
- 続いてfollow_idも確認してみる
- 予想: relationshipテーブルのfollow_idには, 2は入れられない.(userテーブルにid 2 のデータがないので)
- まず,
$ Relationship.create(follow_id: 1)
- 入った.
- 続いて,
$ Relationship.create(follow_id: 2)
- エラー. 望んでいる挙動なので問題なし!
- この時, userテーブルのid 1 のデータをdeleteすると, relationshipテーブル側の, user_id 1のデータは, 連動して消えるか.
$ User.destroy.find(1)
- そもそも, 外部キーを設定しているので, deleteできない, という挙動になった.
- user modelに, 以下を追記してみる
has_many :relationships, dependent: :destroy
- もう一度,
$ User.destroy.find(1)
- 同じく, 外部キーを設定しているので, deleteできないとエラー
- relationshipsテーブルのデータを消してから
- usersテーブルのデータは消せるか.
$ Relationship.find(1).destroy
$ Relationship.find(2).destroy
$ User.find(1).destroy
- deleteできた.
- 外部キーを設定されているテーブルは, 該当データが他テーブルに使用されていると, そのデータを勝手に削除できない様子.
アソシエーションを設定する
- アソシエーションとは : 連動してdelete などする挙動のこと(だいぶざっくり).
- それぞれの関係を定義
- user : relationshipの関係 = 1 : n
- relationship belongs_to user
- follow : relationshipの関係 = 1 : n
- relationship belongs_to follow
- 同時にvalidateも記述しておく
- user_idは必須
- follow_idは必須
- 以上をrelationshipモデルに, 以下のように書いてみる.(followは架空のテーブルなので, 実際はuserテーブル(モデル)を使う)
class Relationship < ApplicationRecord belongs_to :user belongs_to :follow, class_name: 'User' validates :user_id, presence: true validates :follow_id, presence: true end
- この状態で, Relationshipに, 片方だけ(user_idだけ)データを入れてみる.
$ Relationship.create(user_id: 2)
- おかしい, insertできてしまった.
- irbを再起動してみる
- 先程, user_id: 2が入ってしまったので, follow_idでやってみる
$ Relationship.create(follow_id: 2)
- エラーが出た.(望んでいる挙動)(思ったのと違う挙動になるときは, 再起動してみよう)
- user_id, follow_id どちらも指定して入れてみる
$ Relationship.create(user_id: 2, follow_id: 2)
- 入った.
- validatesの presenceが効いているのが確認できた.
- userテーブルのmodelにアソシエーションを書いてみる
has_many :relationships, dependent: :destroy
を追記- そして, userテーブルのデータをdeleteしてみる
$ User.find(2).destroy
- usersテーブルのレコードが消え, 連動してrelationshipsテーブルの該当レコードも消えた.
- relationshipsテーブルは, usersテーブルに外部キーを貼っているのに, なぜ消せたのか
- => 外部キーを貼っていても, アソシエーションで主従の関係を作り(has_many, belong_to), かつ, dependent: :destroy しているなら, 連動して消える様子.
- (普通は, 外部キーを設定されているテーブルは, 該当データが他テーブルに使用されていると, そのデータを勝手に削除できない(FOREIGN KEY constraint failedエラー: 外部キー制約))
userテーブルのmodelに, さらにアソシエーションを書いてみる
has_many :followings, through: :relationships, source: :follow
を追記
- $ User.find(4).followings で, 以下が得られた(usersテーブルのデータが返ってくる)
[#<User id: 4, name: "name_1", created_at: "2020-03-18 11:33:05", updated_at: "2020-03-18 11:33:05">, #<User id: 5, name: "name_2", created_at: "2020-03-18 12:00:39", updated_at: "2020-03-18 12:00:39">]
- userを特定し, followingsを指定すると
- relationshipsテーブルを通して
- そのテーブル(relationshipsテーブル)のfollow_idに該当する
- userテーブルのレコードを, 配列で取得できる
- userテーブルのmodelに, さらにアソシエーションを書いてみる. 以下を追記.
has_many :reverse_of_relationships, class_name: 'Relationship', foreign_key: 'follow_id', dependent: :destroy has_many :followers, through: :reverse_of_relationships, source: :user
- 挙動を確認する為,
has_many :relationships, dependent: :destroy
は一旦コメントアウト
- この状態で usersテーブルのデータをdelete
$ User.find(5).destroy
- エラー(FOREIGN KEY constraint failedエラー: 外部キー制約))
has_many :relationships, dependent: :destroy
をコメントアウトしている為, エラーになる.
- => ではなぜ, 先程は
has_many :relationships, dependent: :destroy
だけを書いて,has_many :reverse_of_relationships, class_name: 'Relationship', foreign_key: 'follow_id', dependent: :destroy
は書かなくても, 外部キー制約のエラーが出なかったのか- => relationshipsテーブル に関しては, migrationファイルの時点で, すでに外部キー設定をしている為
- => reverse_of_relationships に関しては, user modelのファイルの5行目で初めて出てきたので, 外部キー設定などまだ何もされていない. その為エラーが出なかった.
has_many :relationships, dependent: :destroy
のコメントアウトを外し, 再度userテーブルのデータを消してみる$ User.find(5).destroy
- user テーブルからid 5 が消えた
- プラス, user modelに書いた内容
has_many :relationships, dependent: :destroy has_many :reverse_of_relationships, class_name: 'Relationship', foreign_key: 'follow_id', dependent: :destroy
- に従って, relationshipsテーブルから, user_idが5, または, follow_idが5 のレコードが, 連動して消えた.
user modelにさらに追記
- follow, unfollow, following? のメソッドを追記.
- controllerではなく, modelに書く(DB関連はmodelに書くのがいい. controllerに書いても動くが)
def follow(other_user) unless self == other_user self.relationships.find_or_create_by(follow_id: other_user.id) end end def unfollow(other_user) relationship = self.relationships.find_by(follow_id: other_user.id) relationship.destroy if relationship end def following?(other_user) self.followings.include?(other_user) endそれぞれのdefを解釈する(ざっくりと)
def follow(other_user) unless self == other_user self.relationships.find_or_create_by(follow_id: other_user.id) end end
unless self == other_user
: 自身が, 他のuser idと同じでなければ
- (followアクションを読んだ時, 投げた引数が自身のuser idでなければ)
self.relationships.find_or_create_by(follow_id: other_user.id)
:
- users経由でrelationshipsテーブルの, follow_idカラムに, 投げた引数の値でfind_or_create_byする(見つけに行き, あればそのrelationのレコードを返し, なかったらcreateする)
def unfollow(other_user) relationship = self.relationships.find_by(follow_id: other_user.id) relationship.destroy if relationship end
relationship = self.relationships.find_by(follow_id: other_user.id)
:
- users経由でrelationshipテーブルの, follow_idカラムに, 投げた引数の値が見つかれば relationshipに代入する
relationship.destroy if relationship
:
- relationshipに値が入っていれば, destroy. user_idが自分で, follow_idが他人. その「レコード」を消す.
def following?(other_user) self.followings.include?(other_user) end
self.followings.include?(other_user)
:
- 自分が属するレコード群のfollow_idカラムに, 指定した引数の値が入っているか.
controllerを作成する
$ rails generate controller relationships create destroy
- (helperやviewが一気にできるので, 手動でcontrollerだけ作った方がいいかもしれない)
- controllerに以下を追記する
def index end def create user = User.find(params[:follow_id]) following = current_user.follow(user) if following.save flash[:success] = 'ユーザーをフォローしました' redirect_to user else flash.now[:alert] = 'ユーザーのフォローに失敗しました' redirect_to user end end def destroy user = User.find(params[:follow_id]) following = current_user.unfollow(user) if following.destroy flash[:success] = 'ユーザーのフォローを解除しました' redirect_to user else flash.now[:alert] = 'ユーザーのフォロー解除に失敗しました' redirect_to user end end
- 上記sampleには
current_user
と出てくる箇所があるが, これは多分deviseを使っているから. deviseが入っていないと反応しない.- formボタンをクリックして, follow, unfollowするテストを. 一回indexでやってみる. viewページには, 以下を記述.
<% unless current_user == @user %> <% if current_user.following?(@user) %> <%= form_for(current_user.relationships.find_by(follow_id: @user.id), html: { method: :delete }) do |f| %> <%= hidden_field_tag :follow_id, @user.id %> <%= f.submit 'Unfollow', class: 'btn btn-danger btn-block' %> <% end %> <% else %> <%= form_for(current_user.relationships.build) do |f| %> <%= hidden_field_tag :follow_id, @user.id %> <%= f.submit 'Follow', class: 'btn btn-primary btn-block' %> <% end %> <% end %> <% end %>
- しかし,
current_user
は反応しないので, 一時的に User.find(4) などに, ベタ書きしておく.(こういうのをハードコーディングという?)- current_userを
user id: 4
- follow対象を
user id: 6
ということにして, 以下話を進める<% if User.find(4).following?(User.find(6)) %> <%= form_for(User.find(4).relationships.find_by(follow_id: User.find(6).id), html: { method: :delete }) do |f| %> <%= hidden_field_tag :follow_id, User.find(6).id %> <%= f.submit 'Unfollow', class: 'btn btn-danger btn-block' %> <% end %> <% else %> <%= form_for(User.find(4).relationships.build) do |f| %> <%= hidden_field_tag :follow_id, User.find(6).id %> <%= f.submit 'Follow', class: 'btn btn-primary btn-block' %> <% end %> <% end %>
- この状態で, indexのwebページにアクセス
- relationship_path などないとエラー
- routesに
resources :relationships
を一旦設定- ページが表示された
- follow ボタンをクリックしてみる
- 4 が 6 をfollowしている, というレコードができた.
- unfollowボタンをクリックしてみる
- レコードが消えた. 正常に動く事を確認.
再確認
- viewに書いてある以下のコードの流れを追う.
<% if User.find(4).following?(User.find(6)) %> <%= form_for(User.find(4).relationships.find_by(follow_id: User.find(6).id), html: { method: :delete }) do |f| %> <%= hidden_field_tag :follow_id, User.find(6).id %> <%= f.submit 'Unfollow', class: 'btn btn-danger btn-block' %> <% end %> <% else %> <%= form_for(User.find(4).relationships.build) do |f| %> <%= hidden_field_tag :follow_id, User.find(6).id %> <%= f.submit 'Follow', class: 'btn btn-primary btn-block' %> <% end %> <% end %>
<% if User.find(4).following?(User.find(6)) %>
- User.find(4)が, userのmodelに書かれたメソッド, following? ↓ を呼んでいる.
def following?(other_user) self.followings.include?(other_user) end
self.followings.include?(other_user)
には, アソシエーションが使われている.followings
の部分は, user modelに書かれたhas_many :followings, through: :relationships, source: :follow
が使われている.user テーブルがhas_manyしている仮想テーブル, 「followings」(relationshipsテーブルのfollow_idのカラムの集合)の中に, User.find(6) が includeされているか. をbooleanで返す.
<%= form_for(User.find(4).relationships.find_by(follow_id: User.find(6).id), html: { method: :delete }) do |f| %>
の部分について.
- User.find(4).relationships という書き方ができるのは, usersとrelationsが関連付けされているから
- relationshipsテーブル上の, User.find(4)でfollow_idがUser.find(6).id のレコードに対して form_forする.
- methodはdeleteでpostする
- form_forの飛び先は
localhost:3000/relationships/1
- このroutes(
relationships/:id
) にdeleteで飛ばしたら, controllerのdestroyが動く
destroyメソッドの中では, unfollowメソッド(user modelに書いてある) が動く.
<%= hidden_field_tag :follow_id, User.find(6).id %>
- follow_idをUser.find(6).idにして hiddenで設置
<%= f.submit 'Unfollow', class: 'btn btn-danger btn-block' %>
- Unfollowというテキストで, class(この場合は見た目) は 〜〜 にしてsubmit.
<%= form_for(User.find(4).relationships.build) do |f| %>
build
= new の事.- User.find(4)が, relationshipsテーブルで新規にレコード作る, という事.
以上で
- railsにフォロー機能を実装できた事になります.
- ベタ書きしている部分(
User.find(4)
) などは, deviseと連携してcurrent_user
とかにして,変数的にすればOKです!
- 投稿日:2020-03-23T19:21:40+09:00
[Rails備忘録]配列を平坦化させる Array#flatten
Railsの備忘録です。
Array#flatten # 自身を再帰的に平坦化する例。 a = [1, [2, 3, [4], 5]] p a.flatten #=> [1, 2, 3, 4, 5] p a #=> [1, [2, 3, [4], 5]]参考ページ
Ruby 2.7.0 リファレンスマニュアル instance method Array#flatten
[初心者向け] RubyやRailsでリファクタリングに使えそうなイディオムとか便利メソッドとか
- 投稿日:2020-03-23T18:20:23+09:00
3/23 最終課題五日目
3/23
最終課題五日目メモです
できている商品詳細ページをgithubに
出してコードレビューをするコンフリクトが起こっていたので
修正した後にコードレビューをしてもらうaタグはlink_toで書きましょう。
と指摘を頂いたので
修正
修正後無事にLGTMともらうその後マージしてマスターにプッシュする
そして今まで作ったとこに変なところが無いかや修正があるか探すとりあえず簡単な修正をしてコミットし
マスターにプッシュしましたこれで商品詳細ページ(マークアップ)が終わったので次に進む
次はサーバーサイドに進むが
みんなで話し合った結果
先に商品出品のサーバーサイドを進めることに決定
2人で進めることになりました
画像のところとその他で別れることに決定Git hubでブランチを作った後さらに
ブランチを作ることで二人で進めれるようにする
(孫ブランチ?)
子ブランチの状態で新しいブランチを作る
そうするとマスターブランチの小ブランチを作るか
小ブランチの孫ブランチを作るかと選択が出るので
孫の方を選択
作る際のブランチの名前は小ブランチの名前と一緒もしくは似ているものは
エラーの原因になるプルリクエスするときは気をつける
間違ってもマスターにプルリクエストを送ることはしないこと!!!やること
* 商品モデルと商品の画像モデルを作成し、1つの投稿フォームで記事とそれに紐付く複数の画像を投稿できる
* 画像は送信前にプレビューを表示できる
* 上記要件を満たし、1度出品した商品の編集ができる
* 編集画面から、ひもづく画像の変更、削除、追加ができる
* 商品を削除する機能がある
* 商品を削除する際は、削除した商品に紐づく画像が同時に削除されるデータベースを作らないとできないので
データベースを作る
Itemsテーブルを作る際外部キーが原因でエラー
Itemテーブルと関連づけされているテーブルを作っていく調べたこと
Webスクレイピングとは、ウェブサイトのHTMLから必要なデータを取得する事を言い、
それを行うプログラムをスクレイパとも呼びます。Ancestryとは
AncestryはRuby on RailsのActiveRecordモデルのレコードを
ツリー構造(階層)として編成することを可能にするGemです。
カテゴリーを作る際に必要になるgemです
https://qiita.com/Rubyist_SOTA/items/49383aa7f60c42141871
参考ページorderとは
取得した値に対して、条件を指定して並び替えることができる機能
並びの順番を変えることができる
降順にする場合は”DESC”、昇順は”ASC”
https://techacademy.jp/magazine/7727
参考ページカテゴリーにpathカラムがあったので
Pathカラムがわからなかったので調べた
https://kyabatalian.hatenablog.com/entry/2016/12/19/193430
参考ページ
path列に対してパターン比較すれば先祖を取得できます。
メソッドとかもあるので必要みたいですマイグレーションをしてエラーが出たので
結局は原因はわからなかったけど
メモで
Gemfile
gem 'ancestry'ターミナル
$ bundle install
$ rails g migration add_ancestry_to_category ancestry:string:index
$ rake db:migrate
ここでエラー
エラーの原因を探して解決方法を見る
テーブルがおかしなことになっているのでリセットする
$ rake db:migrate reset
またエラー
というのを繰り返した結果
途中でモデルに記述するとうまくいった
記述していなかったのが原因?
初歩ミスっぽいです
- 投稿日:2020-03-23T17:47:15+09:00
【Rails】ancestryで簡単に多階層型データの作成し呼び出す
概要
・多階層型DB作成のためのgem "ancestry" の導入
・seeds.rbでのデータインプット効率化
・viewファイルへの表示階層型DB作成のためのgem "ancestry" の導入
gemの導入
Gemfilegem 'ancestry'ターミナル$ bundle install $ rails g migration add_ancestry_to_category ancestry:string:index $ rake db:migrateモデルの設定
categories_controller.rbclass Category < ApplicationRecord has_many :items has_ancestry enditems_controller.rbclass Item < ApplicationRecord belongs_to :category endデータの追加
seeds.rbdrink = Category.create(name: "飲み物") drink_juice = juice.children.create(name: "ジュース") drink_juice.children.create([{name: "オレンジ"}, {name: "アップル"}, {name: "グレープ"}])ターミナル% rails db:seed
seeds.rbでのデータインプット効率化
数件、数十件レベルのデータ量であれば、上記「データの追加」でも問題ありませんが、
それ以上の膨大なデータを作成するのであれば、以下の方法で効率的に作成しましょう。seeds.rb@category1 = Category.create(name:"飲み物") category1s = [ {level2:"ジュース",level2_children:["オレンジジュース","コーラ","サイダー"]}, {level2:"お酒",level2_children:["ビール","ハイボール","焼酎","ワイン"]}, {level2:"お茶",level2_children:["緑茶","烏龍茶","ほうじ茶","はとむぎ茶"]}, ] category1s.each.with_index(1) do |category1,i| level2_var="@category1_#{i}" level2_val= @category1.children.create(name:"#{category1[:level2]}") eval("#{level2_var} = level2_val") category1[:level2_children].each do |level2_children_val| eval("#{level2_var}.children.create(name:level2_children_val)") end endターミナル% rails db:seed
これでデータベースに反映されるはずです。
子要素の呼び出し
親要素.children親要素の呼び出し
子要素.parent以上です。
最後まで読んでいただきありがとうございました。
- 投稿日:2020-03-23T15:36:35+09:00
redisで特定のデータを一括で削除、操作をするスクリプト
redisで有効期限が設定されていないセッションデータを一括削除するスクリプト
今回は有効期限が設定されていないデータを削除するscriptを作りました。
シンプルに検索して、eachして、ifでチェックして該当すれば削除を行ってます。
session-delete.luaredis.call('select', '1') --[[ DBの数字を選んでください ]] local expresion = 'session*' --[[ 精査対象を正規表現で捉えてください ]] local delete_count = 0 for _,session_key_id in ipairs(redis.call('keys', expresion)) do if redis.call('TTL', session_key_id) == -1 then --[[ 有効期限が -1 (未設定)のものを削除します ]] redis.call('DEL', session_key_id) delete_count = delete_count + 1 end end return 'deleted:'..delete_count実行方法は簡単
bashredis-cli eval "$(cat search.lua)" 0docker環境ならこんな感じで
bashdc exec imdb redis-cli eval "$(cat search.lua)" 0
- 投稿日:2020-03-23T15:31:15+09:00
【rails】seedデータからファイルを読み込んで、ファイル内のimageを一括で保存する
railsのseedデータからファイルにあるイメージを一括で保存する方法を、説明します。
準備)
①carrierwaveを使います。
②db/imagesを作成します。以下のコードでイメージを保存できます。
seeds.rbImage.create!([ (1..20).map do |n| [ id: n, image: open("#{Rails.root}/db/images/#{n}.jpg") ] end ])しかし、この場合、イメージ名を1.jpgとかにしないといけないから、めんどくさい。。。
なので、このコードで楽にいけます!
seeds.rbimages = Dir.open("#{Rails.root}/db/images") images.each.with_index(-1) do |image, i| next if image =~ /^\.+$/ Image.create!( id: i, name: image, image: open("#{Rails.root}/db/images/#{image}") ) end説明します。
images = Dir.open("#{Rails.root}/db/images")
ここで、ディレクトリー内の全ての画像を並べることができます。もし、以下のコードで計算すると、、
images.each do |image| puts image end (結果) . .. XXX.jpg XXX.png XXX.jpgこんな感じになり、「.」[..]が邪魔です。
なので、
next if image =~ /^\.+$/
を入れることで、「.」[..]がスキップされるようになります。今回は、idも入れたかったので、
each_with_indexメゾット
を使いましたが、「0」から始まるし、「.」[..]をスキップするので、逆に2から始まってしまいます。なので、「1」から始めるために、
each.with_index(-1)
を使っいました。これで、簡単にseedで画像を保存できるようになりました。めでたし。
ご指摘の点があれば、ぜひツッコンでください!
よろしくお願いします!
- 投稿日:2020-03-23T14:23:01+09:00
日時表示を日本時間に修正
タイムゾーンの設定
application.rbに、タイムゾーンの設定を追記します。
【例】application.rb〜省略〜 # Initialize configuration defaults for originally generated Rails version. config.time_zone = 'Tokyo' #追記 # Settings in config/environments/* take precedence over those specified here. # Application configuration can go into files in config/initializers # -- all .rb files in that directory are automatically loaded after loading # the framework and any gems in your application. 〜省略〜
- 投稿日:2020-03-23T14:21:59+09:00
ActiveRecord で unscoped を呼ぶとその前のクエリが消える
ブログ記事からの転載です。
unscoped
を使うことでdefault_scope
を取り除くことができるさて、皆さん大好き
default_scope
ですが、モデルでdefault_scope
を定義すると次のように暗黙的にクエリが追加されます。class User < ActiveRecord::Base default_scope { order(:updated_at) } end # 暗黙的に ORDER BY のクエリが追加れる puts User.all.to_sql # => SELECT "users".* FROM "users" ORDER BY "users"."updated_at" ASC puts User.where(name: "Tom").to_sql # => SELECT "users".* FROM "users" WHERE "users"."name" = 'Tom' ORDER BY "users"."updated_at" ASC毎回
order(:updated_at)
する必要がないので便利ですね。
でも『あ〜今日はdefault_scope
のクエリ追加してほしくないな〜〜〜』って思うときがあると思うんですよ。
そういう時にunscoped
を使うとdefault_scope
のクエリを取り除く事ができます。# unscoped を付けると default_scope はつかなくなる puts User.unscoped.all.to_sql # => SELECT "users".* FROM "users" puts User.unscoped.where(name: "Tom").to_sql # => SELECT "users".* FROM "users" WHERE "users"."name" = 'Tom'これで
default_scope
をつかっていてもシュッと取り除く事ができて便利ですね!
default_scope
以外のクエリも取り除かれる
unscoped
を使うことでdefault_scope
を取り除く事ができるようになります。
しかしunscoped
はめちゃくちゃ強くて『呼び出すよりも前のリレーション』も取り除いてしまします。# unscoped よりも前に付けた where のクエリも消してしまう puts User.where(name: "Tom").unscoped.to_sql # => SELECT "users".* FROM "users"
unscoped
でdefault_scope
を消したい場合は必ず『一番最初』にunscoped
を呼び出しましょう。
逆に『レシーバのクエリを全部消したい』場合はunscoped
を呼び出すと一括で消すことができるので便利です。
- 投稿日:2020-03-23T14:00:55+09:00
Ajaxとjbuilderの参考リファレンスなど
Ajax
【jQuery日本語リファレンス 】Ajax
Ajaxのリファレンスになります。オプションも含めて説明しています。
【js STUDIO】$.ajax()
オプションや動作を含めて説明されています。
Ruby on RailsのAjax処理
RailsでのAjaxの流れを図解つきで説明されています。jbuilder
【GitHub】jbuilder
jbuilderのドキュメントです。
Rails4でJSONを作るならto_jsonよりjbuilder
jbuilderの使い方を丁寧に説明した記事になります。
- 投稿日:2020-03-23T12:07:26+09:00
Herokuへのデプロイ手順|Rails + MySQL
はじめに
MySQLで作成したRailsアプリを、Herokuを使ってデプロイすることがあったので備忘録としてまとめました。同じ環境で初めてデプロイする方の参考になれば幸いです。
自分の環境
- macOS 10.15.3
- DB: MySQL
- Rails 5.0.7
Railsアプリケーションの作成
・アプリ作成 [現在作成中]
・Gemfile修正
sqlite3
が入っている場合は、以下のようにしてproduction環境下で反映されないようにしましょう。Gemfile.rbgroup :development, :test do gem 'sqlite3', '~> 1.4' end下記
mysql2
を追加。Gemfile.rbgem 'mysql2'・config/environments/production.rbの設定
Heroku上の本番環境でHTMLにCSSやJavaScriptが反映されるように、以下の記述を書き換えます。
ターミナル# デフォルトのfalseをtrueにします。 config.assets.compile = true作成したRailsアプリをGit管理
Railsプロジェクトに移動します。(ディレクトリへのパスは自身の物へ置き換えてください)
ターミナル$ cd rails/MyApp以下を実行すると、RailsアプリがGit管理されます。
ターミナル# リポジトリを新規作成。 $ git init # 変更があったすべてのファイルがaddされる。 $ git add . # ファイルの変更や追加などを保存。 $ git commit -m "<ここにコミットメッセージをいれる>"Herokuアカウントの登録
https://jp.heroku.com/ にて登録。
Heroku CLIのインストール
Heroku CLIをインストールすると、ターミナル上でHerokuのコマンド操作ができるようになります。
https://devcenter.heroku.com/articles/heroku-cli
上記サイト、もしくは下記コマンドでインストールできます。ターミナルbrew tap heroku/brew && brew install heroku以下のコマンドで、インストールができているか確認します。
ターミナル$heroku -v >>heroku/7.0.47 darwin-x64 node-v10.1.0SSH公開鍵の作成、Herokuへ追加
・SSH公開鍵の作成
まずは公開鍵が作成されているか確認しましょう。
ターミナルcat ~/.ssh/id_rsa.pub作成されてない場合は以下のように表示されます。
ターミナルNo such file or directoryそれでは公開鍵を作成していきましょう。(実行ディレクトリはどこでも大丈夫です)
ターミナル$ ssh-keygen Generating public/private rsa key pair # 鍵の保存先を聞かれます。変更する必要はないので、そのままEnterキーで進みます。 Enter file in which to save the key (/Users/ユーザー名/.ssh/id_rsa): # 鍵のパスワードを設定するか聞かれます。 # 設定しない場合はそのままEnterを、設定する場合はパスワードを入力します。 Enter passphrase (empty for no passphrase): # パスワードの確認をされます。同じパスワードを入力してください。 # 設定していない場合は空欄のままEnterキーで進みます。 Enter same passphrase again:これで公開鍵が作成されました。確認しましょう。
ターミナルcat ~/.ssh/id_rsa.pub #このように文字列が表示されるはずです。 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAklOUpkDHrfHY17SbrmTIpNLTGK9Tjom/BWDSU GPl+nafzlHDTYW7hdI4yZ5ew18JH4JW9jbhUFrviQzM7xlELEVf4h9lFX5QVkbPppSwg0cda3 Pbv7kOdJ/MTyBlWXFCR+HAo3FXRitBqxiX1nKhXpHAZsMciLq8V6RjsNAQwdsdMFvSlVK/7XA t3FaoJoAsncM1Q9x5+3V0Ww68/eIFmb1zuUFljQJKprrX88XypNDvjYNby6vw/Pb0rwert/En mZ+AW4OZPnTPI89ZPmVMLuayrD2cE86Z/il8b+gw3r3+1nKatmIkjn2so1d01QraTlMqVSsbx NrRFi9wrf+M7Q== schacon@mylaptop.local・Herokuに公開鍵を追加
Herokuにログインします。
ターミナル$ heroku login以下のコマンドでSSH公開鍵がHerokuに追加されます。
ターミナル$ heroku keys:add ~/.ssh/id_rsa.pub追加されたか確認しておきましょう。
ターミナル$ heroku keys # SSHキーの一部が表示されます。 ssh-rsa AAAA...BBBB作成したRailsアプリとHerokuの紐付け
Railsプロジェクトに移動します。(ディレクトリへのパスは自身の物へ置き換えてください)
ターミナル$ cd rails/MyAppHerokuへ新しいアプリケーションを作成しましょう。
ターミナル$ heroku apps:create <好きなアプリ名>以下のように表示されたらアプリ名が既に使われちゃってます。
重複は許されないので他のアプリ名へ変更しましょう。ターミナルName myapp is already takenHerokuにDBを追加
・MySQLを追加
以下のコマンドで、
clearDB
というMysqlを使うためのアドオンがignite
プランで追加されます。ターミナル$ heroku addons:create cleardb:ignite以下のような表示が出たら、クレジットカードの登録が必要です。
ターミナル▸ Please verify your account to install this add-on plan (please enter a credit card) For more ▸ information, see https://devcenter.heroku.com/categories/billing Verify now at ▸ https://heroku.com/verify*アドオンを追加するためにクレジットの登録が必要ですが、iginteプランは無料で使えます。
詳しいプラン内容については以下を参照。
https://elements.heroku.com/addons/cleardb・Herokuアプリに環境変数を設定
以下のコマンドで、環境変数に入る値を取得します。
ターミナル$ heroku config === <アプリの名前> Config Vars CLEARDB_DATABASE_URL: mysql://<ユーザー名>:<パスワード>@<ホスト名>/<データベース名>?reconnect=trueHerokuアプリの環境変数に、上記で取得した値をそれぞれ設定。
ターミナル$ heroku config:add DB_NAME='<データベース名>' $ heroku config:add DB_USERNAME='<ユーザー名>' $ heroku config:add DB_PASSWORD='<パスワード>' $ heroku config:add DB_HOSTNAME='<ホスト名>' $ heroku config:add DB_PORT='3306' # gemで「mysql2」を使用しているので、mysql://ではなく「mysql2://」とします。 $ heroku config:add DATABASE_URL='mysql2://<ユーザー名>:<パスワード>@<ホスト名>/<データベース名>?reconnect=true'設定した値を確認しましょう。
以下のように表示されるはずです。$ heroku config === <アプリの名前> Config Vars CLEARDB_DATABASE_URL: mysql://<ユーザー名>:<パスワード>@<ホスト名>/<データベース名>?reconnect=true DATABASE_URL: mysql2://<ユーザー名>:<パスワード>@<ホスト名>/<データベース名>?reconnect=true DB_HOSTNAME: <ホスト名> DB_NAME: <データベース名> DB_PASSWORD: <パスワード> DB_PORT: 3306 DB_USERNAME: <ユーザー名>Herokuにデプロイ
以下コマンドでローカルリポジトリをHerokuへpushすると、自動でデプロイが進んでいきます。
ターミナル$ git push heroku master*master以外のブランチをpushする場合は以下を実行
ターミナル$ git push heroku <ブランチ名>:master最後に以下のコマンドを入力して、データベースのマイグレーションをします。
ターミナル$ heroku rake db:migrate以下のコマンドを実行すると、ブラウザでアプリケーションにアクセスできます。
ターミナル$ heroku openお疲れ様でした!
参考サイト・記事
mysqlを使ったRailsアプリをHerokuにデプロイする流れ
Deploying with Git
SSH 公開鍵の作成
- 投稿日:2020-03-23T11:54:51+09:00
ツイッター風Railsアプリをテストする(統合テスト編)
この記事の基本的な方針
今回は別記事、ツイッター風Railsアプリ最短復習(忙しい人の流し読みで開発シリーズ)で作ったアプリの統合テストをします。
手を動かしながら読みたいようでしたら、以下でこのツイッター風Railsアプリを手に入れてください。
Terminal$ git clone https://github.com/annaPanda8170/cheaptweet.git $ bundle install $ bundle exec rake db:create $ bundle exec rake db:migrate基本解説はしません。手順のみ示します。
想定する読み手
既に一度Railsアプリをチュートリアルやスクール等で作ったことがある方を想定しております。
Mac使用で、パソコンの環境構築は完了していることが前提です。具体的な手順
①準備
Gemfile#省略 group :development, :test do #省略 gem 'rspec-rails' gem 'factory_bot_rails' end group :development do #省略 gem 'spring-commands-rspec' end #省略※spring-commands-rspecは起動時間を速くするためのものでなくても問題はありません。
Terminal$ bundle install $ rails g rspec:install $ rails g rspec:feature cheapTweet $ rails g factory_bot:model user $ rails g factory_bot:model tweet $ bundle exec spring binstub rspec control + c $ rails s.rspec--format documentation※これでRspecの出力が読みやすくなるそうです。
spec/rails_helper.rb#省略 require 'capybara/rspec' include Warden::Test::Helpersここで空っぽのまま一度起動してみます。
Terminal$ bundle exec rspecOutputCheapTweets add some scenarios (or delete) /Users/handaryouhei/Desktop/cheaptweet/spec/features/cheap_tweet_spec.rb (PENDING: Not yet implemented) Pending: (Failures listed here are expected and do not affect your suite's status) 1) CheapTweets add some scenarios (or delete) /Users/handaryouhei/Desktop/cheaptweet/spec/features/cheap_tweet_spec.rb # Not yet implemented # ./spec/features/cheap_tweet_spec.rb:4 Finished in 0.0014 seconds (files took 2.21 seconds to load) 1 example, 0 failures, 1 pendingspec/factories/tweets.rbFactoryBot.define do factory :tweet do text {"hello"} association :user end endspec/factories/users.rbFactoryBot.define do factory :user do sequence(:nickname) { |n| "annaPanda#{n}" } sequence(:email) { |n| "a#{n}@a" } password {"111111"} password_confirmation {"111111"} end end②テスト構築
spec/features/cheap_tweet_spec.rbrequire 'rails_helper' RSpec.feature "CheapTweets", type: :feature do scenario "新規登録するとログアウトが表示されているTOP画面に遷移する" do visit root_path click_link "会員登録" fill_in "user_nickname", with: "annaPanda" fill_in "user_email", with: "a@a" fill_in "user_password", with: "111111" fill_in "user_password_confirmation", with: "111111" click_button "Sign up" expect(page).to have_content 'ログアウト' end scenario "ログインするとログアウトが表示されているTOP画面に遷移する" do user = FactoryBot.create(:user) visit root_path click_link "ログイン" fill_in "user_email", with: "a1@a" fill_in "user_password", with: "111111" click_button "Log in" expect(page).to have_content 'ログアウト' end scenario "非ログイン状態でTOP画面に遷移するとログアウトが表示されていない" do visit root_path expect(page).not_to have_content 'ログアウト' end scenario "投稿したらデータベースにtweetが一つ増える" do user = FactoryBot.create(:user) login_as(user, scope: :user) visit root_path expect{ click_link "投稿" fill_in "tweet_text", with: "こんにちは" click_button "投稿" }.to change( Tweet.all, :count ).by(1) end scenario "自分で投稿したtweetをshowして編集するとindexに反映される" do user = FactoryBot.create(:user) login_as(user, scope: :user) visit root_path click_link "投稿" fill_in "tweet_text", with: "こんにちは" click_button "投稿" click_link "こんにちは" click_link '編集' fill_in "tweet_text", with: "こんにちは!" click_button '編集' visit root_path expect(page).to have_content 'こんにちは!' end scenario "自分以外が投稿したtweetをshowすれば編集ボタンが表示されない" do tweet = FactoryBot.create(:tweet) visit root_path click_link "a" expect(page).not_to have_content '編集' end scenario "自分で投稿したtweetをshowして削除ボタンを押せばデータベースのtweetが一つ減る" do user = FactoryBot.create(:user) login_as(user, scope: :user) visit root_path click_link "投稿" fill_in "tweet_text", with: "こんばんは" click_button "投稿" click_link "こんばんは" expect{ click_link "削除" }.to change( Tweet.all, :count ).by(-1) end scenario "自分以外が投稿したtweetをshowすれば削除ボタンが表示されない" do tweet = FactoryBot.create(:tweet) visit root_path click_link "hello" expect(page).not_to have_content '削除' end scenario "ヘッダーの自分の名前をクリックすると自分の投稿一覧が表示される" do tweet = FactoryBot.create(:tweet) user = FactoryBot.create(:user) login_as(user, scope: :user) visit root_path click_link "投稿" fill_in "tweet_text", with: "おはよう" click_button "投稿" click_link "投稿" fill_in "tweet_text", with: "さようなら" click_button "投稿" within 'header' do click_link user.nickname end expect(page).to have_content 'おはよう' expect(page).to have_content 'さようなら' expect(page).not_to have_content 'hello' end scenario "TOP画面の投稿者をクリックするとそのユーザーの一覧が表示される" do tweet = FactoryBot.create(:tweet) user = FactoryBot.create(:user) login_as(user, scope: :user) visit root_path click_link "投稿" fill_in "tweet_text", with: "おはよう" click_button "投稿" click_link "投稿" fill_in "tweet_text", with: "さようなら" click_button "投稿" click_link tweet.user.nickname expect(page).not_to have_content 'おはよう' expect(page).not_to have_content 'さようなら' expect(page).to have_content 'hello' end scenario "コメントすると表示される" do tweet = FactoryBot.create(:tweet) user = FactoryBot.create(:user) login_as(user, scope: :user) visit root_path click_link "hello" fill_in "comment_text", with: "ハロー" click_button "コメント" expect(page).to have_content 'ハロー' end end
click_link
とclick_button
で指定しているのはテキストです。
fill_in
で指定しているのはidです。
have_content
で表示されているかを確認しています。Terminal$ bundle exec rspecOutputCheapTweets 新規登録するとログアウトが表示されているTOP画面に遷移する ログインするとログアウトが表示されているTOP画面に遷移する 非ログイン状態でTOP画面に遷移するとログアウトが表示されていない 投稿したらデータベースにtweetが一つ増える 自分で投稿したtweetをshowして編集するとindexに反映される 自分以外が投稿したtweetをshowすれば編集ボタンが表示されない 自分で投稿したtweetをshowして削除ボタンを押せばデータベースのtweetが一つ減る 自分以外が投稿したtweetをshowすれば削除ボタンが表示されない ヘッダーの自分の名前をクリックすると自分の投稿一覧が表示される TOP画面の投稿者をクリックするとそのユーザーの一覧が表示される コメントすると表示される Finished in 0.87206 seconds (files took 2.18 seconds to load) 11 examples, 0 failures
まとめ
読むだけなら割と直感的にいけると思います。
この統合テストさえ済ませていれば、コントローラは不要なのでは?という考えもあるようです。
モデルのテストとこの統合テストを行うことがRailsアプリのテストのスタンダードになるかもしれませんね。
- 投稿日:2020-03-23T11:00:59+09:00
Rails2.7 Rails6 Docker React環境でシンプルCRUD実装
streampackのminsuです。
以前の記事で Docker + Rails + React の環境構築を行いindexページの表示まで行ったのでCRUD機能を追加します。
ですが期間も空いているため、折角なので以前の環境である
- Rails 5.1.4
- Ruby 2.4.1
- mysql 5.7
ではなく、新しい環境で作り直します。
最新版確認
https://rubygems.org/gems/rails
https://www.ruby-lang.org/ja/downloads/作成環境
- Rails 6.0.2
- Ruby 2.7
- mysql 5.7
ファイルの用意
Gemfile
Gemfile.lock
Dockerfile
docker-compose.yml
を作成します。Gemfilesource "https://rubygems.org" gem "rails", "6.0.2"Gemfile.lockFROM ruby:2.7.0 RUN apt-get update -qq && \ apt-get install -y \ nodejs \ build-essential RUN apt-get update && apt-get install -y curl apt-transport-https wget && \ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \ echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \ apt-get update && apt-get install -y yarn RUN mkdir /app WORKDIR /app ADD Gemfile* /app/ RUN bundle install -j4 --retry 3 ADD . /app WORKDIR /app CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]docker-compose.ymlversion: '3' services: db: image: mysql:5.7 command: mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci ports: - "4306:3306" environment: - MYSQL_ROOT_PASSWORD=root volumes: - mysql_vol:/var/lib/mysql app: build: . command: /bin/sh -c "rm -f /app/tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" volumes: - .:/app ports: - "3000:3000" depends_on: - db volumes: mysql_vol:rails app作成
rails new
rails new を行います。
$ docker-compose run app rails new . --force --database=mysqldb設定を変更します。
database.ymlusername: root password: root #docker-compose.ymlのMYSQL_ROOT_PASSWORD host: db #docker-compose.ymlのサービス名今回も gem
react-rails
を利用するのでGemfileに追記します。Gemfilegem 'react-rails'再度 build して
$ docker-compose buildreactを使うので下記コマンドを実行
$ docker-compose run app rails webpacker:install $ docker-compose run app rails webpacker:install:react $ docker-compose run app rails generate react:installmodel 作成
$ docker-compose run app rails g model List title:string description:string $ docker-compose run app rails db:create $ docker-compose run app rails db:migrateかなりの数の warning 出てきた。
Ruby 2.7.0に対応していないgemが存在することに起因しているようで非表示にすることもできる* が必要なwarningも見逃す可能性があるのでスルーすることにする。
*bash_profileにexport RUBYOPT='-W:no-deprecated -W:no-experimental'
を追加controller 作成
lists controller と view を作成
$ docker-compose exec app rails g controller Lists indexlists_controller.rbclass ListsController < ApplicationController def index @lists = List.all end endindex.html.erb<%= react_component 'ListsIndex', lists: @lists %>react_component タグを用いてreactを呼び出します。
react file 作成
viewから呼び出すreact fileを実装していきます。
$ rails g react:component ListsIndexコマンドで app/javascript/components/ListsIndex.js が作成されるので編集します。
ListsIndex.jsimport React from "react" import PropTypes from "prop-types" export default class Lists extends React.Component { constructor(props){ super(props) this.state = { lists: [] }; } componentDidMount(){ this.setState({ lists: this.props.lists }) } render () { return ( <div> <table> <thead> <tr> <th>ID</th> <th>Title</th> <th>Description</th> </tr> </thead> <tbody> {this.state.lists.map((list) => { return ( <tr key={list.id}> <td>{list.id}</td> <td>{list.title}</td> <td>{list.description}</td> </tr> ); })} </tbody> </table> </div> ); } }動作確認
無事に一覧が表示されました。
simple CRUD の実装
railsにapiを追加します。
apiで行うアクションは index, create, update, destroy です。
/api/v1/xxx
でアクセスできるようにrouteを設定し、controllerを追加します。routes.rbRails.application.routes.draw do get 'lists/index' namespace :api do namespace :v1 do resources :lists, only: [:index, :create, :update, :destroy] end end endapp/controllsers/api/v1/lists_controllser.rbclass Api::V1::ListsController < ApplicationController protect_from_forgery with: :null_session def index render json: List.all end def create list = List.create(list_params) render json: list end def update list = List.find(params[:id]) list.update(list_params) render json: list end def destroy List.destroy(params[:id]) end private def list_params params.require(:list).permit(:id, :title, :description) end endcontrollerには基本的なメソッド、そして
protect_from_forgery with: :null_session
を記述しました。
http://localhost:3000/api/v1/lists
でindexが呼び出されリストが取得できるはずです。index
reactからapiを利用してlists を取得します。
componentDidMountを書き換えます。ListsIndex.jscomponentDidMount(){ this.getIndex(); } getIndex(){ fetch('/api/v1/lists.json') .then((response) => {return response.json()}) .then((data) => {this.setState({ lists: data }) }); }delete
delete機能を実装します。
ボタンを追加return ( <div> <div>this is list</div> <table> <thead> <tr> <th>ID</th> <th>Title</th> <th>Description</th> <th>function</th> </tr> </thead> <tbody> {this.state.lists.map((list) => { return ( <tr key={list.id}> <td>{list.id}</td> <td>{list.title}</td> <td>{list.description}</td> <td> <button onClick={() => this.handleDelete(list.id)}>delete</button> </td> </tr> ); })} </tbody> </table> </div> );ボタンから呼び出されるhandleDeleteを実装します。
handleDelete(id){ fetch(`http://localhost:3000/api/v1/lists/${id}`, { method: 'DELETE', headers: { 'Content-Type': 'application/json' } }) .then((response) => { console.log('List was deleted'); this.deleteList(id); }) } deleteList(id){ let lists = this.state.lists.filter((list) => list.id != id) this.setState({ lists: lists }) }apiでのdestroyだけではstateの値は変わらないので、画面は更新されません。
そのためdeleteListにてstateの値を変更しています。constructorに下記も追記します。
constructor(props){ ... this.getIndex = this.getIndex.bind(this); this.handleDelete = this.handleDelete.bind(this); this.deleteList = this.deleteList.bind(this); }画面を確認すると deleteボタンが追加されており、要素の削除が行えます。
create
要素追加のformを作成します。
stateにてformの値を管理するために下記のように追記します。constructor(props){ super(props) this.state = { // lists: this.props.lists lists: [], form: { title: "", description: "", } }; ...各inpuフォームとaddボタンを追加
return ( <div> <div>this is list</div> <table> <thead> <tr> <th>ID</th> <th>Title</th> <th>Description</th> <th>function</th> </tr> </thead> <tbody> {this.state.lists.map((list) => { return ( <tr key={list.id}> <td>{list.id}</td> <td>{list.title}</td> <td>{list.description}</td> <td> <button onClick={() => this.handleDelete(list.id)}>delete</button> </td> </tr> ); })} <tr> <td></td> <td><input type="text" value={this.state.form.title} onChange={e=>this.handleChange(e,'title')} /></td> <td><input type="text" value={this.state.form.description} onChange={e=>this.handleChange(e,'description')} /></td> <td><button onClick={() => this.handleCreate()}>add</button></td> </tr> </tbody> </table> </div> ); }ここで利用する
handleChange
とhandleCreate
を実装します。
handleChangeではinputフォームの入力値をstateにて管理させています。handleChange(e,key){ let target = e.target; let value = target.value; let form = this.state.form; form[key] = value; this.setState({ form: form }); }handleCreateではapiのcreateメソッドを呼び出して要素の追加を行います。
追加後はstateのlistsの更新と
inputフォームの値のリセットを行なっています。handleCreate(){ let body = JSON.stringify({ list: { title: this.state.form.title, description: this.state.form.description } }) fetch('http://localhost:3000/api/v1/lists', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: body, }) .then((response) => {return response.json()}) .then((list)=>{ this.addList(list); this.formReset(); }) } addList(list){ this.setState({ lists: this.state.lists.concat(list) }) } formReset(){ this.setState({ form:{ title: "", description: "" } }) }constructorに下記を追記
this.handleChange = this.handleChange.bind(this); this.addList = this.addList.bind(this); this.formReset = this.formReset.bind(this);画面を確認するとcreate用のinputフォームが追加され、addボタンのクリックにより要素の追加を行えます。
完成したListIndex.js
ListIndex.jsimport React from "react" import PropTypes from "prop-types" class ListsIndex extends React.Component { constructor(props){ super(props) this.state = { // lists: this.props.lists lists: [], form: { title: "", description: "", } }; this.getIndex = this.getIndex.bind(this); this.handleDelete = this.handleDelete.bind(this); this.deleteList = this.deleteList.bind(this); this.handleChange = this.handleChange.bind(this); this.addList = this.addList.bind(this); this.formReset = this.formReset.bind(this); } componentDidMount(){ this.getIndex(); } getIndex(){ fetch('/api/v1/lists.json') .then((response) => {return response.json()}) .then((data) => {this.setState({ lists: data }) }); } handleDelete(id){ fetch(`http://localhost:3000/api/v1/lists/${id}`, { method: 'DELETE', headers: { 'Content-Type': 'application/json' } }) .then((response) => { console.log('List was deleted'); this.deleteList(id); }) } deleteList(id){ let lists = this.state.lists.filter((list) => list.id != id) this.setState({ lists: lists }) } handleChange(e,key){ let target = e.target; let value = target.value; let form = this.state.form; form[key] = value; this.setState({ form: form }); } handleCreate(){ let body = JSON.stringify({ list: { title: this.state.form.title, description: this.state.form.description } }) fetch('http://localhost:3000/api/v1/lists', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: body, }) .then((response) => {return response.json()}) .then((list)=>{ this.addList(list); this.formReset(); }) } addList(list){ this.setState({ lists: this.state.lists.concat(list) }) } formReset(){ this.setState({ form:{ title: "", description: "" } }) } render () { return ( <div> <table> <thead> <tr> <th>ID</th> <th>Title</th> <th>Description</th> <th>function</th> </tr> </thead> <tbody> {this.state.lists.map((list) => { return ( <tr key={list.id}> <td>{list.id}</td> <td>{list.title}</td> <td>{list.description}</td> <td> <button onClick={() => this.handleDelete(list.id)}>delete</button> </td> </tr> ); })} <tr> <td></td> <td><input type="text" value={this.state.form.title} onChange={e=>this.handleChange(e,'title')} /></td> <td><input type="text" value={this.state.form.description} onChange={e=>this.handleChange(e,'description')} /></td> <td><button onClick={() => this.handleCreate()}>add</button></td> </tr> </tbody> </table> </div> ); } } export default ListsIndexまとめ
Ruby2.7, Rails6
Docker
react
での環境構築
reactからのrails api利用の実装を行いました。
自分用のまとめですが、誰かの助けとなれば幸いです。
- 投稿日:2020-03-23T10:50:33+09:00
headerとflashの間にできた謎の余白を消す(Devise, Bootstrap)
はじめに
先日投稿したこちらで
Bootstrap + jQueryを使ってFlashなどの
<button>xボタン</button>
をクリックした時にFlashを閉じる機能を実装しました。
ただ、ブラウザ場で確認すると、
Flashが消える前、後、双方で との間に余白がないようにぴったり合わしているにも関わらず
<header>
とflash
の間に余白が発生していました。この余白を無くすことができたので紹介します。
開発環境
- bootstrap-sass (3.3.7)
- devise (4.7.1)
- jquery-rails (4.3.1)
- rails (5.2.4.1)
前置き (layoutのデザイン)
header.html<header class= "header-layout navbar navbar-fixed-top navbar-inverse">
- Bootstrapの
navbar-fixed-top
を使ってheaderを固定しております。- headerの下の
<%= yield%>
には画面いっぱいの背景画像を設置した表紙のようになっております。headerの下にHome画面レイアウトの背景画像がぴったりになるように
<body>
padding: top;
でしっかり合わせてあります。)(余白ができていた時のパターン↓)
application.html.erb<body> <%= render 'shared/header'%> <%= notice %> //ここ <%= alert %> //ここ <body>*おそらく、ユーザーがログイン後に
flash[:notice]
が呼び出された場合、flash[:alert]は、余白としてflash[:norice]の上に表示されていました。if構文(余白消えない。)
まずこちらを参考にしてみた
https://whatsupguys.net/programming-school-dive-into-code-learning-47/]app/views/shared/_flash_messages.html.erb<% if notice %> //条件 <% elsif alert %> //条件 <% end %>上記のように条件を組んでブラウザ場の検証を確認しても
ブラウザ場の検証(要素) <header> </header> " " //謎の余白 <class= "alert"> "ログインしました" //flash[:notice] </>flashは表示できているけれど
<header>
とflash
が呼ばれる<class= "alert">
の間に" "
と表示され、余白の原因になっています。
おそらく" "
の部分は表示されなかったflash[alert]
だと思います。
if構文を使って条件を書いても、、余分な余白は表示されたままでした。解決方法
解決方法を調べてみました。
まず、deviseとBootstrapを組み合わせる祭、Bootstrapには、
deviseのkey
である[:notice]
,[:alert]
に対応したクラスがないので、key
を置き換えることが最善です。こちらを参考に
(https://hachy.github.io/2019/10/15/flashes-with-devise-and-bootstrap.html)app/helpers/users_helper.rbmodule UsersHelper #Bootstrapに対応できるdevise flash[notice][alert]を変更する def bootstrap_alert(key) case key when "alert" "warning" when "notice" "success" when "error" "danger" end end end
:alert
→:warning
,:notice
→:success
に変更する為にヘルパーメソッドを作成しています。(devise userモデルを使っているのでそのヘルパーを利用しています。)Flashの変更
変更前↓ (deviseのデフォルト)
application.html.erb<body> <%= render 'shared/header'%> <%= notice %> //ここ <%= alert %> //ここ <body>変更後↓
app/views/shared/_flash_messages.html.erb<% flash.each do |key, value| %> <div class="alert alert-<%= bootstrap_alert(key)%> close-flash"> <%= value %> <button type="button" class="close close-button" data-dismiss="alert"> × </button> </div> <% end %>
- deviseのデフォルトflashを変更し、Bootstrapに組み合わせれるように作ったヘルパーメソッド
bootstrap_alert(key)
を使ってdeviseflash[:key]
を変更。これでFlash[:key]がしっかりBotstrapにも適用でき、余分な
" "
が呼び出されることなく、特定のflash[:key]だけが呼び出され余白も消えました。参考にしたサイト
- 投稿日:2020-03-23T10:28:44+09:00
Devise <%= notice %> <%= alert %>による余白をなくす方法
はじめに
先日投稿したこちらで
Bootstrap + jQueryを使ってFlashなどの
<button>xボタン</button>
をクリックした時にFlashを閉じる機能を実装しました。
ただ、ブラウザ場で確認すると、
Flashが消える前、後、双方で
<header>
と<%= yield%>
の間に余白が発生していました。この余白を無くす方法を紹介します。
開発環境
- bootstrap-sass (3.3.7)
- devise (4.7.1)
- jquery-rails (4.3.1)
- rails (5.2.4.1)
前置き (layoutのデザイン)
私の場合
<header>
はheader.html<header class= "header-layout navbar navbar-fixed-top navbar-inverse">
- Bootstrapの
navbar-fixed-top
を使ってheaderを固定しております。headerの下の
<%= yield%>
には画面いっぱいの背景画像を設置した表紙のようになっております。(余白ができていた時のパターン↓)
application.html.erb<body> <%= render 'shared/header'%> <%= notice %> //ここ <%= alert %> //ここ <body>ユーザーがログイン後に、
flash[:notice]
が呼び出された場合、呼び出されなかったflash[:alert]は、余白としてflash[:norice]の上に表示されていました。if構文(余白消えない。)
まずこちらを参考にしてみた
https://whatsupguys.net/programming-school-dive-into-code-learning-47/]app/views/shared/_flash_messages.html.erb<% if notice %> //条件 <% elsif alert %> //条件 <% end %>上記のように条件を組んでブラウザ場の検証を確認しても
ブラウザ場の検証(要素) <header> </header> " " //謎の余白 <class= "alert"> "ログインしました" //flash[:notice] </>flashは表示できているけれど
<header>
とflashが呼ばれる<class= "alert">
の間に" "
と表示され、余白の原因になっております。
おそらく" "
の部分は表示されなかったflash[alert]
だと思います。
if構文を使って条件を書いても、、余分な余白は表示されたままでした。解決方法
解決方法を調べてみました。
まず、deviseとBootstrapを組み合わせる祭、Bootstrapには、
deviseのkey
である[:notice]
,[:alert]
に対応したクラスがないので、key
を置き換えることが最善です。こちらを参考に
(https://hachy.github.io/2019/10/15/flashes-with-devise-and-bootstrap.html)app/helpers/users_helper.rbmodule UsersHelper #Bootstrapに対応できるdevise flash[notice][alert]を変更する def bootstrap_alert(key) case key when "alert" "warning" when "notice" "success" when "error" "danger" end end end
:alert
→:warning
,:notice
→:success
に変更する為にヘルパーメソッドを作成しています。(devise userモデルを使っているのでそのヘルパーを利用しています。)Flashの変更
変更前↓ (deviseのデフォルト)
application.html.erb<body> <%= render 'shared/header'%> <%= notice %> // <%= alert %> // <body>変更後↓
app/views/shared/_flash_messages.html.erb<% flash.each do |key, value| %> <div class="alert alert-<%= bootstrap_alert(key)%> close-flash"> <%= value %> <button type="button" class="close close-button" data-dismiss="alert"> × </button> </div> <% end %>
- deviseのデフォルトflashを変更し、Bootstrapに組み合わせれるように作ったヘルパーメソッド
bootstrap_alert(key)
を使ってdeviseflash[:key]
を変更。これでFlash[:key]がしっかりBotstrapにも適用でき、余分な
" "
が呼び出されることなく、特定のflash[:key]だけが呼び出され余白も消えました。参考にしたサイト
- 投稿日:2020-03-23T10:28:44+09:00
Devise <%= notice %> <%= alert %>による謎の余白をなくす方法
はじめに
先日投稿したこちらで
Bootstrap + jQueryを使ってFlashなどの
<button>xボタン</button>
をクリックした時にFlashを閉じる機能を実装しました。
ただ、ブラウザ場で確認すると、
Flashが消える前、後、双方で
<header>
と<%= yield%>
の間に余白が発生していました。この余白を無くす方法を紹介します。
開発環境
- bootstrap-sass (3.3.7)
- devise (4.7.1)
- jquery-rails (4.3.1)
- rails (5.2.4.1)
前置き (layoutのデザイン)
私の場合
<header>
はheader.html<header class= "header-layout navbar navbar-fixed-top navbar-inverse">
- Bootstrapの
navbar-fixed-top
を使ってheaderを固定しております。headerの下の
<%= yield%>
には画面いっぱいの背景画像を設置した表紙のようになっております。(余白ができていた時のパターン↓)
application.html.erb<body> <%= render 'shared/header'%> <%= notice %> //ここ <%= alert %> //ここ <body>ユーザーがログイン後に、
flash[:notice]
が呼び出された場合、呼び出されなかったflash[:alert]は、余白としてflash[:norice]の上に表示されていました。if構文(余白消えない。)
まずこちらを参考にしてみた
https://whatsupguys.net/programming-school-dive-into-code-learning-47/]app/views/shared/_flash_messages.html.erb<% if notice %> //条件 <% elsif alert %> //条件 <% end %>上記のように条件を組んでブラウザ場の検証を確認しても
ブラウザ場の検証(要素) <header> </header> " " //謎の余白 <class= "alert"> "ログインしました" //flash[:notice] </>flashは表示できているけれど
<header>
とflashが呼ばれる<class= "alert">
の間に" "
と表示され、余白の原因になっております。
おそらく" "
の部分は表示されなかったflash[alert]
だと思います。
if構文を使って条件を書いても、、余分な余白は表示されたままでした。解決方法
解決方法を調べてみました。
まず、deviseとBootstrapを組み合わせる祭、Bootstrapには、
deviseのkey
である[:notice]
,[:alert]
に対応したクラスがないので、key
を置き換えることが最善です。こちらを参考に
(https://hachy.github.io/2019/10/15/flashes-with-devise-and-bootstrap.html)app/helpers/users_helper.rbmodule UsersHelper #Bootstrapに対応できるdevise flash[notice][alert]を変更する def bootstrap_alert(key) case key when "alert" "warning" when "notice" "success" when "error" "danger" end end end
:alert
→:warning
,:notice
→:success
に変更する為にヘルパーメソッドを作成しています。(devise userモデルを使っているのでそのヘルパーを利用しています。)Flashの変更
変更前↓ (deviseのデフォルト)
application.html.erb<body> <%= render 'shared/header'%> <%= notice %> // <%= alert %> // <body>変更後↓
app/views/shared/_flash_messages.html.erb<% flash.each do |key, value| %> <div class="alert alert-<%= bootstrap_alert(key)%> close-flash"> <%= value %> <button type="button" class="close close-button" data-dismiss="alert"> × </button> </div> <% end %>
- deviseのデフォルトflashを変更し、Bootstrapに組み合わせれるように作ったヘルパーメソッド
bootstrap_alert(key)
を使ってdeviseflash[:key]
を変更。これでFlash[:key]がしっかりBotstrapにも適用でき、余分な
" "
が呼び出されることなく、特定のflash[:key]だけが呼び出され余白も消えました。参考にしたサイト
- 投稿日:2020-03-23T06:19:56+09:00
ユーザーの状態によって表示を変更する方法 Rails
初めに
Rubyのライブラリの1つである、便利機能のdeviseを使いました
devise
ログイン機能の実装を簡単にしてくれるgemです
本題
今回は、ログインしているかしていないかで見せ方を変更したかったので、、、
- if user_signed_in? .purchase = link_to '購入ページ','#' - else %p.setumei 親譲りの無鉄砲で小供の時から損ばかりしている。小学校に居る時分学校の二階から飛び降りて一週間ほど腰を抜かした事がある。なぜそんな無闇をしたと聞く人があるかも知れぬ。別段深い理由でもない。新築の二階から首を出していたら、同級生の一人が冗談に、いくら威張っても、そこから飛び降りる事は出来まい。弱虫やーい。と囃したからである。小使に負ぶさって帰って来た時、おやじが大きな眼をして二階ぐらいから飛び降りて腰と記述しました。
ログインしている
ログインしていない
- 投稿日:2020-03-23T01:33:47+09:00
PHP サイト でも Capistrano (Ruby Gem)
環境
- Ruby 2.6.5
設定手順
Capistrano 設定ファイルの用意
bundle add capistrano bundle exec cap installこれで必要なファイルが作成されます。
あとは
deploy.rb
,config/deploy/*
を変更して設定完了です。ファイルを置けば使えるPHPサイトなら、認証設定程度を記述すればOKです。
設定ファイル例
staging.rb/production.rb# server configuration role :web, %w{ec2-user@123.123.123.123} # file location set :deploy_to, "/var/www/xxx"deploy.rb# application name set :application, "xxxxxx" # repository set :repo_url, "sample@sample.git.jp:/sample.git" # Default branch is :master ask :branch, `git rev-parse --abbrev-ref HEAD`.chomp # revision to be kept set :keep_releases, 2デプロイ手順
# config/deploy/staging.rb を使ったデプロイ bundle exec cap staging deploy # config/deploy/production.rb を使ったデプロイ bundle exec cap production deployロールバック手順
cap deploy:rollbackこのほかにも
cap deploy:rollback:code
などがある。