- 投稿日:2020-04-07T23:53:58+09:00
[あやふや解消シリーズ]ORMについて[vol.1]
TL;DR
- ORMとは、オブジェクト指向のプログラミング言語で関係データベースを扱えるようにうまく変換してくれるもの。
- ORMを利用する際には「N+1問題」に注意する必要がある。
- Railsでは基本的にはORマッパーにAciruveRecordを使用している
3行で分かるこの記事を書いた動機
筆者:「Rails独学で勉強してます!」 ????:「ORマッパーは何使ってる?」 筆者:「ミ°(学びの浅はかさを見破られ恥ずかしさのあまり舌をかみ切って死亡)」ORMってなんの略?
ORMとは、
Object-relational mapping
(日:オブジェクト関係マッピング)の略称です。ORMって何?
- ORMとは、アプリケーションが持つリッチなオブジェクトをリレーショナルデータベース(RDBMS)のテーブルに接続することです。
- オブジェクト関係マッピングは、オブジェクト指向言語からリレーショナルデータベースにアクセスする技術である。
- オブジェクト関係マッピングとは、データベースとオブジェクト指向プログラミング言語の間の非互換なデータを変換するプログラミング技法である。
引用:
Railsガイド/ 1.2 O/Rマッピング
ORMは不快なアンチパターン/ ORMの仕組み
Wikipedia/ オブジェクト関係マッピングつまり、ORMとは
オブジェクト指向のプログラミング言語で関係データベースを扱えるようにうまく変換してくれるもの。
ORMのメリット/デメリットは?
メリット
SQLを書かなくてもデータベースにアクセスできる
- 例1 以下のようなデータベースから特定の名前を持つユーザの情報を検索したい場合、
table name -> users
id name sex 1 Alice female 2 Bob man 3 Chris man //MySQL SELECT * FROM users WHERE name = 'Alice'; //ORM(例:ActiveRecord) User.find_by(name: "Alice")利用するSQLの種類によって生じる微妙な差異を吸収できる
- 例2 以下のようなデータベースからpointが60以下のユーザの特定のカラムの数値を更新したい場合、
table name -> users
id name point promotion flag 1 Alice 80 1 0 2 Bob 50 1 0 3 Chris 80 1 0 //MySQL UPDATE users SET promotion = 0, flag = 1 WHERE point <= 60; //PostgreSQL UPDATE users SET {promotion,flag} = {0,1} WHERE point <= 60; //ORM(例:ActiveRecord) User.where("point <= 60").update(promotion:0, flag:1)デメリット
N+1問題によるパフォーマンスの悪化
N+1問題とは、一覧を取得するSQLを発行してから、各要素ごとに個別のSQLを発行してしまうこと。
厳密には1+N問題
と呼んだほうが、実態をより正確に表現できるN+1問題の怖いところは、問題を放置していても
動きはする
ことである。
必要以上にSQLクエリを走らせることになるため、データ量が増えるにつれパフォーマンスを低下させてしまう。
- 具体例
前提条件UserとPostは1:Nの関係 # app/models/user.rb class User < ActiveRecord::Base has_many :posts end # app/models/post.rb class Post < ActiveRecord::Base belongs_to :user end コントローラではPostモデルのみ取得 # app/contollers/posts_controller.rb def index @posts = Post.all # SELECT "posts".* FROM "posts" が発行される。実際にはViewで発行される endビューでUserモデルの情報を表示<h1>Listing posts</h1> <table> <thead> <tr> <th>Title</th> <th>Content</th> <th>User</th> <th colspan="3"></th> </tr> </thead> <tbody> <!-- @posts.eachで SELECT "posts".* FROM "posts" クエリが発行される。 --> <% @posts.each do |post| %> <tr> <td><%= post.title %></td> <td><%= post.content %></td> <!-- post.user.name で SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", user_id]] クエリが発行される。 --> <td><%= post.user.name %></td> <td><%= link_to 'Show', post %></td> <td><%= link_to 'Edit', edit_post_path(post) %></td> <td><%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %></td> </tr> <% end %> </tbody> </table>この時のログProcessing by PostsController#index as HTML Post Load (0.2ms) SELECT "posts".* FROM "posts" User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 1]] User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 2]] User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 3]] User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 4]] User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 5]] User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 6]] User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 7]] User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 8]] User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 9]] User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 10]] Rendered posts/index.html.erb within layouts/application (32.9ms) Completed 200 OK in 147ms (Views: 132.6ms | ActiveRecord: 2.0ms)対策;N+1問題を検出するgemであるbulletを利用する
Gemfileに追加してbundle install、いくつかの設定を調整したのち、問題のUserモデルの情報を表示するとポップアップで
N+1問題を検出し、解決方法を提示してくれる。
下記のように変更し、再び表示させるとポップアップは表示されないようになる。変更点# app/contollers/posts_controller.rb def index @posts = Post.all.includs(:user) # 下記2つのSQLが発行される # SELECT "posts".* FROM "posts" # SELECT "users".* FROM "users" WHERE "users"."id" IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10) # この変更によってpost.user.nameの際にいちいち SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", user_id]] を発行しなくてすむ end引用
Rails Webook/ N+1問題/Eager Loadingとは
TECHSCORE BLOG/ Railsライブラリ紹介N+1問題を検出するgemであるbulletを利用する
Active Recordについて
Active Recordとは、
ORMシステムに記述されている「Active Recordパターン」を実装したもの
Active Recordパターンは、データアクセスのロジックを常にオブジェクトに含めておくことで、そのオブジェクトの利用者にデータベースへの読み書き方法を指示できるといったもの
CoC(設定より規約)
ActiveRecordはこれを採択しているため、Railsで採用されている慣習に従っている限り、設定用のコードを最小限で済ませることが可能になっている。
- 投稿日:2020-04-07T22:25:25+09:00
Rails コマンドが急に使えなくなった
久しぶりにrailsを起動しようとしたらrails sが通りませでした。
同じくrails -vやrails newをしても同じ様な状態になります。色々調べたのですが、どうしても解決できずにこちらで質問させていただきます、、、
(初心者です、質問の仕方が間違っていたらすみません)
dlopen(/Users/ユーザー名/.rbenv/versions/2.5.1/lib/ruby/2.5.0/x86_64-darwin18/digest/md5.bundle, 9): Library not loaded: /usr/local/opt/openssl/lib/libssl.1.0.0.dylib (LoadError)
Referenced from: /Users/ユーザー名/.rbenv/versions/2.5.1/lib/ruby/2.5.0/x86_64-darwin18/digest/md5.bundle
Reason: image not found - /Users/ユーザー名/.rbenv/versions/2.5.1/lib/ruby/2.5.0/x86_64-darwin18/digest/md5.bundlerails sを打つと色々出てきますが、最後にこの様な文があります、、
rails newやrails -vを打っても同じ様な症状です。
自分ではどうすることもできず
初歩的な質問かもしれませんが、どなたかお助けいただけませんでしょうか??、
よろしくお願いします。気になる事といえば
最近仮想開発環境を構築するのにVirtualBoxやvagrantを設定しました。
- 投稿日:2020-04-07T22:23:24+09:00
開発環境、テスト環境、本番環境って何?雑にメモ
3つの環境が用意されているよ
例えば、herokuにpushする場合は、本番環境を使用しているみたい
こいつらは、RAILS_ENVという環境変数を用いて動作モードを切り替えられる
例えば、コマンドプロンプトで普通に指令を送る場合は、開発環境に送っている。
じゃあ「本番環境」を指定して実行したい時は、、、
このコードを記述すればおk
RAILS_ENV=production
- 投稿日:2020-04-07T21:13:52+09:00
クロスサイトリクエストフォージェリ(CSRF)の対策
クロスサイトリクエストフォージェリ(CSRF)
Webサイトにスクリプトや自動転送(HTTPリダイレクト)を仕込むことによって、利用者に意図せず別のWebサイト上で何らかの操作(掲示板への書き込みや銀行口座への送金など)を行わせる攻撃手法のことをいいます。
CSRFの脆弱性が存在すると以下のような被害を被る可能性があります。
①利用者のアカウントによる物品の購入
②利用者の退会処理
③利用者のアカウントによる掲示板への書き込み
④利用者のパスワードやメールアドレスが変更
CSRF脆弱性の影響は「重要な処理」の悪用に限られるため、CSRFの脆弱性を個人情報の取得等に用いることはできません。CSRFの攻撃例
例えば、利用者が罠サイトを閲覧することによってパスワードが変更されてしまう場合
①利用者がexample.jpにログインしている
②攻撃者は罠を作成
③利用者が罠を閲覧する
④罠のJavaScriptによる、被害者のブラウザ上で攻撃対象サイトに対し、新しいパスワードabcdefがPOSTメソッドにより送信される
⑤パスワードが変更されるCSRFの対策
CSRF攻撃を防ぐには、「重要な処理」に対するリクエストが利用者の意図によるものかどうかを確認することが必要になります。
このためCSRF対策が以下の2点です。
①CSRF対策の必要なページを区別する
②正規利用者の意図したリクエストを区別できるように実装するCSRF対策の必要なページを区別する
CSRF対策はすべてのページに行う必要はありません。対策に必要なページは、他のサイトから勝手に実行されては困るようなページです。例えば、ECサイトの物品購入ページや、パスワード変更など個人情報の編集確定画面などです。
CSRFの対策としては、まず実装するWebアプリケーションのどのページに脆弱性対策が必要なのか設計段階で明らかにすることです。
例えば、機能一覧を紙に記入し、CSRFの対策が必要なページを色分けすると良いと思います。
商品ページ=>認証=>カートに追加=>購入確認=>購入確定
この中だと最後の購入確定がCSRFの対策が必要です。正規利用者の意図したリクエストを区別できるように実装
CSRF対策で必要なことは、正規利用者の意図したリクエストなのかどうかということです。
意図したリクエストとは、利用者が対象のアプリケーション上で「実行」ボタンなどを押して、「重要な処理」のリクエストを発行することです。
正規のリクエストかどうかを判断する方法は3種類あります。
①秘密情報の埋め込み
②パスワードの再入力
③Refererのチェック秘密情報の埋め込み
登録画面や注文確定画面などのCSRF攻撃への対策が必要なページに対して、第三者の不正利用者が知り得ない秘密情報を要求するようにすれば、不正リクエストによる重要な処理が実行されることはありません。このような目的で使用される秘密情報のことをトークンといいます。
パスワードの再入力
こちらは文字通り重要な処理が確定する前に、再度パスワードを入力してもらいます。これはCSRF対策の他にも物品の購入などに先立って、利用者の意思の念押しをしたり、共用のPCにおいて正規の利用者以外の利用者が、重要な処理を実行するのを防いだりする効果があります。
CSRFの攻撃例として、とりあげたパスワード変更ページにも現在のパスワードを再入力させることによりCSRF攻撃を防ぐことが可能です。RailsでのCSRF対策方法
Rails側できちんと対策を行ってくれています。基本的には開発者はなにもしなくても大丈夫です。
例として、RailsでのCSRF対策app/controllers/application_controller.rbclass ApplicationController < ActionController::Base # Prevent CSRF attacks by raising an exception. # For APIs, you may want to use :null_session instead. protect_from_forgery with: :exception #追加部分 before_action :configure_permitted_parameters, if: :devise_controller? def configure_permitted_parameters devise_parameter_sanitizer.for(:sign_up) << :nickname end endprotect_from_forgery with: :exception
これがRailsアプリケーション内でCSRF対策を行うというという命令になります。こちらをすべてのコントローラの親であるapplication_controller.rbに記述することによって、その子コントローラすべてに先ほど説明したようなCSRF対策をRails側で行ってくれます。
具体的には、まずサイトのHTMLに一意のトークンを埋め込みます。これと同じトークンを、セッションcookie(クッキー)にも保存しています。ユーザーがPOSTリクエストを送信すると、HTMLに埋められているCSRFトークンも一緒に送信されます。あとは、サーバ側でページのトークンとセッション内のトークンを比較し、両者が一致することを確認したらリクエストを受け付けます。
- 投稿日:2020-04-07T20:05:38+09:00
React + Rails API + axios + react-router-domで複数のフォームを作成する
こんばんは!スージーです!
最近、学習し始めたReactにてフォームの扱い方で苦労したので備忘録。やりたい事
railsのcreateアクションにてフォームに2つのデータを入力してDBへ保存しビューで一覧表示する。
汚いレイアウトは勘弁して下さい。今回はCRUDの新規作成(Create)と一覧表示(Read)の実装をする。開発環境
Ruby 2.5.1
Rails 5.2.4
React 16.13.1参考
Ruby on Rails+ReactでCRUDを実装してみた
https://qiita.com/yoshimo123/items/9aa8dae1d40d523d7e5d[Rails API][React]axiosを用いて"POST"したい!
https://teratail.com/questions/166335Ruby on Rails+ReactでCRUDを実装してみた
https://qiita.com/yoshimo123/items/9aa8dae1d40d523d7e5dMacにNode.jsをインストール
https://qiita.com/kyosuke5_20/items/c5f68fc9d89b84c0df09まずはRails側から実装開始
apiモードでrails newする
$ rails new neko -d mysql --api // 「--api」→オプションをつける事でapiモードで作成 // 「-d mysql」→DBにはMySQLを利用gem 'rack cors'をインストールとモデル・コントローラを作成
Gemfile# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible gem 'rack-cors'# 注目! 29行目あたりにコメントアウトされているのでコメントアウトを外す$ cd neko // アプリ階層へ移動 neko $ bundle install // rack-corsをbundle installする neko $ rails g model post name:string neko_type:string // モデルを作成する neko $ rails db:create neko $ rails db:migrate neko $ rails g controller posts // コントローラー作成。apiモードなのでviewは作成されないposts_controller.rbclass PostsController < ApplicationController def index @post = Post.all render json: @post end def create @post = Post.create(name: params[:name], neko_type: params[:neko_type]) render json: @post end end通常railsではprivate以下にpost_paramsなどメソッド作ってPost.create(post_params)としますが、Reactを使って実装すると以下のエラーが出てしまいました。
Started POST "/posts" for ::1 at 2020-04-06 20:37:54 +0900 Processing by ProductsController#create as HTML Parameters: {"name"=>"たま", "neko_type"=>"雑種"} Completed 500 Internal Server Error in 5ms (ActiveRecord: 0.0ms) NoMethodError (undefined method `permit' for "たま":String):なので、今回は一旦
@post = Post.create(name: params[:name], neko_type: params[:neko_type])
で実装を進めていきます。ルーティングを設定
routes.rbRails.application.routes.draw do resources :posts endRailsを起動
Reactが
3000
番ポート、Railsは3001
番ポートを使います。$ rails s -p 3001 => Booting Puma => Rails 5.2.4.2 application starting in development => Run `rails server -h` for more startup options Puma starting in single mode... * Version 3.12.4 (ruby 2.5.1-p57), codename: Llamas in Pajamas * Min threads: 5, max threads: 5 * Environment: development * Listening on tcp://localhost:3001 * Use Ctrl-C to stop // Getのエンドポイントにアクセス。 // ターミナルで新しいタブを開いて以下コマンドを叩いてみてjsonでレスポンスあるか確認 neko $ curl -G http://localhost:3001/posts/ =>[ ] // 空の[]が返ってくるが、DBにデータが入っていないのでここはこのままでOKRails側で3000ポートのアクセスを許可する
config/initializers/cors.rb# Be sure to restart your server when you modify this file. # Avoid CORS issues when API is called from the frontend app. # Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests. # Read more: https://github.com/cyu/rack-cors 8行目~16行目あたりにある以下のソースのコメントアウトを外す Rails.application.config.middleware.insert_before 0, Rack::Cors do allow do origins 'http://localhost:3000'# 注目! 3000番ポートを許可する為に'http://localhost:3000'に変更する resource '*', headers: :any, methods: [:get, :post, :put, :patch, :delete, :options, :head] end endここまででRails側の実装は終了です。次はReact側を実装していきます。
次にReact側の実装開始
Node.jsをまだインストールしていなければインストールして下さい。
neko $ npm install -g create-react-app // create-react-appをインストール neko $ create-react-app react_front // プロジェクトを作成 neko $ cd react_front react_front $ npm start // reactのデフォルトTOP画面がブラウザに表示されるディレクトリ構造
ディレクトリ構造は以下のようになります。
. |_app |_bin |_config |_db |_lib |_log |_public |_react_front # 注目 ここがこれから実装するディレクトリ | |_node_modules | |_public | |_src | |_.gitignore | |_package-lock.json | |_package.json | |_yarn.lock |_storage |_test |_tmp |_vender |_.gitignore |_.ruby-version |_config.ru |_Gemfile |_Gemfile.lock |_package-lock.json |_README.mdちなみにgitでバージョン管理する場合にリモートリポジトリにpushする時にエラーが起きるかもしれません(エラー内容忘れた)。macのFinderでディレクトリに潜っていくと分かるのですが、Railsの
.gitディレクトリ
とReactの.gitディレクトリ
が存在します。それぞれプロジェクト作成した時に作成されてしまうからです。キャプチャは既に片方削除していますが、React(react_front内)の
.gitディレクトリ
は削除して下さい。それでエラーは解決できます。App.js
コードはこちらを参考にカラム名やパスを自分の環境に合わせて変更して利用させて頂きました。
App.jsimport React from 'react'; import { BrowserRouter as Router, Link, Switch, Route } from 'react-router-dom'; import List from './Components/List'; import New from './Components/New'; import './App.css'; const App = () => { return ( <Router> <div id="App"> <Header /> <Switch> <Route exact path='/' component={ List }/> <Route exact path='/new' component={ New } /> </Switch> </div> </Router> ); } const Header = () => ( <nav> <div> <Link to="/">SampleTodo</Link> <Link to="/new">新規投稿</Link> </div> </nav> ) export default App;Components/List.jsx
画面遷移を簡単に実装する為にaxiosをインストールしておきます。
neko $ npm install axios --save // 「--save」オプションでpackage.jsonにも反映List.jsximport React, { Component } from 'react'; import axios from 'axios' class List extends Component { constructor(props) { super(props) this.state = { posts: [] }; } componentDidMount() { axios.get('http://localhost:3001/posts') .then((results) => { this.setState({products: results.data}) }) .catch((data) =>{ console.log(data) }) } render() { const {posts} = this.state return ( <div> {posts.map((list) => { return <li key={list.id}> { list.name }{ list.neko_type }</li> // postsに格納されているdataをmapメソッドを使い1つ1つ取り出し表示させる })} </div> ); } } export default List;Components/New.jsx
Components/New.jsximport React, { Component } from 'react'; import axios from 'axios' class New extends Component { constructor(props){ super(props); this.state = { name: '', neko_style: '' }; } handleInputValue = (event) => { this.setState({ // setStateメソッドで更新するstateと新しいstateの値を指定する [event.target.name]: event.target.value // フォームのname="neko_type"のnameを参照 // this.setState({title: event.target.value})と同じ書き方となる }); } handleSubmit = (e) => { e.preventDefault(); axios({ method : "POST", url : "http://localhost:3001/posts", data : { name: this.state.name, neko_type: this.state.neko_type } }) .then((response)=> { console.log(this.props) this.props.history.push('/'); }) .catch((error)=> { console.error(error); }); } render() { const { name, neko_type } = this.state; return ( <div> <p>新規投稿</p> <div> <label>名前 : </label> <input type="text" name="name" value={ name } onChange={ this.handleInputValue } /> // 2つ以上のフォームを扱う場合はname=""を書く // これで1つのhandleInputValueイベントで複数のstateの値を更新できる <label>猫種 : </label> <input type='text' name="neko_type" value={ neko_type } onChange={ this.handleInputValue } /> // 2つ以上のフォームを扱う場合はname=""を書く // これで1つのhandleInputValueイベントで複数のstateの値を更新できる <input type="button" onClick={this.handleSubmit} value="Submit" /> </div> </div> ); } } export default New;まとめ
最初は1つのデータだけ送るフォームでCRUDを実装してキャッキャウフフしていましたが、フォームって複数のデータを扱えないと使えない事に気づき、色々な参考記事をかいつまみながら実装しました。
まだまだReactとRails APIの連携に対して理解力が足ないので、もっともっと勉強が必要と感じました。
次は残りの更新(update)
と削除(delete)
を実装して開発中のアプリに載せ替えしようと思います。終わり
- 投稿日:2020-04-07T19:02:49+09:00
rspecで例外が起きたことを確認する
この記事で書くこと
以下のような処理でのensureの処理が走ったかどうかを確認する方法
class Cat < ApplicationRecord validates :todays_foods, presence: true def sing! sing_a_song! ensure say_hello end def say_hello 'にゃーん' end def sing_a_song! if todays_foods.empty? raise お腹が空いてたらエラー return end 'にゃにゃにゃーん!' end endテストの中身
describe '#sing!' do context 'with exception error,' do let!(:cat) do cat = build(:cat, name: 'お腹すいた猫', todays_foods: 0) cat.save(validate: false) # validatesを無効にして作成。 cat end it 'say にゃーん.' do expect do cat.sing! end.to raise_error(ActiveRecord::RecordInvalid).and(eq('にゃーん')) end end end
- 投稿日:2020-04-07T16:20:25+09:00
クロスサイトスクリプティング(XXS)対策
クロスサイトスクリプティング(XXS)
Webアプリケーションでは、外部からの入力などに応じて表示が変化するページを実装したいことがしばしばあります。
しかし、この部分のHTML生成の実装に問題があると、外部よりスクリプトを埋め込まれクッキーを盗まれたり、JavaScriptによる攻撃を受けてしまうおそれがあります。
こういった攻撃手法をクロスサイトスクリプティングと言います。XSSの攻撃例
では、実際にXSSの脆弱性を用いた攻撃例です。
ここでは登場人物を用いて説明します。
ユウスケ・・・ 一般ユーザ
タカシ ・・・攻撃を仕掛ける悪意のある者
1. タカシはXSSの脆弱性があるサイトに悪意のあるスクリプトを埋め込む
2. タカシは1.でスクリプトを埋め込んだサイトをリンクに指定する罠サイトを用意する
3. タカシはユウスケに罠サイトへ誘導するようなメールを送信する
4. ユウスケは罠サイトにアクセスし、タカシがスクリプトを埋め込んだサイトにアクセス
5. ユウスケのブラウザ上でタカシが埋め込んだスクリプトが実行される
※ここで出てくる罠サイトとは、スクリプトを埋め込んだXSSの脆弱性のあるサイトへのリンクを含んでいるページのことをいいます。
ここで実行されるスクリプトの例としては、cookieを攻撃者のサーバに送信されてしまったり、マルウェア(悪意のあるソフトウェア)を仕込んであるサイトにリダイレクトされウイルスに感染させられたりとJavascriptで記述することが可能なすべての攻撃を受けてしまう可能性があります。XSSの対策
XSSが発生する主要因として、フォームから入力されたHTMLタグがそのままページに反映されてしまっていることがあげられます。
したがって、XSSを防ぐためにはHTMLを生成する際に意味を持つ「"」や「<」を文字参照によってエスケープすることが基本となります。文字参照
HTML上で直接記述できない特殊文字を表記する際に用いられる記法です。例えば、HTML中に「<」もしくは「>」と記述するとこの二つはタグの初め、終わりと認識されてしまいます。これでは文字列として上記の記号を用いることができません。そこで、文字参照を利用します。
変換前 変換後 < & lt; > & gt; & & amp; " & quot; ' & #39; 上の表は、XSSを対策する際にエスケープすべき特殊文字の一覧です。これらの特殊文字を文字参照に変換して保存すれば、外部から埋め込まれたスクリプトが実行されることはありません。
rawメソッド
文字列を文字参照にエスケープしないためのヘルパーメソッドです。
【例】raw(文字列)<%= raw(tweet.text) %>
- 投稿日:2020-04-07T16:08:34+09:00
sidekiqのキューが処理されずCPU負荷が上がって困っている人へ
- 投稿日:2020-04-07T15:48:55+09:00
情報セキュリティとは
情報セキュリティ
Webサービスにおいてのセキュリティ(安全保障)です。情報セキュリティにおける理想は、「不正なアクセスや情報の漏洩を防ぎつつ、権限がある人は便利に利用できる」状態を維持することです。「機密性」「完全性」「可用性」の3つの要素を維持することを目標にします。
①機密性 権限を持たない人が情報資産を見たり使用できないようにすること
②完全性 権限を持たない人が情報を書き換えたり消したりできないようにすること
③可用性 権限を持つ人がいつでも利用したいときに利用できるようにすること脆弱性(ぜいじゃくせい)について
コンピュータやネットワーク、アプリケーション全体のセキュリティに弱点を作り出すコンピュータソフトウェアの欠陥や仕様上の問題点のことを脆弱性と言います。Webアプリケーションに脆弱性があると、開発者側だけでなく、利用者側も被害を被る可能性があり、様々な被害が生じる可能性があります。脆弱性は、バグや、開発者のセキュリティチェック不足により生まれます。
脆弱性によってもたらされる被害
脆弱性がアプリケーション内に存在することによって、以下のような被害が想定されます。
①個人情報を勝手に閲覧される(機密性侵害)
②Webページの内容が改ざんされる(完全性侵害)
③Webページ自体が利用不能になる(可用性侵害)
このような問題が起きてしまうと、利用者への金銭的損失の補填や補償、開発者の社会的信頼の失墜による売上の減少、Webサイト停止による機会損失など多くの被害をもたらすので脆弱性対策は必須です。脆弱性が生まれる理由
一つは、バグによるもの
二つ目は、開発者側のセキュリティチェック不足によるもの
ここでいうバグとは、クロスサイトスクリプティングのように投稿フォーム等にscriptタグでJavascriptのコードを記述・送信することで、それがページに埋め込まれ、実行されてしまうといったようなプログラミング言語の仕様に起因するものようなことを言います。クロスサイトスクリプティング(XSS)
Webサイトに利用されるアプリケーションの脆弱性もしくはその脆弱性を悪用した攻撃のことです。特にWeb閲覧者側が制作することのできる動的サイト(例:TwitterなどのSNS、掲示板等)に対して、その脆弱性を利用して悪意のある不正なスクリプトを挿入することによりその発生するサイバー攻撃です。
HTTP
HTTPとはWebブラウザとWebサーバの間でHTMLや画像ファイルなどのコンテンツの送受信に用いられる通信プロトコルです。Webページを閲覧・利用することができるのもHTTPという仕組みがあるからです。
プロトコル
複数のユーザが滞りなく信号やデータ、情報を相互に伝送できるよう、あらかじめ決められた約束事や手順の集合のことです。
例えば人と人との会話を例とすると、片方が日本語で話し、もう片方が中国語で話していては会話にならない状態です。
そこで、会話を成立させるためには使用言語をきちんと決める必要があります。この使用言語を何にするか決める役割が通信におけるプロトコルにあたります。
HTTPはクライアント(自分のパソコンなど)、ブラウザからのリクエストに対して、サーバからのレスポンスが返ってくることによって実現します。
例えば飲食店で考えたときに、料理を注文します。すると、お店側は注文された料理を提供します。
HTTPもこれと同じでクライアントが要求したページをサーバ側がクライアントに合わせて提供するという仕組みになっています。
例として、クライアントとサーバー間でHTTP通信を行う際はメッセージのやり取りを行います。
まず、クライアントからサーバーに対して以下のようなリクエストメッセージを送信します。
HTTPリクエスト GET / HTTP/1.1 リクエストライン Accept: image/gif, image/jpeg, / ヘッダ Accept-Language: ja Accept-Encoding: gzip, deflate User-Agent: Mozilla/4.0 (Compatible; MSIE 6.0; Windows NT 5.1;) Host: www.xxx.zzz Connection: Keep-Alive (空行) メッセージボディ(POSTメソッドなどで使用) www.xxx.zzz というアドレス
に対してHTTP/1.1というバージョンを使い、GETメソッドで"/"パスにアクセスしたい、というメッセージをクライアントからサーバに送っているいう意味になります。
それに対してサーバは以下のようなレスポンスメッセージを返します。
HTTPレスポンス HTTP/1.1 200 OK ステータスライン Date: Sun, 11 Jan 2004 16:06:23 GMT ヘッダ Server: Apache/1.3.22 (Unix) (Red-Hat/Linux) Last-Modified: Sun, 07 Dec 2003 12:34:18 GMT ETag: "1dba6-131b-3fd31e4a" Accept-Ranges: bytes Content-Length: 4891 Keep-Alive: timeout=15, max=100 Connection: Keep-Alive Content-Type: text/html (空行) htmlやcssの情報(メッセージボディ) ステータスラインを見てみると先ほどのリクエストメッセージに対して、HTTP1.1/(バージョン)200(ステータス番号)OK(補足メッセージ)と返しています。
これは「HTTP/1.1というバージョン下でリクエストを受け付けました」という意味を表しています。ステータスコード
HTTP通信のレスポンスメッセージのステータスライン中にある3桁の数字で、クライアントからのリクエストに対して、サーバからの返
100の位に意味があり、そちらで分類されます。
ステータスコード 意味 100番台 処理が継続中 200番台 正常終了 300番台 リダイレクト 400番台 クライアント側でのエラー 500番台 サーバー側でのエラー よく使われるステータスコードとしては、200(正常終了)、301及び302(リダイレクト)、404(ファイルが存在しない)、500(内部サーバーエラー)などがあります。
セッション/クッキー(cookie)
HTTPは、ステートレスな通信となっています。
ステートレスとはサーバが現在の状態を保持せず、ユーザの入力の内容のみによって出力が決定される状態のことです。
例えば、ユーザのログイン状態やECサイトのカート機能などがあります。これらは、ページが遷移してもユーザの情報や購入しようとしている商品の情報を保持している必要があります。
こういった状態を保持するために用いられるのがセッションであり、HTTP通信のセッションを管理するために作られた仕組みをクッキー(cookie)といいます。このような通信をステートフル通信といいます。セッション
複数回に渡るリクエストにおいて、クライアントを特定するための仕組みです。具体的には、クライアントは初回のリクエストで自身を識別させるIDをサーバーに渡します。以降、サーバーはそのIDを持ってクライアントを認識します。
クッキー(cookie)
クライアント側のブラウザに保持することができる情報のことです。通常、初回の通信でサーバーがクライアントにクッキーとしてセッションIDを保持させ、以降クライアントはそれを用いてサーバーに対して自身を特定させます。
実際にクッキー(cookie)がどのように使われているか
まず、サーバー側で明示的にクッキー(cookie)を設定しますよ、という宣言をします。
Railsでは、session_store.rbというファイル内でセッションの管理方法を指定します。デフォルトでクッキー(cookie)を利用する設定になっているため、Railsでは意識せずクッキー(cookie)でのセッション管理を行うことができます。config/initializer/session_store.rbTechReviewSite::Application.config.session_store :cookie_store, key: '_tech_review_site_session'ログイン処理を例にとると、以下のような流れでクッキー(cookie)が利用されます。
①クライアントはログイン画面でIDとパスワードを入力する
②すると、サーバでクッキー(cookie)が生成され、クライアントが保持する
③次回以降アクセスする際に、クライアントが保持しているクッキー(cookie)がサーバに送信される
④サーバはこのクッキー(cookie)値を元にクライアントを識別し、ログイン作業を省く
まとめると
HTTPとはWebアプリケーションを利用する際にクライアントとサーバ間の情報をやり取りするための通信プロトコル
セッションとは、Webアプリケーション上で前のページの状態を保持するために利用される機能
- 投稿日:2020-04-07T15:46:46+09:00
PostgreSQLのデータベースの存在を確認する
はじめに
Railsのコマンドから、PostgreSQLにデータベースを作成/削除をして、本当に作成されているか、削除されているか、確認してみる。
環境
Vagrant + Ubuntu 16.04.5 LTS
Rails 5.2.4.2手順
Railsのプロジェクトファイルを作成する。
$rails new test01 -d postgresql -B $cd test01データベースを作成する。
$rails db:create Created database 'test01_development' Created database 'test01_test'データベースの一覧を表示する。
psql -l確かに、作成されている事が確認できます。一覧は、qキーで閉じる事ができます。
次は、一旦作成したデータベースを削除します。
$ rails db:drop Dropped database 'test01_development' Dropped database 'test01_test'psql -lで確かに、削除された事が確認できます。
再度、データベースを作成して、マイグレーションのバージョンを確認すると、まだ、マイグレーションは1度も実行していないため、バージョンは0で表示される筈です。
$rails db:create $rails db:version Current version: 0適当に、Usersテーブルを作成する。
$rails g model Users name:string $rails db:migrateUsersテーブルが確かに作成されている事を確認する。
$psql -q -c 'select * from Users' test01_development id | name | created_at | updated_at ----+------+------------+------------ (0 rows)マイグレーションをロールバックすると、先ほど作成したUserテーブルは削除されるため、テーブルを参照するとテーブルは存在しませんと言われ、エラーになります。
<補足>
psqlのcオプションは、SQLコマンドを投げるためのコマンドです。詳しくは、psql --helpでコマンドの詳細が確認できます。$rails db:rollback $psql -q -c 'select * from Users' test01_development ERROR: relation "users" does not exist LINE 1: select * from Users
- 投稿日:2020-04-07T12:07:57+09:00
【Rails】Ajaxを用いた非同期投稿の実装
目標
開発環境
・Ruby: 2.5.7
・Rails: 5.2.4
・Vagrant: 2.2.7
・VirtualBox: 6.1
・OS: macOS Catalina前提
ログイン機能を実装済み。
ログイン機能 ➡︎ https://qiita.com/matsubishi5/items/5bd8fdd45af955cf137d
投稿機能の実装
テーブル
schema.rbActiveRecord::Schema.define(version: 2020_04_05_115005) do create_table "books", force: :cascade do |t| t.string "title" t.text "body" t.integer "user_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false end create_table "users", force: :cascade do |t| t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" t.integer "sign_in_count", default: 0, null: false t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" t.string "current_sign_in_ip" t.string "last_sign_in_ip" t.string "name" t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["email"], name: "index_users_on_email", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true end endモデル
user.rbclass User < ApplicationRecord has_many :books, dependent: :destroy endbook.rbclass Book < ApplicationRecord belongs_to :user endルーティング
routes.rbRails.application.routes.draw do devise_for :users resources :users resources :books endコントローラー
books.controll.rbclass BooksController < ApplicationController def create book = Book.new(book_params) book.user_id = current_user.id if book.save redirect_to book, notice: "successfully created book!" else @books = Book.all.order(created_at: :desc) #降順 render 'index' end end def index @book = Book.new @books = Book.all.order(created_at: :desc) #降順 end private def book_params params.require(:book).permit(:title, :body) end endビュー
books/index.html.erb<% if @book.errors.any? %> <h2><%= @book.errors.count %>error</h2> <div> <ul> <% @book.errors.full_messages.each do |msg| %> <li><%= msg %></li> <% end %> </ul> </div> <% end %> <%= form_with model: @book do |f| %> <div class="field row"> <%= f.label :title %> <%= f.text_field :title, class: "col-xs-12 book_title" %> </div> <div class="field row"> <%= f.label :body %> <%= f.text_area :body, class: "col-xs-12 book_body" %> </div> <div class="actions row"> <%= f.submit class: "btn btn-primary col-xs-12" %> </div> <% end %> <h2>Books index</h2> <table class="table table-hover table-inverse"> <thead> <tr> <th></th> <th>Title</th> <th>Opinion</th> </tr> </thead> <tbody> <% @books.each do |book| %> <tr> <td> <%= link_to book.user do %> <%= attachment_image_tag book.user, :profile_image, :fill, 50, 50, fallback: "no-image-mini.jpg" %> <% end %> </td> <td><%= link_to book.title, book %></td> <td><%= book.body %></td> </tr> <% end %> </tbody> </table>非同期機能の実装
1. jQueryの導入
Gemfilegem 'jquery-rails'ターミナル$ bundleapplication.js//= require rails-ujs //= require activestorage //= require turbolinks //= require jquery //= require_tree .2. booksコントローラーのcreateアクションを編集
books.controller.rbclass BooksController < ApplicationController #ローカル変数をインスタンス変数に変更 def create @book = Book.new(book_params) @book.user_id = current_user.id unless @book.save render 'index' end end end3. フォームを編集する
非同期投稿を行うときは必ず「form_with」を使用する。
※「form_for」「form_tag」だとレイアウトが崩れた。
books/index.html.erb<!-- form_withの引数に「remote: true」を追記 --> <%= form_with model: @book, remote: true do |f| %> <div class="field row"> <%= f.label :title %> <%= f.text_field :title, class: "col-xs-12 book_title" %> </div> <div class="field row"> <%= f.label :body %> <%= f.text_area :body, class: "col-xs-12 book_body" %> </div> <div class="actions row"> <%= f.submit class: "btn btn-primary col-xs-12" %> </div> <% end %>4. 投稿一覧を編集する
books/index.html.erb<tbody class="new_book"> <!-- 投稿一覧の親要素にクラスをつける --> <% @books.each do |book| %> <%= render 'books', book: book %> <!-- 投稿一覧をパーシャル化 --> <% end %> </tbody>books/_books.html.erb<tr> <td> <%= link_to book.user do %> <%= attachment_image_tag book.user, :profile_image, :fill, 50, 50, fallback: "no-image-mini.jpg" %> <% end %> </td> <td><%= link_to book.title, book %></td> <td><%= book.body %></td> </tr>5. JavaScriptファイルの作成
books/create.js.erb$(".book_title").val(''); $(".book_body").val(''); $(".new_book").prepend("<%= j(render 'books', book: @book) %>");
$(".new_book")
➡︎ 「4」で付けたクラスを指定
.prepend("<%= j(render 'books', book: @book) %>");
➡︎ 既存投稿の先頭に新規投稿を表示
$(".book_title").val('');
$(".book_body").val('');
➡︎ 入力フォームを空にする参考サイト
- 投稿日:2020-04-07T11:57:20+09:00
RailsでERBを直接使い、文字列として結果を取得する
はじめに
Railsのviewではなく、erbの結果を文字列としてほしいケースが有ったので、その方法です。
パスにRails固有のコードを使っていますが、そこ以外はRailsじゃなくても使えます。
ERBのインスタンス生成
file = File.read(Rails.root.join('app', 'views', 'hoge', 'fuga.html.erb').to_s) erb = ERB.new(file)といった形でERBのインスタンスを生成できます。
Railsじゃない場合は、
file = File.read('/path/to')みたいな感じですね。
結果の取得
result_text = erb.result_with_hash(hoge:1, fuga:2)で結果の取得ができます。
引数に渡したハッシュが、そのままerb内でkeyを変数名、valueを値とするローカル変数として読み込まれます。
あとがき
erbを直接使いたい状況、そこまで多くはないとは思いますが、もし必要な方の役に立てば幸いです。
- 投稿日:2020-04-07T11:52:04+09:00
【Rails5】rails_adminで多対多の関連付けを上手く編集する方法
はじめに
一瞬でめちゃイケてる管理画面を作ってくれる
rails_admin
今までCRUDそれぞれ画面編集してたのはなんだったんだ……と思う完成度です。しかし、多対多の関連付けの時、毎回のようにエラーが出ました。
出たエラー
ActiveRecord::HasManyThroughOrderError in RailsAdmin::Main#edit
原因
色々試しましたが、Modelでの関連付け設定の順番が原因でした。
構成
タイトルごとに複数のタグ。
タグごとに複数のタイトル。title has_many tags
tag has_many titlesこれを実現させるために、tag_mapsというテーブルを間に挟んでいます。
[tag_maps]
id
title_id
tag_id解決したコード
model/title.rbclass Title < ApplicationRecord has_many :tag_maps has_many :tags, through: :tag_maps endmodel/tag_map.rbclass TagMap < ApplicationRecord belongs_to :title belongs_to :tag endmodel/tag.rbclass Tag < ApplicationRecord has_many :tag_maps has_many :titles, through: :tag_maps endただ普通に記述してるだけなのですが、なぜか順番が逆だとエラーが出ました。
オマケの注意点
今回のこれは、Rails5での解決策でした。
(Rails 5.2.4.2)他のバージョンでは、ここ見たら参考になるかも……?
https://github.com/sferik/rails_admin/wiki/Associations-basicsあとrails_adminの編集画面で若干表示項目が変に見えるのは……まぁええでしょ。エラー出てないし
終わり
- 投稿日:2020-04-07T11:45:02+09:00
[Rails]モデル、テーブル、カラムの作成と削除コマンド
本記事について
開発して行く中で
「あれ?マイグレーションファイルの作成コマンドってこんなんだっけ。」
のように、ド忘れしてしまうことがあります。
そのため、メモ代わりに書きたいと思います。DB関係
モデル & テーブル作成
モデル & マイグレージョンファイル作成
rails g model モデル名(単数) 例 : userモデル rails g model user補足ですが上のコマンドで以下が作成されます。
1. モデルのクラスファイル
2. マイグレーションファイル
3. モデルの自動テスト
4. モデルの自動テストで使うfictureファイル実行
bundle installモデル削除
rails d model モデル名(単数)テーブルの削除
主な流れ
- マイグレーションファイル作成
- マイグレーションを編集
- 実行例: users テーブルの削除
マイグレージョンファイルの作成rails g migration DropTableUsersマイグレーションファイルの編集
2020xxxxx.rbclass DropTableUsers < ActiveRecord::Migration def change drop_table :users end end実行
bundle install既存のテーブルにカラムを追加
rails g Addカラム名Toテーブル名 カラム名:カラム型
の形でターミナルに入力しマイグレーションファイルを作成します。例: usersテーブルにstring型nameカラムを追加
rails g migration AddNameToUsers name:string実行
bundle install既存のテーブルのカラムを削除
rails g migration Removeカラム名Fromテーブル名 カラム名:テーブル名
の形でターミナルに入力しマイグレーションファイルを作成します。例: usersテーブルのstring型nameカラムを削除
rails g migration RemoveNameFromUsers name:string実行
bundle installおわり
最後まで見ていただきありがとうございました。
- 投稿日:2020-04-07T11:33:08+09:00
【Rails】マイグレーションファイルやschemaファイルの仕組み、rails db:migrate、rails db:rollbackとかを纏めてみた
マイグレーションファイルとは?
マイグレーションファイルとはデータベースの設計図のことです。
このマイグレーションファイルをどのように作成して、データベースに反映させるのか、
また、一度データベースに反映させた内容をどのように修正できるのか、自分なりに纏めてみました。マイグレーションファイルを作成し、データベースに反映させる
まずはターミナルでマイグレーションファイル(DBの設計図)を作成します。
僕の場合、text型のbodyカラムを持つ、BoardモデルとUserモデのマイグレーションファイルを作成しました。まずはreference型に
board_id:references
、user_id:references
と間違った内容を付けたとします。# 誤った内容であることに注意! rails g model comment body:text board_id:references user_id:referencesこれによって、以下のマイグレーションファイルが作成される。
db/migrate/20200330045356_create_comments.rb# 誤ったファイルであることに注意! class CreateComments < ActiveRecord::Migration[5.2] def change create_table :comments do |t| t.text :body, null: false t.references :board_id, foreign_key: true t.references :user_id, foreign_key: true t.timestamps end end endこのマイグレーションファイルを作成した段階では、データベースに反映されていません。
ターミナルで下記のrails db:migrate
コマンドを実行すると、作成したマイグレーションファイルが読み込まれ、データベースに反映されます。> rails db:migrate == 20200330045356 CreateComments: migrating =================================== -- create_table(:comments) -> 0.0143s == 20200330045356 CreateComments: migrated (0.0165s) ==========================データベースに反映されているマイグレーションファイルを確認する
そして、
rails db:migrate:status
を実行すると、DBに反映されたマイグレーションファイルを確認できます。
up
と書いているファイルがデータベースに反映されているもの(マイグレーション済みということ)です。> rails db:migrate:status Status Migration ID Migration Name -------------------------------------------------- up 20200310093526 Sorcery core up 20200316101344 Create boards up 20200316105948 Add user id to boards up 20200328064702 Add board image to board up 20200330045356 Create comments次に、スキーマファイルで現在のデータベースの構造を確認できるのですが、
下記のcommentsテーブルはboard_id_id
、user_id_id
と誤ったカラムが追加されているので、修正したいです。db/schema.rb# 誤ったテーブル内容 create_table "comments", force: :cascade do |t| t.text "body", null: false t.integer "board_id_id" t.integer "user_id_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["board_id_id"], name: "index_comments_on_board_id_id" t.index ["user_id_id"], name: "index_comments_on_user_id_id" endデータベース及びマイグレーションファイルの修正方法
まずは、
rails db:rollback
で最新のマイグレーションファイルをdown状態にします。
down状態のマイグレーションファイルは、データベースに反映されていない状態にあるということです。
(up状態にあるマイグレーションファイルを削除・編集することは避けましょう)> rails db:rollback == 20200330045356 CreateComments: reverting =================================== -- drop_table(:comments) -> 0.0044s == 20200330045356 CreateComments: reverted (0.0079s) ==========================
rails db:migrate:status
で、以下の様にdown状態になったことが確認できますね。> rails db:migrate:status Status Migration ID Migration Name -------------------------------------------------- up 20200310093526 Sorcery core up 20200316101344 Create boards up 20200316105948 Add user id to boards up 20200328064702 Add board image to board down 20200330045356 Create comments次に行う手順として、2通り方法があります。
①down状態にしたマイグレーションファイルを削除し、新しいマイグレーションファイルを作成してから、rails db:migrate
する。
②down状態にしたマイグレーションファイルを直接編集し、rails db:migrate
する。どちらでもいいのですが、今回はカラム名を修正するだけなので、②の方法を取ってみます。
board_id
をboard
に、user_id
をuser
に変更します。db/migrate/20200330045356_create_comments.rb# 誤ったファイルであることに注意! class CreateComments < ActiveRecord::Migration[5.2] def change create_table :comments do |t| t.text :body, null: false t.references :board, foreign_key: true t.references :user, foreign_key: true t.timestamps end end endこれで
rails db:migrate
すると、マイグレーションファイルがup状態に戻ります。> rails db:migrate:status Status Migration ID Migration Name -------------------------------------------------- up 20200310093526 Sorcery core up 20200316101344 Create boards up 20200316105948 Add user id to boards up 20200328064702 Add board image to board up 20200330045356 Create commentsスキーマファイルを確認すると、
db/schema.rbcreate_table "comments", force: :cascade do |t| t.text "body", null: false t.integer "board_id" t.integer "user_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["board_id"], name: "index_comments_on_board_id" t.index ["user_id"], name: "index_comments_on_user_id" end無事、正しいカラム名を持ったテーブルがデータベースに反映されました!
まとめ
最後に、ここまでの内容をまとめておきます!
- マイグレーションファイルとはデータベースの設計図のこと。
rails g model モデル名 カラム名:型
で、モデルとマイグレーションファイルを作成するrails db:migrate
で、マイグレーションファイルをDBに反映させる(up状態)rails db:rollback
で、マイグレーションファイルをDBに反映させる前の状態に戻す(down状態)rails db:migrate:status
で、各マイグレーションファイルのDBへの反映状態(up,down)を確認できる- マイグレーションファイルの修正方法は、①down状態にしたマイグレーションファイルを削除し、新しいマイグレーションファイルを作成してから、
rails db:migrate
する。もしくは、 ②down状態にしたマイグレーションファイルを直接編集し、rails db:migrate
する。
- 投稿日:2020-04-07T11:28:32+09:00
Sign in with apple実装中に、サーバーサイド側でJWTを検証しようとしたところ、Signature verification raisedが発生する
iOSクライアントからJwt送信 -> Rails側でデコードをする場合に
Signature verification raised
が発生した
各種パラメーターは正しく与えているのにデコードに失敗してしまう
jwtの有効期限が切れたのかと思ったがそういうわけでもないようだった
結論、keysの正しいkidを選択していなかったことだった
Appleのサイトから証明書のjsonを取得すると、keysが2つあるのに気づいていた
確認したところ、デコードに使えるのは片方1つのみで、それはjwtの中に指定されている
正しいkeyのkidを指定したところ、うまく動くようになった
- 投稿日:2020-04-07T10:58:00+09:00
Railsチュートリアルメモ - 第14章
サマリ
- モデルの複雑な関連付けの方法
- 1つのテーブル
relationship
を使って、follower
とfollowing
を管理する方法- ネストしたルーティングの実装
- ajaxの実装
ポイント
テーブル名と異なるカラムをFKとして使用したい場合
1対多の1側
# user.rb has_many :active_relationships, class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy1対多の多側
# relationship.rb # follower_idカラムが存在している必要がある belongs_to :follower, class_name: "User"
has_many
とbelongs_to
について
has_many
で指定できるパターン# モデルの複数形 e.g. microposts has_many :microposts # 別名のシンボル、クラス名、FK has_many :active_relationships, class_name: "Relationship", foreign_key: "follower_id"
- 関連付けとは別に、
has_many :hoge, through: :fuga, source: :foobar
を定義しておくと、こ配列``
belongs_to
で指定できるパターンデフォルトでは外部キーの名前を_idといったパターンとして理解し、 に当たる部分からクラス名を推測する
1. モデルのシンボル
2. 別名とクラス名class Relationship < ApplicationRecord belongs_to :follower, class_name: "User" belongs_to :followed, class_name: "User" endルーティングのカスタマイズ - ネストしたURLの設定
# /users/1/following や /users/1/followers resources :users do member do get :following, :followers end end # /users/tigers resources :users do collection do get :tigers end endAjaxの実装方法
- ローカル変数ではなくインスタンス変数への変更が必要
- viewには
form_for
の引数に, remote: true
を追加する- controllerには
respond_to
を追加するdef create @user = User.find(params[:followed_id]) current_user.follow(@user) respond_to do |format| format.html { redirect_to @user } format.js end endアンパサンド(&)を使ったブロックの短縮表記
# 以下は同値 [1, 2, 3, 4].map { |i| i.to_s } [1, 2, 3, 4].map(&:to_s)感想
- めちゃくちゃ時間がかかったがなんとか完走することができた
- チュートリアルとはいえtwitter風の動くアプリが完成したのは嬉しい
- まだまだ理解しきったといえる状態ではないので、3周目4週目をやりながら使いこなしていきたい
- 投稿日:2020-04-07T07:59:54+09:00
コーディング未経験のPO/PdMのためのRails on Dockerハンズオン、Rails on Dockerハンズオン vol.14 - TDDでPost機能をコーディング part3 -
はじめに
こんにちは!
またまたPost機能の開発の続きです。今回でラスト!前回のソースコード
前回のソースコードはこちらに格納してます。今回のハンズオンからやりたい場合はこちらからダウンロードしてください。
前回の残り
- 未サインインのユーザーは、ユーザー詳細ページでそのユーザーのポストを投稿日時降順で閲覧できること
- 未サインインのユーザーが、ユーザー詳細ページでそのユーザーのポストのユーザー名をクリックしたとき、何も起こらないこと
- サインイン済のユーザーは、ユーザー詳細ページでそのユーザーのポストを投稿日時降順で閲覧できること
- サインイン済のユーザーが、ユーザー詳細ページでそのユーザーのポストのユーザー名をクリックしたとき、何も起こらないこと
- サインイン済のユーザーは、プロフィールページで自身のポストを投稿日時降順で閲覧できること
- サインイン済のユーザーが、プロフィールページでそのユーザーのポストのユーザー名をクリックしたとき、何も起こらないこと
残り6シナリオ。ユーザー詳細ページにそのユーザーのポストを表示する機能ですね。
では早速最後のコーディングをしていきましょう!!
未サインインのユーザーは、ユーザー詳細ページでそのユーザーのポストを投稿日時降順で閲覧できること
ユーザー詳細ページでそのユーザーのポストが投稿日時降順で表示されていることと、他のユーザーのポストが表示されていないことを検証します。
spec/system/07_posts_spec.rbfeature "ユーザーとして、ポストを投稿したい", type: :system do ... + scenario "未サインインのユーザーは、ユーザー詳細ページでそのユーザーのポストを投稿日時降順で閲覧できること" do + # テスト用のユーザーを作成する + user1 = create_user(1) + user2 = create_user(2) + # ポストを用意する + posts1 = [] + posts1.unshift Post.create(content: "First Post!!", user: user1) + posts1.unshift Post.create(content: "Second Post!!", user: user1) + posts2 = [] + posts2.unshift Post.create(content: "初めてのポスト", user: user2) + posts2.unshift Post.create(content: "2回目のポスト", user: user2) + + # user1のユーザー詳細ページにアクセスする + visit user_path(user1) + + # user1のポストが投稿日時降順で表示されていることを検証する + posts1.each_with_index do |post, i| + expect(find("#posts_list").all(".post-item")[i]).to have_text post.user.name + expect(find("#posts_list").all(".post-item")[i]).to have_text post.content + end + # user2のポストは表示されないことを検証する + posts2.each do |post| + expect(page).not_to have_text post.user.name + expect(page).not_to have_text post.content + end + + # user2のユーザー詳細ページにアクセスする + visit user_path(user2) + + # user2のポストが投稿日時降順で表示されていることを検証する + posts2.each_with_index do |post, i| + expect(find("#posts_list").all(".post-item")[i]).to have_text post.user.name + expect(find("#posts_list").all(".post-item")[i]).to have_text post.content + end + # user1のポストは表示されないことを検証する + posts1.each do |post| + expect(page).not_to have_text post.user.name + expect(page).not_to have_text post.content + end + end end
少し長いですが、今までの延長で理解できるコードになっているはずです!(コメントアウトも参考にしてね。)
さて、このテストを回してみましょう。# rspec spec/system/07_posts_spec.rb Failures: 1) ユーザーとして、ポストを投稿したい 未サインインのユーザーは、ユーザー詳細ページでそのユーザーのポストを投稿日時降順で閲覧できること Failure/Error: expect(find("#posts_list").all(".post-item")[i].find(".post-user-name")).to have_text user.name Capybara::ElementNotFound: Unable to find css "#posts_list" Finished in 27.77 seconds (files took 5.67 seconds to load) 16 examples, 1 failureこの時点では
#posts_list
がないと怒られます。posts_list
、つまりユーザー詳細ページでポストを表示する機能をコーディングしていないので、テストが失敗しています。さて、今回の
posts_list
ですが、ポストページで同じようにポストの一覧を表示するViewを作りました。開発を効率的に進めるために、是非その時の機能を利用したいですね。
Railsでは部分テンプレート(Partial Template)という機能があります。複数のテンプレートから呼び出されるような一部分のViewを別ファイルに切り出して、各テンプレートからそれを呼び出すようなイメージです。
百聞は一見にしかずですので、まずは試してみましょう。
まずは、ポストページのposts_list
配下の要素を部分テンプレート化してみます。# touch app/views/posts/_posts_list.html.erb部分テンプレートは頭に
_
をつけるのが習わしです。app/views/posts/_posts_list.html.erb<% posts.each do |post| %> <div class="card post-item my-1"> <div class="card-body"> <h5 class="card-title"><%= link_to post.user.name, post.user, class: "post-user-name" %></h5> <p class="card-text"><%= safe_join(post.content.split("\n"), tag(:br)) %></p> </div> </div> <% end %>部分テンプレートはこんな感じで書きます。
posts/index.html.erb
に書いていた内容と変わらないです。唯一変わるポイントは最初のeach
する配列の変数が@posts
からposts
になっていることです。
部分テンプレートは別のテンプレートファイルから呼び出されますが、その時にインスタンス変数でなくても変数を渡すことができます。これも実際にみてみた方が早いと思いますので、まずはposts/index.html.erb
からこの部分テンプレートを読み込んで今と変わらない状態になることを確認してみましょう。app/views/posts/index.html.erb... <div id="posts_list" class="my-5"> - <% @posts.each do |post| %> - <div class="card post-item my-1"> - <div class="card-body"> - <h5 class="card-title"><%= link_to post.user.name, post.user, class: "post-user-name" %></h5> - <p class="card-text"><%= safe_join(post.content.split("\n"), tag(:br)) %></p> - </div> - </div> - <% end %> + <%= render partial: "posts_list", locals: { posts: @posts } %> </div> ...呼び出し方は
render partial:
に対して適用したいテンプレートのファイル名(頭の_
は除く)を指定するだけです。さらにオプションでlocals:
の後に{ 変数名: 値 }
をつけることで部分テンプレートに変数を受け渡すことができます。今回の例では@posts
を部分テンプレート内のposts
に代入させていることになります。
変数は複数受け渡すことができ、その場合は{ 変数名1: 値1, 変数名2: 値2 }
のように,
で区切るだけです。ここで一度デグレが起きていないかテストを実行しておきましょう。
# rspec spec/system/07_posts_spec.rb Failures: 1) ユーザーとして、ポストを投稿したい 未サインインのユーザーは、ユーザー詳細ページでそのユーザーのポストを投稿日時降順で閲覧できること Failure/Error: expect(find("#posts_list").all(".post-item")[i].find(".post-user-name")).to have_text user.name Capybara::ElementNotFound: Unable to find css "#posts_list" Finished in 37.59 seconds (files took 6.58 seconds to load) 16 examples, 1 failure今取り掛かっているシナリオのテスト失敗だけなので、ポストページに関するテスト失敗は起きていませんね。うまく部分テンプレートが機能しているようです。
さて、ユーザー詳細ページでもこの部分テンプレートを利用しましょう。
ユーザー詳細ページではそのユーザーのポストだけを表示したいので、部分テンプレートに渡すposts
変数にはそのユーザーのポストのArrayを渡してあげればいいことになります。app/views/users/show.html.erb<div class="container my-5"> <% flash.each do |msg_type, msg| %> <div class="alert alert-<%= msg_type %>"><%= msg %></div> <% end %> <%= @user.name %> <br> <%= @user.email %> + + <div id="posts_list" class="my-5"> + <%= render partial: "posts/posts_list", locals: { posts: @user.posts.order(created_at: :desc) } %> + </div> </div>
先ほどと少し違うのは、
_posts_list.html.erb
がこのファイルとは別のディレクトリ(posts/
)にあるので、そのディレクトリも含めて部分テンプレートファイル名を指定しています。(posts/posts_list
)
また、posts
変数には@user.posts.order(created_at: :desc)
でそのユーザーのポストを作成日時降順で取得したArrayを部分テンプレートに渡しています。ではテストを回してみましょう。
# rspec spec/system/07_posts_spec.rb Finished in 50.91 seconds (files took 7.01 seconds to load) 16 examples, 0 failuresGreenになりました!
未サインインのユーザーが、ユーザー詳細ページでそのユーザーのポストのユーザー名をクリックしたとき、何も起こらないこと
ポストページの場合はユーザー名クリックでユーザー詳細ページへ遷移させていましたが、ユーザー詳細ページ上では
spec/system/07_posts_spec.rbfeature "ユーザーとして、ポストを投稿したい", type: :system do ... + scenario "未サインインのユーザーが、ユーザー詳細ページでそのユーザーのポストのユーザー名をクリックしたとき、何も起こらないこと" do + # テスト用のユーザーを作成する + user = create_user(1) + # テスト用のポストを作成する + posts = [] + posts.unshift Post.create(content: "First Post!!", user: user) + posts.unshift Post.create(content: "Second Post!!", user: user) + + # userのユーザー詳細ページにアクセスする + visit user_path(user) + + # ポストのユーザー名がリンクになっていないことを検証する + posts.each_with_index do |post, i| + expect(find("#posts_list").all(".post-item")[i]).not_to have_selector("a.post-user-name") + end + end end
ポストページと同じ部分テンプレートを使っているので、現在はポストのカードの中のユーザーの名前が表示されている要素は
post-user-name
をclass属性に付与されているa
タグになっています。
これがリンクを作っているところなので、この要素がない状態であれば、ユーザー名をクリックしても何も起こらないことを検証できます。# rspec spec/system/07_posts_spec.rb Failures: 1) ユーザーとして、ポストを投稿したい 未サインインのユーザーが、ユーザー詳細ページでそのユーザーのポストのユーザー名をクリックしたとき、何も起こらないこと Failure/Error: expect(find("#posts_list")).not_to have_selector("a.post-user-name") expected not to find visible css "a.post-user-name" within #<Capybara::Node::Element tag="div" path="/HTML/BODY[1]/DIV[1]/DIV[1]">, found 2 matches: "John Smith", "John Smith" Finished in 41.18 seconds (files took 5.05 seconds to load) 17 examples, 1 failure現在はリンクがついてしまっているのでこれをなんとかします。
今
link_to
を使ってリンクを作っていますが、link_to_unless_current
を使ってみます。使い方はlink_to
と変わりないのですが、リンク先が今のパスの場合は単なるテキストを表示してくれるメソッドです。app/views/posts/_posts_list.erb- <h5 class="card-title"><%= link_to post.user.name, post.user, class: "post-user-name" %></h5> + <h5 class="card-title"><%= link_to_unless_current post.user.name, post.user, class: "post-user-name" %></h5>これでテストを実行してみましょう。
# rspec spec/system/07_posts_spec.rb Finished in 34.46 seconds (files took 4.86 seconds to load) 17 examples, 0 failures無事テストがパスしました。ポストページの方もデグレは起きていないかも全てのテストをパスしていることから確認できますね。
サインイン済のユーザーは、ユーザー詳細ページでそのユーザーのポストを投稿日時降順で閲覧できること
上の2つのテストのサインイン済版ですね。
spec/system/07_posts_spec.rbfeature "ユーザーとして、ポストを投稿したい", type: :system do ... + scenario "サインイン済のユーザーは、ユーザー詳細ページでそのユーザーのポストを投稿日時降順で閲覧できること" do + # テスト用のユーザーを作成する + user1 = create_user(1) + user2 = create_user(2) + # ポストを用意する + posts1 = [] + posts1.unshift Post.create(content: "First Post!!", user: user1) + posts1.unshift Post.create(content: "Second Post!!", user: user1) + posts2 = [] + posts2.unshift Post.create(content: "初めてのポスト", user: user2) + posts2.unshift Post.create(content: "2回目のポスト", user: user2) + # user1でサインインする + sign_in(user1) + + # user2のユーザー詳細ページにアクセスする + visit user_path(user2) + + # user2のポストが投稿日時降順で表示されていることを検証する + posts2.each_with_index do |post, i| + expect(find("#posts_list").all(".post-item")[i]).to have_text post.user.name + expect(find("#posts_list").all(".post-item")[i]).to have_text post.content + end + # user1のポストは表示されないことを検証する + posts1.each do |post| + expect(page).not_to have_text post.user.name + expect(page).not_to have_text post.content + end + end end
未サインインの時と検証内容は同じですね。
# rspec spec/system/07_posts_spec.rb Finished in 36.67 seconds (files took 7.58 seconds to load) 18 examples, 0 failuresすでに実装済みですのでテストはGreenです。
サインイン済のユーザーが、ユーザー詳細ページでそのユーザーのポストのユーザー名をクリックしたとき、何も起こらないこと
これも未サインインで同じテストをしているのでそれをパクります。
spec/system/07_posts_spec.rbfeature "ユーザーとして、ポストを投稿したい", type: :system do ... + scenario "サインイン済のユーザーが、ユーザー詳細ページでそのユーザーのポストのユーザー名をクリックしたとき、何も起こらないこと" do + # テスト用のユーザーを作成する + user1 = create_user(1) + user2 = create_user(2) + # テスト用のポストを作成する + posts = [] + posts.unshift Post.create(content: "First Post!!", user: user2) + posts.unshift Post.create(content: "Second Post!!", user: user2) + # user1でサインインする + sign_in(user1) + + # user2のユーザー詳細ページにアクセスする + visit user_path(user2) + + # ポストのユーザー名がリンクになっていないことを検証する + posts.each_with_index do |post, i| + expect(find("#posts_list").all(".post-item")[i]).not_to have_selector("a.post-user-name") + end + end end
# rspec spec/system/07_posts_spec.rb Finished in 41.16 seconds (files took 5.61 seconds to load) 19 examples, 0 failuresこれも実装済みなのでテストがパスしていますね。
サインイン済のユーザーは、プロフィールページで自身のポストを投稿日時降順で閲覧できること
これも以前のテストとほぼ同じ。
spec/system/07_posts_spec.rbfeature "ユーザーとして、ポストを投稿したい", type: :system do ... + scenario "サインイン済のユーザーは、プロフィールページで自身のポストを投稿日時降順で閲覧できること" do + # テスト用のユーザーを作成する + user1 = create_user(1) + user2 = create_user(2) + # ポストを用意する + posts1 = [] + posts1.unshift Post.create(content: "First Post!!", user: user1) + posts1.unshift Post.create(content: "Second Post!!", user: user1) + posts2 = [] + posts2.unshift Post.create(content: "初めてのポスト", user: user2) + posts2.unshift Post.create(content: "2回目のポスト", user: user2) + # user1でサインインする + sign_in(user1) + + # user1のプロフィールページにアクセスする + visit user_path(user1) + + # user1のポストが投稿日時降順で表示されていることを検証する + posts1.each_with_index do |post, i| + expect(find("#posts_list").all(".post-item")[i]).to have_text post.user.name + expect(find("#posts_list").all(".post-item")[i]).to have_text post.content + end + # user2のポストは表示されないことを検証する + posts2.each do |post| + expect(page).not_to have_text post.user.name + expect(page).not_to have_text post.content + end + end end
ほい。ではテストを回しましょう。
# rspec spec/system/07_posts_spec.rb Finished in 41.83 seconds (files took 6.77 seconds to load) 20 examples, 0 failuresこれもパス。
サインイン済のユーザーが、プロフィールページでそのユーザーのポストのユーザー名をクリックしたとき、何も起こらないこと
これも同じようなテストをすでにしていますね。
spec/system/07_posts_spec.rbfeature "ユーザーとして、ポストを投稿したい", type: :system do ... + scenario "サインイン済のユーザーが、プロフィールページでそのユーザーのポストのユーザー名をクリックしたとき、何も起こらないこと" do + # テスト用のユーザーを作成する + user = create_user(1) + # テスト用のポストを作成する + posts = [] + posts.unshift Post.create(content: "First Post!!", user: user) + posts.unshift Post.create(content: "Second Post!!", user: user) + # user1でサインインする + sign_in(user) + + # user1のプロフィールページにアクセスする + visit user_path(user) + + # ポストのユーザー名がリンクになっていないことを検証する + posts.each_with_index do |post, i| + expect(find("#posts_list").all(".post-item")[i]).not_to have_selector("a.post-user-name") + end + end end
これもパスするはず。
# rspec spec/system/07_posts_spec.rb Finished in 43.64 seconds (files took 6.54 seconds to load) 21 examples, 0 failuresパスしましたね。
ここまででポスト機能で定義したテストシナリオは全てパスできるアプリケーションを作ることができました!
最後に、今までのテストシナリオも含めてデグレの確認をしておきましょう!# rspec Finished in 1 minute 56.88 seconds (files took 6.09 seconds to load) 91 examples, 0 failures2分ほど時間がかかりましたが、全てのテストをクリアできていました!!
まとめ
今日はここまでです!前回、前々回と3回に渡ってポスト機能をTDDでコーディングしてきましたがいかがだったでしょうか?
モデルの関連付け(has_many
,belongs_to
)や部分テンプレート(Partial Template)など新しく使ったものもありましたが、基本的な部分はハードルなくコーディングできるようになったのではないでしょうか?
実際にサービスをリリースするとなると、例えばアイコン登録とか、フォロー機能とか、いいね機能とか、、、作りたい機能がどんどんでてくるとは思いますが、すでに自分で調べながらコーディングをしていくことに対するハードルはなくなったんじゃないでしょうか?
ということでこのハンズオンのコーディング部分はこれで以上にしたいと思います。最後はデプロイです!次とその次、2回に分けてアプリケーションを
Heroku
とEKS
にデプロイしてみようと思います。ここまでできれば、自分の好きなサービスを作って世に公開することができます。ではまた次週!
後片付け
# exit$ docker-compose down本日のソースコード
Other Hands-on Links
- 投稿日:2020-04-07T03:50:41+09:00
任意のページにPV数(訪問者数)を計測、表示する【rails】
概要
webページを作成してリリースしたあと、大体何人見に来てくれているのか気になりますよね!以下のような「あなたは<51>人目の訪問者です」のような記述です。
アクセス回数などで調べる方法もありますが、同一人物が何度もアクセスしたら、延べ人数として重複して数えられてしまいます。これでは、より多くの人に見てもらいたいという意味では、正確な数字が測りにくくなります。また、他のQiita記事を見ると、impressionistとうgemで計測できるとあります。しかし、これはあくまで投稿詳細ページのPV数しか計測できず(投稿のidと紐づけられているため)任意のページのPV数が取得できません。そこで、ここでは、この二つの問題を"ある程度"解決できるIPアドレスを記録するという方法で訪問者数を計測していきます。(ログイン機能があれば、User数で知ることもできますね!)
IPアドレスとは
IPアドレスは機種ごと(パソコンやスマホ)に割り振られる固有の番号と知っている方も多いと思います。しかし、実際はやや異なるみたいです。世界で一つしかないIPアドレス(グローバルIP)はルーター(各家庭や各施設に一つあるインターネットとの出入口の役割を果たすもの)が持っており、その家庭内でのみ一意性が保たれるIPアドレス(プライベートIP)を各機種が持っているというのが一般的みたいです。後述のようにrailsでIPアドレスを取得する場合は、基本的にこのルーターのグローバルIPアドレスが取得されてしまうようです。したがって例えば、家庭内で同じWi-fiを使っている人同士は全て同一のIPアドレスとなります。何人いようが一人と計測されてしまうわけですね。もしくは、Wi-fiや機種を変えたり、インターネット環境を変えると、同一人物でも異なるIPアドレスが取得されてしまいます。このようにIPアドレスで訪問者数をカウントする場合は、"ある程度"の概算にならざるを得ないという事情があります。しかし、アクセス回数で計測する際の、製作者が気になって何度もwebページを見に来たり、利用者がPV数を誤魔化そうとその場で大量にアクセスしまくるといったPV数の不正確性に比べると、まだメリットは大きいのかなと個人的には感じています。
ipアドレス計測用のモデル(テーブル)作成
上記の事情を理解して頂けた方は、さっそく実装に入りましょう。まず、以下のようにipアドレスを計測するモデル(テーブル)を作成します。ターミナル(コマンドプロンプト)に入力してください。(プロダクトフォルダーの階層に移動することを忘れずに!)
$ rails generate model See ip:stringここではSeeモデルという名前で作成します。(※データ型はstring型にしてください。ipアドレスはstring型で取得されるためです)するとmigrationファイルに
db/migration/******.rbclass CreateSees < ActiveRecord::Migration[*.*] def change create_table :sees do |t| t.string :ip t.timestamps end end endが作成されました。以下を実行してテーブルにカラムを適用させましょう。(ターミナル)
$ rails db:migrateこれにてIPアドレスを計測できるデータベースは完成しました。
Controllerの中身変更
次にControllerの中身に入ります。訪問者数を計測&表示したいviewに対応するアクションの中身を以下のように変えてください。ここでは例としてtweetsコントローラーのindexアクションを取り上げます。
app/views/tweets/index.html.erbdef index @see = See.find_by(ip: request.remote_ip) if @see @tweets = Tweet.all else @tweets = Tweet.all See.create(ip: request.remote_ip) end end
request.remote_ip
で前述のようにアクセス者のIPアドレスが取得できます。何をしているかというと、まずseesテーブルで、アクセスした利用者のipアドレスと等しいipのレコードがあるか探し、あれば@seeに代入します。次に、もし、@seeが存在していれば(利用者が過去アクセスしたことがあれば)そのままviewページに受け渡す変数を書くだけです。もし、@seeが空だったら(利用者が初訪問の場合は)viewページに変数が受け渡されるとともに、seesテーブルのipカラムに、新たに利用者のIPアドレスが追加され保存されます。(検索機能などを付けている場合は、以下のように条件分岐が入れ子になります!)app/views/tweets/index.html.erbdef index @see = See.find_by(ip: request.remote_ip) if @see if params[:search] #部分検索 @tweets = Tweet.where("content LIKE ? ",'%' + params[:search] + '%') else @tweets= Tweet.all end else See.create(ip: request.remote_ip) if params[:search] #部分検索 @tweets = Tweet.where("content LIKE ? ",'%' + params[:search] + '%') else @tweets= Tweet.all end end endviewページで訪問者数を表示する
これは簡単で以下の一文を任意の場所に追加するだけです。
あなたは<<%= See.count %>>人目の訪問者ですこれにより、過去訪問してきた、重複の無いIPアドレスの合計数が表示されます。(厳密には前述の理由と、今の新訪問者が未カウントなので、正確性を欠きますが。)今の"新"訪問者をカウントするなら
あなたは<<%= See.count + 1 %>>人目の訪問者ですこのようになりますね!
最後に注意点
ローカル環境で開発する場合、この
request.remote_ip
では、ローカルでデフォルトで用意されている"::1"というものがipアドレスとして取得されてしまいます。開発中にちゃんとできているか実験したくても、それは不可能です。言い換えると、開発環境においては、たとえ異なるパソコンでも全て同じデフォルトのIPアドレスになってしまうわけですね。ご注意ください。リリースしてからのお楽しみで!
- 投稿日:2020-04-07T02:15:23+09:00
[Rails]JS(jquery)が動かない 初歩的ミス
jquery使用の際、同じコード引用下のに別アプリで
なぜか動かない。
と言う原因の一例です。確認した場所
■Gemfile→jqueryがインストールされているか
■application.jsにjqueryの記述があるか。
■js内にturbolinks:load
が入っているかどうか原因
コメントアウトされているコードの並び順
*これ、すごく大事でした。
初歩の初歩でテキストに書いてあった気がする。。。
//= require_tree .
が、最初はこの位置におりました。
移動!!最下部へ
こちらでjqueryが動かない問題は解決いたしました。require_tree .とは?
参考資料によると以下の記載があります(ちなみに
require_tree
はcssにも記述あります)
そしてアセットパイプライン
と言うそうです。
application内にあるcssやjsの読み込み順を司るものです。require_treeディレクティブは、指定されたディレクトリ以下の すべての JavaScriptファイルを再帰的にインクルードし、出力に含めます。 このパスは、マニフェストファイルからの相対パスとして指定する必要があります。 require_directoryディレクティブを使用すると、指定されたディレクトリの 直下にあるすべてのJavaScriptファイルのみをインクルードします。 この場合サブディレクトリを再帰的に探索しません。日本語難しい(´・ω・)
少し簡単な説明require の部分は ディレクティブ (何種類かあります)
require_directory
→与えられたディレクトリ以下のファイルを、
自身よりも前に挿入する。
順番はアルファベット順(さらに大文字→小文字)になる
require_tree
→require=directory と同じ動きをするが、再帰的に読み込む
読み込みはコードの 上から順番に 読み込まれます。上記より考えると、
require_tree
より下に書かれていたjqueryが読み込まれなかったため動かなかった(と、解釈いたしました。)[備考]
require_treeには引数として与えられたディレクトリ以下のファイルをアルファベット順に全て読み込むという意味があります。
現在require_treeの引数には.(ドット)
が渡されています。
引数.(ドット)
はカレントディレクトリを表します。
つまり、この記述によってapp/assets/javascriptsというディレクトリにあるファイルは全て読み込まれることになります。参考ページ
Rails のアセットパイプライン(Asset Pipeline)について
終わりに
こちらは、個人的解釈をもとに解決した方法を備忘録として書いております。
プログラミング初学者ゆえ、
誤記や不備、アドバイス等ございましたら御指摘いただけると幸いです。
最後まで読んでいただきありがとうございます。
- 投稿日:2020-04-07T01:03:50+09:00
【Rails】ルーティングをネストした時にshallowオプションを使うと便利だよ
ネストしたルーティングにshallowオプションを使うと便利だよ!と知ったので、自分なりに記述してみます?
ルーティングをネストする
- このような親子関係のモデルがあるとする。
- Boardモデルが
has_many :comments
- Commentモデルが
belongs_to :board
以下の記述は、ルーティングをネストすることで、掲示板(Board)とコメント(Comment)の親子関係をルーティングで表しています。
config/routes.rbresources :boards do resources :comments end次にターミナルで
rails routes
を実行し、利用可能なルーティングをすべて表示してみます。
rails routes
で確認できる情報
- ルーティング名 (あれば)
- 使用されているHTTP動詞 (そのルーティングがすべてのHTTP動詞に応答するのでない場合)
- マッチするURLパターン
- そのルーティングで使うパラメータ
# rails routes board_comments GET /boards/:board_id/comments(.:format) comments#index POST /boards/:board_id/comments(.:format) comments#create new_board_comment GET /boards/:board_id/comments/new(.:format) comments#new edit_board_comment GET /boards/:board_id/comments/:id/edit(.:format) comments#edit board_comment GET /boards/:board_id/comments/:id(.:format) comments#show PATCH /boards/:board_id/comments/:id(.:format) comments#update PUT /boards/:board_id/comments/:id(.:format) comments#update DELETE /boards/:board_id/comments/:id(.:format) comments#destroy boards GET /boards(.:format) boards#index POST /boards(.:format) boards#create new_board GET /boards/new(.:format) boards#new edit_board GET /boards/:id/edit(.:format) boards#edit board GET /boards/:id(.:format) boards#show PATCH /boards/:id(.:format) boards#update PUT /boards/:id(.:format) boards#update DELETE /boards/:id(.:format) boards#destroyこの状態だと、ある掲示板(board)のコメント(comments)の詳細ページをeditしたり、showしたりする時のURLは、
/boards/:board_id/comments/:id/edit
/boards/:board_id/comments/:id
といった風に指定する必要があり、冗長ですね。shallowオプションの登場
そこで、ネストしたルーティングに、以下の様に
shallow: true
を付けてあげます。config/routes.rbresources :boards do resources :comments, shallow: true end# rails routes board_comments GET /boards/:board_id/comments(.:format) comments#index POST /boards/:board_id/comments(.:format) comments#create new_board_comment GET /boards/:board_id/comments/new(.:format) comments#new edit_comment GET /comments/:id/edit(.:format) comments#edit comment GET /comments/:id(.:format) comments#show PATCH /comments/:id(.:format) comments#update PUT /comments/:id(.:format) comments#update DELETE /comments/:id(.:format) comments#destroy boards GET /boards(.:format) boards#index POST /boards(.:format) boards#create new_board GET /boards/new(.:format) boards#new edit_board GET /boards/:id/edit(.:format) boards#edit board GET /boards/:id(.:format) boards#show PATCH /boards/:id(.:format) boards#update PUT /boards/:id(.:format) boards#update DELETE /boards/:id(.:format) boards#destroyすると、index, new, createの3つのアクション(Commentのidを指定しないアクション)はBoardのidを必要としますが、それ以外のアクションは子のidを指定するだけで済むようになりました。
これは、
- index、new、createは、Commentのidを指定しないため、Boardのidを指定しないと、どのBoardに対するものか参照できない。
- show、edit、update、destroyは、Commentのidを指定するため、それだけで一意性を持てるから、Boardのidを指定する必要がない。
と考えると分かりやすいかなと思います!
参照先
Rails のルーティング(https://railsguides.jp/routing.html)