- 投稿日:2020-04-05T23:57:31+09:00
【Ruby】puts,p,printメソッドの違い
puts、p、printメソッドについて、違いが分からなかったので調べてみました。
putsメソッド
-改行あり
-引数のオブジェクトを文字列に変換して出力putsメソッドputs "あけまして、そらじろう" puts 20200405ターミナルあけまして、そらじろう 20200405
pメソッド
-改行あり
-引数と引数の文字型(文字列や数値)も出力pメソッドp "あけまして、そらじろう" p 20200405ターミナル"あけまして、そらじろう" 20200405
printメソッド
-改行なし
-数のオブジェクトを文字列に変換し出力printメソッドprint "あけまして、そらじろう" print 20200405ターミナルあけまして、そらじろう20200405
追記
putsメソッドとpメソッドの違いは、文字列として識別できるかどうか
printメソッドは、改行のないputsメソッドちなみにpメソッドは、デバッグでよく使うみたいです。
<https://gihyo.jp/book/2018/978-4-297-10123-7
<https://rurema.clear-code.com/
- 投稿日:2020-04-05T23:28:21+09:00
Dockerを使ってrailsのAPIモードの環境構築
はじめに
今回は、Dockerを使ってRailsのAPIモードの環境構築をしていく記事です。
コマンド、設定などを詳細に書いていきます。記事の最後までの所要時間は30分以内です。意外と簡単でした。
APIモードとは
APIモードとはRails5から追加された機能で、APIのようなRailsアプリケーションを作れる機能だそうです。
MVCのうち、モデルとコントローラーのみが作成されます。
APIモードで作成されたアプリのURLにリクエストを送ると、json形式のデータがレスポンスとして返ってきます。環境構築
では、環境構築をしていきます。
$ mkdir sample_app $ cd sample_app $ docker pull ruby:2.5.1 $ docker run --rm -v "$PWD":/usr/src/sample_app -w /usr/src/sample_app ruby:2.5.1 bundle init $ docker build -t developer_name/sample_app .ここで、
Dockerfile
、docker-compose.yml
、Gemfile
、Gemfile.lock
の4つのファイルが必要となるので、sample_app
以下にそれぞれ作っていきます。$ touch 各ファイル
Dockerfile
のsample_app
はそれぞれのディレクトリ名に変更してください。Dockerfile# Debianがベースのrubyイメージを指定 FROM ruby:2.5.1 # 必要なものをインストール RUN apt-get update -qq && apt-get -y install \ build-essential \ libpq-dev \ nodejs \ mysql-client # rails用のディレクトリを作成 RUN mkdir /sample_app # ローカルマシン(Mac)からコンテナの中にファイルをコピー COPY Gemfile /API_sample COPY Gemfile.lock /sample_app # 作業ディレクトリを指定 WORKDIR /sample_app # 上でコピーしたGemfileに従ってGemをインストール RUN gem install bundler && bundle install続いて、
docker-compose.yml
です。
MYSQL_ROOT_PASSWORD
は、後で作成するdatabase.yml
のパスワードに合わせます。docker-compose.ymlversion: '3' services: web: build: . ports: - "3000:3000" depends_on: - db volumes: - .:/API_sample command: bundle exec rails s -p 3000 -b '0.0.0.0' db: image: mysql:5.7 volumes: - mysql_data:/var/lib/mysql/ environment: MYSQL_ROOT_PASSWORD: password ports: - "3306:3306" volumes: mysql_data:Railsアプリの作成
新規アプリ作成時に、後ろに
--api
をつけるとAPIモードでアプリが作成されます。$ docker-compose run web rails new . --force --database=mysql --api $ docker-compose run --rm web rails generate scaffold User name:string $ docker-compose run --rm web rails db:create $ docker-compose run --rm web rails db:migratescaffoldでUserを作ったら、ファイルを見てみましょう。
普通のrailsアプリで作られるassets
やview
が無いことが確認できると思います。ページを開く
それでは、アプリをlocalhostで開いてみましょう。
http://localhost:3000/users真っ白なページに、
[]
とだけ表示されているはずです。
これは、データが空であるということを表しています。では、
curl
コマンドでデータのリクエストを送ってみます。curlはURLシンタックスを用いてファイルを送信または受信するコマンドラインツールである。(Wikipediaより)
下記の例を簡単に説明すると、指定したURLに対して、POSTメソッドを使用してターミナルからjsonファイルを送信しているということです。
$ curl -X POST -H "Content-Type: application/json" -d '{"name": "hoge"}' http://localhost:3000/usersそして、http://localhost:3000/users に接続すると
[{"id":1,"name":"hoge","created_at":"","updated_at":""}]
というデータが表示されると思います。(日付はカットしました)これで完了です!意外とあっさりできますよね。
他にも、GET、PUT、DELETEも使えるので色々と試してみてください。感想
APIモードと聞いて最初はよくわからないな、怖いなと思っていました。
ですが、やってみると簡単ですし、今まで深く考えていなかったHTTPについて調べたり知ったりするきっかけになったので良かったです。駆け出しエンジニアにとっても簡単なのでぜひやってみてはいかがでしょうか?
- 投稿日:2020-04-05T23:13:21+09:00
[Rails]画像選択時にプレビュー表示
本記事投稿のいきさつ
アプリの中で画像投稿機能を実装したとき、ただフォームを作成するだけでは画像を選択しても表示がされず何を選んだのか確認をすることが出来ません。
そこで、画像を選択した時点でプレビュー表示することができればいいなと思い、実際に機プレビュー機能を作成したため、記録として残したいと思います。
今回はユーザーのプロフィール画像の編集画面を想定します。
そのため、既に登録されている画像は最初からプレビューさせた状態で表示をさせます。前提
- 変種画面はprofile_edit.html.haml
- 画像の保存先は Usersテーブル: imageカラム
- file_field本体は隠し、画像が選択されていない時はiconを、 選択されている時はプレビュー画像をクリックすることで画像選択できるようにします。
- jQueryを使用しますが、必要なGemのインストール等は既に出来ているとします。
- ユーザー機能はDeviseを使用しています。
- cssは今回の内容に含まれません。
フォーム作成
まず、今回はhamlでビューのフォームを作成します。
profile_edit.html.haml= form_for current_user, url: {action: 'profile_update'} do |f| .form-group .image_form .image_form__contents -# ラベルでfile_fieldとicon、プレビュー画像を紐付けます = f.label :image, class: 'image_label' do .prev-contents -# 既に登録されている画像があれば表示をさせます - if current_user.image.present? .prev-content = image_tag current_user.image.url, alt: "preview", class: "prev-image" -# 既に登録されている画像がなければiconを表示させます - else = icon('fas', 'image', class: 'photo-icon') -# file_fieldはdisplay: none;で隠します = f.file_field :image, class: 'image_form__contents__field hidden_file'FileReader
今回はFileReaderを使用します。
FileReaderとはHTML5世代の機能でユーザーのPC内にあるファイルやバッファ上の生データに対して、読み取りアクセスを行えるオブジェクトです。jsファイルの編集
今回はimage_preview.jsを作成して、そこに記述していきます。
image_preview.js$(document).on('turbolinks:load', function () { $(function () { // 画像をプレビュー表示させる.prev-contentを作成 function buildHTML(image) { var html = ` <div class="prev-content"> <img src="${image}", alt="preview" class="prev-image"> </div> ` return html; } // 画像が選択された時に発火します $(document).on('change', '.hidden_file', function () { // .file_filedからデータを取得して変数fileに代入します var file = this.files[0]; // FileReaderオブジェクトを作成します var reader = new FileReader(); // DataURIScheme文字列を取得します reader.readAsDataURL(file); // 読み込みが完了したら処理が実行されます reader.onload = function () { // 読み込んだファイルの内容を取得して変数imageに代入します var image = this.result; // プレビュー画像がなければ処理を実行します if ($('.prev-content').length == 0) { // 読み込んだ画像ファイルをbuildHTMLに渡します var html = buildHTML(image) // 作成した.prev-contentをiconの代わりに表示させます $('.prev-contents').prepend(html); // 画像が表示されるのでiconを隠します $('.photo-icon').hide(); } else { // もし既に画像がプレビューされていれば画像データのみを入れ替えます $('.prev-content .prev-image').attr({ src: image }); } } }); }); });上記で出てくるDataURISchemeとは、
簡単にいうと、画像やらJavascriptやらそういったHTMLのコンテンツを文字列として定義出来るものです。
以上でで、画像が表示されます。終わり
最後まで見ていただきありがとうございました。
- 投稿日:2020-04-05T22:53:30+09:00
gem annotate の README を翻訳しました
概要
gem annotate の README を翻訳しました。
Annotate (aka AnnotateModels)
下記のファイルの上部あるいは下部に現在の DB スキーマを要約したコメントを追加します。
- ActiveRecord models
- Fixture files
- Tests and Specs
- Object Daddy exemplars
- Machinist blueprints
- Fabrication fabricators
- Thoughtbot's factory_bot factories, i.e. the
(spec|test)/factories/<model>_factory.rb
filesroutes.rb
file (for Rails projects)スキーマコメントは下記のように記載されます。
# == Schema Info # # Table name: line_items # # id :integer(11) not null, primary key # quantity :integer(11) not null # product_id :integer(11) not null # unit_price :float # order_id :integer(11) # class LineItem < ActiveRecord::Base belongs_to :product . . .また、
SpatialAdapter
,PostgisAdapter
,PostGISAdapter
のいずれかを使用したときは、geom
タイプやsrid
タイプのような幾何学的なカラムにも注釈をつけます。# == Schema Info # # Table name: trips # # local :geometry point, 4326 # path :geometry line_string, 4326また、
-r
オプションを渡すと、rake routes
の出力をroutes.rb
にコメントとして追加します。3.Xにアップグレードするとモデルへの注釈が動作しない?
バージョン 2.7.X では、引数が何も渡されない場合は、gem annotate はデフォルトでモデルに注釈を追加していました。
デフォルトでは、gem annotate は routes とモデルが一緒に注釈を追加されることを許可していません。
#647 で変更が追加されました。
詳細はこちらでご覧ください。この問題を修正する方法はいくつかあります。
- CLI を使用している場合は、
--models
を使用してモデルフラグを明示的に渡してください。あるいは
a)
rails g annotate:install
を実行し、デフォルト設定がmodels
オプション'true'
になるように上書きします。b)
lib/tasks/auto_annotate_models.rake
においてmodels
のキーと値を追加します。Annotate.set_defaults( ... 'models' => 'true', ...インストール
rubygems.org から 追加
group :development do gem 'annotate' endGithub から 追加
group :development do gem 'annotate', git: 'https://github.com/ctran/annotate_models.git' endrubygems.org からインストール
gem install annotateGithub のチェックアウトからインストール
git clone https://github.com/ctran/annotate_models.git annotate_models cd annotate_models rake build gem install pkg/annotate-*.gem利用方法
もし Gemfile 経由でインストールをしているなら、
bundle exec
を下記コマンドにつけてください。Rails での利用方法
すべてのモデル、テスト、フィクスチャ、ファクトリに注釈をつける。
cd /path/to/app annotateモデル、テスト、ファクトリのみに注釈をつける。
annotate --models --exclude fixturesモデルのみに注釈をつける。
annotate --modelsroutes.rb に注釈をつける。
annotate --routesモデル、テスト、フィクスチャ、ファクトリ、シリアライザの注釈を削除する。
annotate --deleteroutes.rb の注釈を削除する。
annotate --routes --delete
db:migrate
を実行するたびに自動的に注釈をつけるためには、
rails g annotate:install
を実行するか、Rakefile
にAnnotate.load_tasks
を追加してください。詳細は Rails における設定 をご覧ください。
Rails の外での利用方法
--routes
オプションが意味をなさないことを除けば Rails 外の利用でも上述のすべてが適用されます。明示的に1つあるいは複数の--require
オプションと--model-dir
オプションを設定することで、annotate
にプロジェクトの構造を知らせ、プロジェクトが起動し関連のあるコードを読み込むことを助ける必要があります。設定
もし特定のモデルで注釈を常に飛ばしたいときは、モデルファイルの任意の場所に下記の文字列を追加してください。
# -*- SkipSchemaAnnotationsRails における設定
設定ファイル(
.rake
ファイル形式)を生成して、デフォルトオプションを設定するためには下記のコマンドを実行してください。rails g annotate:installこのファイルを編集することで、注釈がファイルの上下どちらに追加されるかや、どのタイプのファイルに注釈が記載されるかといったような、出力形式などを制御します。
生成される rake ファイルである
lib/tasks/auto_annotate_models.rake
はAnnotate.load_tasks
も含みます。この rake ファイルはコマンドラインの機能と重複するいくつかの rake タスクを追加します。rake annotate_models # Add schema information (as comments) to model and fixture files rake annotate_routes # Adds the route map to routes.rb rake remove_annotation # Remove schema information from model and fixture filesデフォルトでは、一旦設定ファイルを生成したあとは、
rake db:migrate
が実行されるたびに annotate が実行されます(development 環境のみ)。
この挙動を永続的に無効にしたいときは、.rake
ファイルを編集し、下記のように変更してください。'skip_on_db_migrate' => 'false',上記の記述から下記の記述へ変更する。
'skip_on_db_migrate' => 'true',1回だけ annotate なしで
rake db:migrate
を実行したいときは、.rake
ファイルを編集する代わりにシンプルな環境変数をつけることでそれを実現できます。ANNOTATE_SKIP_ON_DB_MIGRATE=1 rake db:migrateオプション
--additional-file-patterns
コンマで区切った注釈を行う追加のファイルパスあるいは glob(例:
/foo/bar/%model_name%/*.rb,/baz/%model_name%.rb
)を記載する。
-d, --delete
すべてのモデルファイルあるいは routes.rb ファイルから注釈を削除する。
-p [before|top|after|bottom], --position
model/test/fixture/factory/route/serializer ファイルの上部(前)あるいは下部(後)に注釈を置く。
--pc, --position-in-class [before|top|after|bottom]
model ファイルの上部(前)あるいは下部(後)に注釈を置く。
--pf, --position-in-factory [before|top|after|bottom]
factory ファイルの上部(前)あるいは下部(後)に注釈を置く。
--px, --position-in-fixture [before|top|after|bottom]
fixture ファイルの上部(前)あるいは下部(後)に注釈を置く。
--pt, --position-in-test [before|top|after|bottom]
test ファイルの上部(前)あるいは下部(後)に注釈を置く。
--pr, --position-in-routes [before|top|after|bottom]
routes.rb ファイルの上部(前)あるいは下部(後)に注釈を置く。
--ps, --position-in-serializer [before|top|after|bottom]
serializer ファイルの上部(前)あるいは下部(後)に注釈を置く。
--w, --wrapper STR
パラメーターとして渡されたテキストで注釈をラップします。
--w が使用された場合は、同じテキストが始端と終端に使用されます。
--wo, --wrapper-open STR
注釈の始端をラップするテキスト。
--wc, --wrapper-close STR
注釈の終端をラップするテキスト。
-r, --routes
'rake routes' の出力で routes.rb に注釈を付ける。
--models
ActiveRecord モデルに注釈を付ける。
-a, --active-admin
active_admin モデルに注釈を付ける。
-v, --version
この gem の現在のバージョンを表示する。
-m, --show-migration
注釈にマイグレーションのバージョン番号を含める。
-k, --show-foreign-keys
注釈にテーブルの外部キー制約の一覧を載せる。
--ck, --complete-foreign-keys
注釈に完全な外部キーの名前を載せる。
-i, --show-indexes
注釈にテーブルのインデックスの一覧を載せる。
-s, --simple-indexes
注釈内のカラムの関連したインデックスを結合する。
--model-dir dir
app/models ではないディレクトリに保存されたモデルファイルに注釈を付ける。ディレクトリ名はカンマで区切る。
--root-dir dir
ルートディレクトリのプロジェクト内に保存されたファイルに注釈を付ける。ディレクトリ名はカンマで区切る。
--ignore-model-subdirects
モデルディレクトリのサブディレクトリを無視する。
--sort
作成順ではなくアルファベット順でカラムをソートする。
--classified-sort
アルファベット順にカラムをソートする。ただし、一番目には id 、その次が残りのカラム、タイムスタンプカラム、関連付けカラムの順となる。
-R, --require path
モデルを読み込む前に必要とする追加のファイル。このファイルは複数回使用される。
-e [tests,fixtures,factories,serializers], --exclude
tests,fixtures,factories,serializers ファイルに注釈を付けないようにする。
-f [bare|rdoc|yard|markdown], --format
プレインテキスト/RDoc/YARD/Markdown としてスキーマ情報を記述する。
--force
変更がない場合でも新しい注釈を強制的に記述する。
--frozen
注釈の変更を許可しない。ファイルに変更がある場合は、ゼロ以外の値で終了します。
--timestamp
注釈にタイムスタンプを含ませる。
--trace
ファイルに注釈を付けられない場合は、例外メッセージだけでなく、スタックトレース全体を表示する。
-I, --ignore-columns REGEX
与えられた正規表現に合致するカラムに注釈をつけないようにする(例:
annotate -I '^(id|updated_at|created_at)'
)。
--ignore-routes REGEX
与えられた正規表現に合致する routes に注釈をつけないようにする(例:
annotate -I '(mobile|resque|pghero)'
)。
--hide-limit-column-types VALUES
与えられたカラムタイプに limit 値を表示させない。カラムタイプはコンマで区切る(例:
integer,boolean,text
)。
--hide-default-column-types VALUES
与えられたカラムタイプに default 値を表示させない。カラムタイプはコンマで区切る(例:
json,jsonb,hstore
)。
--ignore-unknown-models
不正なモデルファイルに対して警告を表示させない。
--with-comment
モデルの注釈にデータベースコメントを含める。
オプション:
additional_file_patterns
CLI:
--additional-file-patterns
Ruby::additional_file_patterns
注釈を行うために追加のパスを提供します。このパスは glob を含むことができます。絶対パスを使用することを推奨します。下記に例を記載します。
/app/lib/decorates/%MODEL_NAME%/*.rb
/app/lib/forms/%PLURALIZED_MODEL_NAME%/**/*.rb
/app/lib/forms/%TABLE_NAME%/*.rb
適切なモデルは
%*%
構文を用いて推論され、一致するファイルに注釈が付けられます。これは既存のファイル名解決とともに動作します(annotate_models.rb
のresolve_filename
メソッドの中で発見されるオプション)。Rails の設定の中で使用するときは、下記を使用できます。
File.join(Rails.application.root,
'app/lib/forms/%PLURALIZED_MODEL_NAME%/***/**.rb')ソート
デフォルトでは、カラムはデータベース順でソートされます(つまりマイグレーションが行われた順番)。
もしアルファベット順にソートしてマイグレーションを実行した順番とは関係なく注釈の結果を一致させたいときは、
--sort
オプションを使用してください。マークダウン
生成されるフォーマットは実際には MultiMarkdown で、テーブルのための構文拡張機能を利用しています。もしこのフォーマットを使用したいときはパーサーとして
kramdown
を使用することを推奨しています。もしドキュメントを生成するためにyard
を使用している場合は、.yardopts
ファイルにkramdown
を追加することでプロバイダとしてkramdown
をマークダウンのフォーマットに指定してください。--markup markdown --markup-provider kramdown
Gemfile
にも同様に kramdown を追加するようにしてください。gem 'kramdown', groups => [:development], require => false警告
自動作成されたコメントブロックの後にテキストを追加しないでください。annotate によって以前にコメントブロックが追加された可能性があるとき、annotate はモデル内の始端・終端のコメントブロックを削除することがあります。
annotate が行った変更を必ず確認するようにしてください。Git を使用している場合は、
annotate
を実行した後にプロジェクトのステータスを確認することができます。$ git statusVCS(Git や Subversion など)を使っていない人は、特に注意して annotate を扱い、1つの VCS の利用を検討してください。
リンク集
- Factory Bot: http://github.com/thoughtbot/factory_bot
- Object Daddy: http://github.com/flogic/object_daddy
- Machinist: http://github.com/notahat/machinist
- Fabrication: http://github.com/paulelliott/fabrication
- SpatialAdapter: http://github.com/pdeffendol/spatial_adapter
- PostgisAdapter: http://github.com/nofxx/postgis_adapter
- PostGISAdapter: https://github.com/dazuma/activerecord-postgis-adapter
ライセンス
Ruby と同じライセンスでリリースしています。サポートと保証はありません。
作者
AUTHORS.md をご覧ください。
- 投稿日:2020-04-05T22:53:30+09:00
gem annotate のドキュメントを翻訳しました
概要
gem annotate の README を翻訳しました。
Annotate (aka AnnotateModels)
下記のファイルの上部あるいは下部に現在の DB スキーマを要約したコメントを追加します。
- ActiveRecord models
- Fixture files
- Tests and Specs
- Object Daddy exemplars
- Machinist blueprints
- Fabrication fabricators
- Thoughtbot's factory_bot factories, i.e. the
(spec|test)/factories/<model>_factory.rb
filesroutes.rb
file (for Rails projects)スキーマコメントは下記のように記載されます。
# == Schema Info # # Table name: line_items # # id :integer(11) not null, primary key # quantity :integer(11) not null # product_id :integer(11) not null # unit_price :float # order_id :integer(11) # class LineItem < ActiveRecord::Base belongs_to :product . . .また、
SpatialAdapter
,PostgisAdapter
,PostGISAdapter
のいずれかを使用したときは、geom
タイプやsrid
タイプのような幾何学的なカラムにも注釈をつけます。# == Schema Info # # Table name: trips # # local :geometry point, 4326 # path :geometry line_string, 4326また、
-r
オプションを渡すと、rake routes
の出力をroutes.rb
にコメントとして追加します。3.Xにアップグレードするとモデルへの注釈が動作しない?
バージョン 2.7.X では、引数が何も渡されない場合は、gem annotate はデフォルトでモデルに注釈を追加していました。
デフォルトでは、gem annotate は routes とモデルが一緒に注釈を追加されることを許可していません。
#647 で変更が追加されました。
詳細はこちらでご覧ください。この問題を修正する方法はいくつかあります。
- CLI を使用している場合は、
--models
を使用してモデルフラグを明示的に渡してください。あるいは
a)
rails g annotate:install
を実行し、デフォルト設定がmodels
オプション'true'
になるように上書きします。b)
lib/tasks/auto_annotate_models.rake
においてmodels
のキーと値を追加します。Annotate.set_defaults( ... 'models' => 'true', ...インストール
rubygems.org から 追加
group :development do gem 'annotate' endGithub から 追加
group :development do gem 'annotate', git: 'https://github.com/ctran/annotate_models.git' endrubygems.org からインストール
gem install annotateGithub のチェックアウトからインストール
git clone https://github.com/ctran/annotate_models.git annotate_models cd annotate_models rake build gem install pkg/annotate-*.gem利用方法
もし Gemfile 経由でインストールをしているなら、
bundle exec
を下記コマンドにつけてください。Rails での利用方法
すべてのモデル、テスト、フィクスチャ、ファクトリに注釈をつける。
cd /path/to/app annotateモデル、テスト、ファクトリのみに注釈をつける。
annotate --models --exclude fixturesモデルのみに注釈をつける。
annotate --modelsroutes.rb に注釈をつける。
annotate --routesモデル、テスト、フィクスチャ、ファクトリ、シリアライザの注釈を削除する。
annotate --deleteroutes.rb の注釈を削除する。
annotate --routes --delete
db:migrate
を実行するたびに自動的に注釈をつけるためには、
rails g annotate:install
を実行するか、Rakefile
にAnnotate.load_tasks
を追加してください。詳細は Rails における設定 をご覧ください。
Rails の外での利用方法
--routes
オプションが意味をなさないことを除けば Rails 外の利用でも上述のすべてが適用されます。明示的に1つあるいは複数の--require
オプションと--model-dir
オプションを設定することで、annotate
にプロジェクトの構造を知らせ、プロジェクトが起動し関連のあるコードを読み込むことを助ける必要があります。設定
もし特定のモデルで注釈を常に飛ばしたいときは、モデルファイルの任意の場所に下記の文字列を追加してください。
# -*- SkipSchemaAnnotationsRails における設定
設定ファイル(
.rake
ファイル形式)を生成して、デフォルトオプションを設定するためには下記のコマンドを実行してください。rails g annotate:installこのファイルを編集することで、注釈がファイルの上下どちらに追加されるかや、どのタイプのファイルに注釈が記載されるかといったような、出力形式などを制御します。
生成される rake ファイルである
lib/tasks/auto_annotate_models.rake
はAnnotate.load_tasks
も含みます。この rake ファイルはコマンドラインの機能と重複するいくつかの rake タスクを追加します。rake annotate_models # Add schema information (as comments) to model and fixture files rake annotate_routes # Adds the route map to routes.rb rake remove_annotation # Remove schema information from model and fixture filesデフォルトでは、一旦設定ファイルを生成したあとは、
rake db:migrate
が実行されるたびに annotate が実行されます(development 環境のみ)。
この挙動を永続的に無効にしたいときは、.rake
ファイルを編集し、下記のように変更してください。'skip_on_db_migrate' => 'false',上記の記述から下記の記述へ変更する。
'skip_on_db_migrate' => 'true',1回だけ annotate なしで
rake db:migrate
を実行したいときは、.rake
ファイルを編集する代わりにシンプルな環境変数をつけることでそれを実現できます。ANNOTATE_SKIP_ON_DB_MIGRATE=1 rake db:migrateオプション
--additional-file-patterns
コンマで区切った注釈を行う追加のファイルパスあるいは glob(例:
/foo/bar/%model_name%/*.rb,/baz/%model_name%.rb
)を記載する。
-d, --delete
すべてのモデルファイルあるいは routes.rb ファイルから注釈を削除する。
-p [before|top|after|bottom], --position
model/test/fixture/factory/route/serializer ファイルの上部(前)あるいは下部(後)に注釈を置く。
--pc, --position-in-class [before|top|after|bottom]
model ファイルの上部(前)あるいは下部(後)に注釈を置く。
--pf, --position-in-factory [before|top|after|bottom]
factory ファイルの上部(前)あるいは下部(後)に注釈を置く。
--px, --position-in-fixture [before|top|after|bottom]
fixture ファイルの上部(前)あるいは下部(後)に注釈を置く。
--pt, --position-in-test [before|top|after|bottom]
test ファイルの上部(前)あるいは下部(後)に注釈を置く。
--pr, --position-in-routes [before|top|after|bottom]
routes.rb ファイルの上部(前)あるいは下部(後)に注釈を置く。
--ps, --position-in-serializer [before|top|after|bottom]
serializer ファイルの上部(前)あるいは下部(後)に注釈を置く。
--w, --wrapper STR
パラメーターとして渡されたテキストで注釈をラップします。
--w が使用された場合は、同じテキストが始端と終端に使用されます。
--wo, --wrapper-open STR
注釈の始端をラップするテキスト。
--wc, --wrapper-close STR
注釈の終端をラップするテキスト。
-r, --routes
'rake routes' の出力で routes.rb に注釈を付ける。
--models
ActiveRecord モデルに注釈を付ける。
-a, --active-admin
active_admin モデルに注釈を付ける。
-v, --version
この gem の現在のバージョンを表示する。
-m, --show-migration
注釈にマイグレーションのバージョン番号を含める。
-k, --show-foreign-keys
注釈にテーブルの外部キー制約の一覧を載せる。
--ck, --complete-foreign-keys
注釈に完全な外部キーの名前を載せる。
-i, --show-indexes
注釈にテーブルのインデックスの一覧を載せる。
-s, --simple-indexes
注釈内のカラムの関連したインデックスを結合する。
--model-dir dir
app/models ではないディレクトリに保存されたモデルファイルに注釈を付ける。ディレクトリ名はカンマで区切る。
--root-dir dir
ルートディレクトリのプロジェクト内に保存されたファイルに注釈を付ける。ディレクトリ名はカンマで区切る。
--ignore-model-subdirects
モデルディレクトリのサブディレクトリを無視する。
--sort
作成順ではなくアルファベット順でカラムをソートする。
--classified-sort
アルファベット順にカラムをソートする。ただし、一番目には id 、その次が残りのカラム、タイムスタンプカラム、関連付けカラムの順となる。
-R, --require path
モデルを読み込む前に必要とする追加のファイル。このファイルは複数回使用される。
-e [tests,fixtures,factories,serializers], --exclude
tests,fixtures,factories,serializers ファイルに注釈を付けないようにする。
-f [bare|rdoc|yard|markdown], --format
プレインテキスト/RDoc/YARD/Markdown としてスキーマ情報を記述する。
--force
変更がない場合でも新しい注釈を強制的に記述する。
--frozen
注釈の変更を許可しない。ファイルに変更がある場合は、ゼロ以外の値で終了します。
--timestamp
注釈にタイムスタンプを含ませる。
--trace
ファイルに注釈を付けられない場合は、例外メッセージだけでなく、スタックトレース全体を表示する。
-I, --ignore-columns REGEX
与えられた正規表現に合致するカラムに注釈をつけないようにする(例:
annotate -I '^(id|updated_at|created_at)'
)。
--ignore-routes REGEX
与えられた正規表現に合致する routes に注釈をつけないようにする(例:
annotate -I '(mobile|resque|pghero)'
)。
--hide-limit-column-types VALUES
与えられたカラムタイプに limit 値を表示させない。カラムタイプはコンマで区切る(例:
integer,boolean,text
)。
--hide-default-column-types VALUES
与えられたカラムタイプに default 値を表示させない。カラムタイプはコンマで区切る(例:
json,jsonb,hstore
)。
--ignore-unknown-models
不正なモデルファイルに対して警告を表示させない。
--with-comment
モデルの注釈にデータベースコメントを含める。
オプション:
additional_file_patterns
CLI:
--additional-file-patterns
Ruby::additional_file_patterns
注釈を行うために追加のパスを提供します。このパスは glob を含むことができます。絶対パスを使用することを推奨します。下記に例を記載します。
/app/lib/decorates/%MODEL_NAME%/*.rb
/app/lib/forms/%PLURALIZED_MODEL_NAME%/**/*.rb
/app/lib/forms/%TABLE_NAME%/*.rb
適切なモデルは
%*%
構文を用いて推論され、一致するファイルに注釈が付けられます。これは既存のファイル名解決とともに動作します(annotate_models.rb
のresolve_filename
メソッドの中で発見されるオプション)。Rails の設定の中で使用するときは、下記を使用できます。
File.join(Rails.application.root,
'app/lib/forms/%PLURALIZED_MODEL_NAME%/***/**.rb')ソート
デフォルトでは、カラムはデータベース順でソートされます(つまりマイグレーションが行われた順番)。
もしアルファベット順にソートしてマイグレーションを実行した順番とは関係なく注釈の結果を一致させたいときは、
--sort
オプションを使用してください。マークダウン
生成されるフォーマットは実際には MultiMarkdown で、テーブルのための構文拡張機能を利用しています。もしこのフォーマットを使用したいときはパーサーとして
kramdown
を使用することを推奨しています。もしドキュメントを生成するためにyard
を使用している場合は、.yardopts
ファイルにkramdown
を追加することでプロバイダとしてkramdown
をマークダウンのフォーマットに指定してください。--markup markdown --markup-provider kramdown
Gemfile
にも同様に kramdown を追加するようにしてください。gem 'kramdown', groups => [:development], require => false警告
自動作成されたコメントブロックの後にテキストを追加しないでください。annotate によって以前にコメントブロックが追加された可能性があるとき、annotate はモデル内の始端・終端のコメントブロックを削除することがあります。
annotate が行った変更を必ず確認するようにしてください。Git を使用している場合は、
annotate
を実行した後にプロジェクトのステータスを確認することができます。$ git statusVCS(Git や Subversion など)を使っていない人は、特に注意して annotate を扱い、1つの VCS の利用を検討してください。
リンク集
- Factory Bot: http://github.com/thoughtbot/factory_bot
- Object Daddy: http://github.com/flogic/object_daddy
- Machinist: http://github.com/notahat/machinist
- Fabrication: http://github.com/paulelliott/fabrication
- SpatialAdapter: http://github.com/pdeffendol/spatial_adapter
- PostgisAdapter: http://github.com/nofxx/postgis_adapter
- PostGISAdapter: https://github.com/dazuma/activerecord-postgis-adapter
ライセンス
Ruby と同じライセンスでリリースしています。サポートと保証はありません。
作者
AUTHORS.md をご覧ください。
- 投稿日:2020-04-05T22:09:30+09:00
undefined method 'page' for # <Array:0x000........>が出たときの対処法
はじめに
gem kaminariについて、新しい発見があったので、記録として残す。
今回のエラー
controller.rbインスタンス変数 = オブジェクト.page(:params[:page]).per(10)index.html.haml= paginate インスタンス変数と定義したところ、エラーが出た。
エラー文
undefined method 'page' for # <Array:0x000........>なぜ???
実は、、、
私の解釈では、、
controller.rbインスタンス変数 = オブジェクト.page(:params[:page]).per(10)のつもりだったのが、
controller.rbインスタンス変数 = 配列.page(:params[:page]).per(10)の間違いだった。
解決策
kaminariの公式ページを読めばわかるのだが、
通常.pageメソッドはActive Recordのオブジェクトにしか適用されないらしい。
今回はそうではなく、配列(Array)だったのでエラーが出ていたようだ。
そこでkaminariでは、配列にも.pageメソッドを使用できるやり方がある。
controller.rbインスタンス変数 = Kaminari.paginate_array(配列).page(params[:page]).per(10)これで解決した。
おわりに
あまり深く理解せず使用していたジェムがたくさんあるが、こういうエラーがきっかけで、
改めて公式レファレンスを読むことの重要性を感じた。公式レファレンスを読み進めていくと、新しい発見もたくさんあるので非常に楽しいので、皆さんもいろんなgemの公式レファレンスを読んでみてほしい。
参考記事
公式レファレンス
https://github.com/kaminari/kaminari/blob/master/README.mdkaminariでundefined method `page' for #<Array:0x000xxxxxxと出た
https://haayaaa.hatenablog.com/entry/2019/03/11/215042
- 投稿日:2020-04-05T22:05:26+09:00
rails newするとCould not load command "rails/commands/server/server_command". Error: uninitialized constant URI::Generic.と表示される時の対処法
Vagrantを使って環境構築をしている際、エラーに遭遇したので記録を残しておきます。
構築環境
Ruby 2.5.0
Ruby on rails 5.1.7
Vagrant 2.2.7
Virtual Box 6.1.4
rbenv 1.1.2発生したエラー
$ rails new #省略 [WARNING] Could not load command "rails/commands/server/server_command". Error: uninitialized constant URI::Generic.解決した方法
Rubyのバージョンを2.5.7に上げるとこのエラーは解消されました。
#バージョン2.5.7のRubyをインストール $ rbenv install 2.5.7 #省略 #使用する全体のrubyのバージョンを指定 $rbenv global 2.5.7 #Rubyのバージョンを確認 $ruby -v ruby 2.5.7p206 (2019-10-01 revision 67816) [x86_64-linux] $rails new #うまくいきましたhttps://stackoverflow.com/questions/59961343/failing-to-start-up-default-rails-server
この質問を参考にしてバージョンを変えてみたんですが、よく読むと2.5.7でも同じ現象が起こったって書かれてますね...。原因が分かる方は教えていただきたいです。
- 投稿日:2020-04-05T21:36:25+09:00
アカウントの有効化〜Railsチュートリアル11章〜
いよいよRailsチュートリアル11章に入っていきます。11章の体感としてはProgateの内容でも少し触った内容であったので割とスムーズにすすめることができました。
それではまとめていきます。アカウントの有効化
現時点でのApplicationは新規登録したユーザーははじめからすべての機能にアクセスできるようになっている。
ここではアカウントを有効化するステップを新規登録の途中に差し込むことで本当にそのメールアドレスの持ち主なのかどうかを確認できるようにする。
大まかな流れ
(1)有効化トークンやダイジェストを関連付けておいた状態で
(2)有効化トークンを含めたリンクをユーザーにメールで送信し
(3)ユーザーがそのリンクをクリックすると有効化できるようにする
というものである。基本的な手順
1.ユーザーの初期状態は「有効化されていない」にしておく
2.ユーザー登録が行われたときに有効化トークンとそれに対応する有効化ダイジェストを生成する。
3.有効化ダイジェストはデータベースに保存しておき、有効化トークンはメールアドレスと一緒にユーザーに送信する有効化用メールのリンクに仕込んでおく
4.ユーザーがメールのリンクをクリックしたらApplicationはメールアドレスをキーにしてユーザーを探し、データベース内に保存しておいた有効化ダイジェストと比較することでトークンを認証する。
5.ユーザーが認証できたら、ユーザーのステータスを「有効化されていない」から「有効化済み」に変更する。AccountActivationsリソース
Session機能を使ってアカウントの有効化という作業を「リソース」としてモデル化する。アカウントの有効化リソースはActive Recordのモデルとは関係ないので両者を関連付けることはしない。その代わりにこの作業に必要なデータ(有効化トークンや有効化ステータス)をUserモデルに追加する。
普段のリソースとは異なる点
有効化用のリンクにアクセスして有効化のステータスを変更する部分では、RESTのルールに従うとPATCHリクエストとUpdateアクションになるべきである。しかし有効化リンクはメールでユーザーに送られる。ユーザーがこのリンクをクリックすればそれはブラウザで普通にクリックしたときと同じであり、その場合ブラウザから発行されるのはGETリクエストになってしまう。このためユーザーからのGETリクエストを受けるためにEDITアクションに変更して使っていく。アカウント有効化に使うリソースを追加する
Rails.application.routes.draw do resources :account_activations, only: [:edit] ↑ #有効化のメールにedit_account_activation_url (activation_token, ...) を使用したいのでeditアクションへ名前付きルートが必要になる。 そこで上記のResourcesを追加する。AccountActivationのデータモデル
有効化のメールには一意の有効化トークンが必要であるも送信メールとデータベースのそれぞれに同じ文字列をおいておく方法だと情報漏えいが起こった際多大な被害につながる。
そこでデータベースに仮想的な属性をつけてハッシュ化した文字列をデータベースに保存するようにする。具体的には仮想属性の有効化トークンにアクセスし、user.activation_tokenでユーザーを認識できるようにする。$ rails generate migration add_activation_to_users \ > activation_digest:string activated:boolean activated_at:datetime ↑をデータベースにmigrateする。アクティブトークンのコールバック
ユーザーが新しい登録を完了するためには必ずアカウントの有効化が必要になるので有効化トークンや有効化ダイジェストはユーザーオブジェクトが作成される前に作成しておく必要がある。
そこでbefore_createコールバックが必要となる。before_create :create_activation_digest上のコードはメソッド参照と呼ばれるもので、こうするとRailsはcreate_activation_digestというメソッドを探し、ユーザーを作成する前に実行するようになる。create_activation_digestメソッド自体はUserモデル内でしか使わないので、外部に公開する必要はない。
よってprivateキーワードを指定してこのメソッドを隠蔽する。self.activation_token = User.new_token self.activation_digest = User.digest(activation_token)このコードを9章で作成した永続Sessionのためのユーザー登録した時と比べる。
9章では記憶トークンとダイジェストはすでにデータベースにいるユーザーのために作成されるのに対し、before_createコールバックはユーザーが作成される前に呼び出されることなので更新される属性がまだない。
このコールバックがあることでUser.newで新しいユーザーが定義されるとactivation_token属性やactivation_digest属性が得られるようになる。class User < ApplicationRecord attr_accessor :remember_token, :activation_token before_save :downcase_email before_create :create_activation_digest validates :name, presence: true, length: { maximum: 50 } . . . private # メールアドレスをすべて小文字にする def downcase_email self.email = email.downcase end # 有効化トークンとダイジェストを作成および代入する def create_activation_digest self.activation_token = User.new_token self.activation_digest = User.digest(activation_token) end endアカウント有効化のメール送信
データのモデル化が終わったのでアカウント有効化メールの送信に必要なコードを追加する。このメソッドではActionMailerライブラリを使ってUserのメイラーを追加する。メイラーは、モデルやコントローラと同様にrails generateで生成できる。メイラーの構成はコントローラのアクションとよく似ている。テンプレートはビューと同じようなもの。このテンプレートの中に有効化トークンとメールアドレス (= 有効にするアカウントのアドレス) のリンクを含め、使っていく。
Userメイラーで
account_activationメソッドと、第12章で必要となるpassword_resetメソッドを生成する。
生成したメイラーごとにビューのテンプレートが2つずつ生成される。
1つはテキストメール用のテンプレート
1つはHTML用のテンプレートである。生成されたApplicationメイラーにはデフォルトのformアドレスがある。
最初に生成されたテンプレートをカスタマイズして実際に有効化メールで使えるようにする。
app/mailers/application_mailer.rb class ApplicationMailer < ActionMailer::Base default from: "noreply@example.com" layout 'mailer' end次にユーザーを含むインスタンス変数を作成してビューで使えるようにし、user.emailにメール送信を行う。subjectキーはmailの件名に当たる。
class UserMailer < ApplicationMailer def account_activation(user) @user = user mail to: user.email, subject: "Account activation" endedit_account_activation_url(@user.activation_token, ...)
ここで思い出してみる。edit_user_url(user)
上のメソッドは、次の形式のURLを生成します。http://www.example.com/users/1/editこれに対応するアカウント有効化リンクのベースURLは次のようになります。
http://www.example.com/account_activations/q5lt38hQDc_959PVoo6b7A/editクエリパラメータを使って、このURLにメールアドレスもうまく組み込んでみましょう。クエリパラメータとは、URLの末尾で疑問符「?」に続けてキーと値のペアを記述したものです。
account_activations/q5lt38hQDc_959PVoo6b7A/edit?email=foo%40example.comこのとき、メールアドレスの「@」記号がURLでは「%40」となっている点に注目してください。これは「エスケープ」と呼ばれる手法で、通常URLでは扱えない文字を扱えるようにするために変換されています。Railsでクエリパラメータを設定するには、名前付きルートに対して次のようなハッシュを追加します。
edit_account_activation_url(@user.activation_token, email: @user.email)送信メールのプレビュー
Railsでは、特殊なURLにアクセスするとメールのメッセージをその場でプレビューすることができます。
development環境のメール設定 config/environments/development.rb Rails.application.configure do . config.action_mailer.raise_delivery_errors = true config.action_mailer.delivery_method = :test host = 'example.com' # 自分のクラウドIDEのリンクを貼る config.action_mailer.default_url_options = { host: host, protocol: 'https' } . end# Preview all emails at http://localhost:3000/rails/mailers/user_mailer class UserMailerPreview < ActionMailer::Preview # Preview this email at # http://localhost:3000/rails/mailers/user_mailer/account_activation def account_activation user = User.first user.activation_token = User.new_token UserMailer.account_activation(user) end # Preview this email at # http://localhost:3000/rails/mailers/user_mailer/password_reset def password_reset UserMailer.password_reset end endメールtestの実装
test/mailers/user_mailer_test.rb require 'test_helper' class UserMailerTest < ActionMailer::TestCase test "account_activation" do user = users(:michael) user.activation_token = User.new_token mail = UserMailer.account_activation(user) assert_equal "Account activation", mail.subject assert_equal [user.email], mail.to assert_equal ["noreply@example.com"], mail.from assert_match user.name, mail.body.encoded assert_match user.activation_token, mail.body.encoded assert_match CGI.escape(user.email), mail.body.encoded end endアカウントの有効化
AccountActivationsコントローラのeditアクションを書いていく。
authenticated?メソッドの抽象化
app/models/user.rb
class User < ApplicationRecord . . . # トークンがダイジェストと一致したらtrueを返す def authenticated?(attribute, token) digest = send("#{attribute}_digest") return false if digest.nil? BCrypt::Password.new(digest).is_password?(token) end . . . endmodule SessionsHelper . . . # 現在ログイン中のユーザーを返す (いる場合) def current_user if (user_id = session[:user_id]) @current_user ||= User.find_by(id: user_id) elsif (user_id = cookies.signed[:user_id]) user = User.find_by(id: user_id) if user && user.authenticated?(:remember, cookies[:remember_token]) log_in user @current_user = user end end end``アカウントを有効化するeditアクション
app/controllers/account_activations_controller.rb class AccountActivationsController < ApplicationController def edit user = User.find_by(email: params[:email]) if user && !user.activated? && user.authenticated?(:activation, params[:id]) user.update_attribute(:activated, true) user.update_attribute(:activated_at, Time.zone.now) log_in user flash[:success] = "Account activated!" redirect_to user else flash[:danger] = "Invalid activation link" redirect_to root_url end end end本日はここまで
- 投稿日:2020-04-05T17:19:22+09:00
[Rails][data-vocabulary.org スキーマのサポートは終了します。]gem Gretelをschema.orgに対応させる方法。
data-vocabulary.org スキーマのサポートは終了します。
2020年4月6日からGoogleで「data-vocabulary.org」を利用した構造化データがリッチリザルトとしてサポートされなくなるので、引き続きリッチリザルトを利用したい場合は「schema.org」を利用した構造化データに移行する必要があります。
パンくずリスト | Google 検索デベロッパー ガイド | Google Developers
https://developers.google.com/search/docs/data-types/breadcrumb?hl=jaGem Gretelとは
Gretel( https://rubygems.org/gems/gretel/versions/3.0.7 )は設定ファイルを書くことで、簡単にパンクズリストを出力することができるようになるGemです。
パンクズの出力のオプションでsemantic: true
をつけることによりリッチリザルトにも対応していました(data-vocabulary.org)。しかし、このGemはメンテナンスが長いこと止まっており、Googleの「schema.org」を利用するにはパッチを当てる必要がります。
自分で検索した所同じような対応をしている人が散見されたので、僕の方法を示して行こうと思います。※ 本来は別のGemなどに移行することをオススメしますが、昔から使っている人がお手軽に対応できる方法を本記事にしています。
モンキーパッチを当てる
Gretel Gemを使っているRailsプロジェクトで
config/initializers/gretel.rb
を作成してruby# frozen_string_literal: true module Gretel module ViewHelpers delegate :breadcrumbs_json_ld, to: :gretel_renderer end class Renderer # rubocop:disable Rails/OutputSafety def breadcrumbs_json_ld { "@context": 'http://schema.org/', "@type": 'BreadcrumbList', "itemListElement": links.map.with_index do |link, i| { '@type': 'ListItem', 'position': i + 1, 'item': { '@id': "#{root_url.chop}#{link}", 'name': link.text } } end }.to_json.html_safe end # rubocop:enable Rails/OutputSafety end endYamitake gist gretel.rb https://gist.github.com/yamitake/3659b9d87404ad975f8a881b05971e33
使い方
breadcrumbs_json_ld
を宣言したので、viewファイル側で下記のように宣言することにより、schema.orgに対応したjsonLDが出力されます。.breadcrumbs == breadcrumbs = tag.script(breadcrumbs_json_ld, type: 'application/ld+json')おわりに
今までGretelで開発してきた人が暫定対応としては上記の方法でお手軽に対応できますが、gretelはメンテナンスが長いことされていないので別のGemを探すか自前でパンクズの実装をした方がいいと思います。
- 投稿日:2020-04-05T17:07:44+09:00
Ruby で Google Spreadsheets を機械が読み書き
TL; DR
- Google Spreadsheets の表を Ruby プログラムを使って無人操作する方法を書きました。
- ほぼ 先日書いた Python の記事 の Ruby 版です。
- 公式 Quick Start は人間が介在する OAuth2 のやり方だけど、ようはこれの無人版です。
- サービスアカウントというものを作って、それに必要な権限を与え、そのアカウントの秘密鍵を使ってアクセスします。
スプレッドシートの準備
これは何も特別なことはありません。Google Drive に適当なシートを作成して、ファイルの ID だけ控えておいてください。
プロジェクトとサービスアカウント
各種ドキュメントを操作するには当然権限が必要です。プログラムから操作する場合は、大雑把に言って2とおりの権限獲得の手法があります。
- 一時的に人間の許可を得て、その人間のアカウントで操作
- 権限を与えられた 機械ようのアカウント で操作
ざっくり言うと前者はインタラクティヴなソフトで使う方法で、後者は自動化システムで使う方法です。今回は後者の方法を使います。ここで「機械ようのアカウント」を サービスアカウントと言います。つまり、まずはサービスアカウントを作り、必要な権限を与える必要がります。だいたい以下の手順です。
- GCPのコンソール に行って、プロジェクトを作ります。
- GCPの左上のハンバーガーメニュー > IAMと管理 > サービスアカウント > サービスアカウントを作成
- 入力は必須項目だけで良いと思います。
name@project.iam.gserviceaccount.com
というアカウントができます。- アカウントを作ると秘密鍵 (private key) をダウンロードできると思います。JSON形式でダウンロードしてください。
- このアカウントに対して、操作したいファイル(スプレッドシート)の操作権限を与えてください(=共有してください)。与え方は通常の人間向けの権限操作といっしょです。
- ハンバーガーメニュー > APIとサービス > ダッシュボード > +APIとサービスを有効化 と進み、
Google Sheets API
を有効化してください。パッケージのインストール
たぶんこれだけでいいはず。
$ gem install google-api-clientコード
シートの 1 行目を読み出し、最終行に
don't
,panic
,42
と追記するコードです。require "google/apis/sheets_v4" Sheet_id = "*******" # スプレッドシートの(ファイルの)ID service = Google::Apis::SheetsV4::SheetsService.new service.authorization = Google::Auth::ServiceAccountCredentials.make_creds( json_key_io: File.open("ダウンロードした秘密鍵.json"), scope: Google::Apis::SheetsV4::AUTH_SPREADSHEETS ) # データの読み出し例 resp = service.get_spreadsheet_values(Sheet_id, "シート1!a1:z1") p resp.values # 行の追記例 service.append_spreadsheet_value( Sheet_id, "シート1!a:c", {"values": [["don't", "panic", 42]]}, value_input_option: "RAW" # 式を入れたいときは "USER_ENTERED" )終わりに
append_spreadsheet_value
はIoT機器ぽいものでログ等をどんどん追記する用途には便利だと思います。
- 投稿日:2020-04-05T15:08:35+09:00
Ruby と Perl で解くAtCoder ABC 161 D 幅優先探索
はじめに
AtCoder の AtCoder Beginner Contest 161 の D問題が解けなかったので、復習を兼ねて投稿しました。
お礼
オフィシャルの解説やネット上の解説・解答を参照して理解を進めております。
AtCoder さん、競技プロプレイヤーさん、ありがとうございます。幅優先探索
幅優先探索といえば、次のようなマップ・迷路問題で使用されます。
#...#.# ..#...# .#..#..所謂、ある地点での上下左右において、. だったらキューに追加、# だったらキューに追加しない、というふうな解法を行います。
今回の D問題はそれを応用させ、末尾の数字 (仮に 3とします) のルンルン数 (2, 3, 4) をキューに追加することにより解いていきます。
また、マップ・迷路問題ではゴールにたどり着いた時点もしくは、全マップを探索した時点でループを終了しますが、今回は K番目に到達した時点でループを終了します。
Ruby
AtCoder Beginner Contest D - Lunlun Number
Ruby.rbk = gets.chomp.to_i cnt = 0 que = [] (1..9).each do |i| que.push(i) cnt += 1 if cnt == k puts i exit end end while que.size > 0 do n = que.shift if n % 10 == 0 cnt += 1 if cnt == k puts n * 10 exit end que.push(n * 10) cnt += 1 if cnt == k puts n * 10 + 1 exit end que.push(n * 10 + 1) end if n % 10 != 0 && n % 10 != 9 cnt += 1 if cnt == k puts n * 10 + n % 10 - 1 exit end que.push(n * 10 + n % 10 - 1) cnt += 1 if cnt == k puts n * 10 + n % 10 exit end que.push(n * 10 + n % 10) cnt += 1 if cnt == k puts n * 10 + n % 10 + 1 exit end que.push(n * 10 + n % 10 + 1) end if n % 10 == 9 cnt += 1 if cnt == k puts n * 10 + 8 exit end que.push(n * 10 + 8) cnt += 1 if cnt == k puts n * 10 + 9 exit end que.push(n * 10 + 9) end endキューに push して while で回して shift で取り出す要領で、幅優先探索を実行します。
Perl
Perl.pluse v5.18; # strict say state use warnings; use List::Util qw(reduce first max min sum0); chomp (my $k = <STDIN>); my @que; my $cnt; for my $i (1..9) { push @que, $i; $cnt++; if ($cnt == $k) { say $i; exit; } } while (@que) { my $i = shift @que; if ($i % 10 == 0) { $cnt++; if ($cnt == $k) { say $i.'0'; exit; } push @que, $i.'0'; $cnt++; if ($cnt == $k) { say $i.'1'; exit; } push @que, $i.'1'; } elsif ($i % 10 != 0 && $i % 10 != 9) { $cnt++; if ($cnt == $k) { say $i.($i % 10 - 1); exit; } push @que, $i.($i % 10 - 1); $cnt++; if ($cnt == $k) { say $i.($i % 10); exit; } push @que, $i.($i % 10); $cnt++; if ($cnt == $k) { say $i.($i % 10 + 1); exit; } push @que, $i.($i % 10 + 1); } else { $cnt++; if ($cnt == $k) { say $i.'8'; exit; } push @que, $i.'8'; $cnt++; if ($cnt == $k) { say $i.'9'; exit; } push @que, $i.'9'; } }Perl の方は、数字を文字としても扱う様なコーディングにしています。
Perl(文字) Perl(数字) Ruby 実行時間 56 ms 42 ms 25 ms ここでは、Ruby が速い結果となりました。
まとめ
- ABC 161 D を解いた
- 幅優先探索の応用ができるようになった
参照したサイト
ABC 161 解説
D言語で解く AtCoder Beginner Contest 161 (A〜D)
shift, unshift, pop, pushまとめ
- 投稿日:2020-04-05T10:18:51+09:00
Twitterユーザーの性別を機械学習で予測する
性別がタグ付けされたTwitterユーザー 2万人分のデータがあったので、このデータを使ってTwitterユーザーの性別予測を行ってみた。テキスト処理にはRuby、機械学習にはPythonを使っている。
先に結論
Twitterのプロフィールを用いた単純な機械学習による性別予測は、約60%の精度しかでなかった。
今回用いたデータは外国語のデータであり、日本語のプロフィールだと違った結果になるが、精度は同じようにあまりでないと思われる。こう思う理由は、「Twitterユーザーのデータは、そもそも人が見ても性別の判定が難しい」ため。
Twitterユーザー性別判定の手順
手順1〜5にはRuby、手順6にはPythonを使っている。
- プロフィールに含まれる単語をリストアップする
- 各単語の出現回数を記録する
- 極端に出現回数が少ない、もしくは多すぎる単語は除去する
- 単語数を次元数とみなしてユーザーのプロフィールをベクトルで表現する
- 正解データのラベルを作成する
- 機械学習を適用する
Rubyでテキストの事前処理を行う
前述の手順1〜5を行うRubyコードは下記の通り。今回のような手法だと、このテキスト処理の部分に性能が大きく左右される。やり方は無数にあり、このコードでは本当に最小限のテキスト処理しか行っていない。
# https://www.kaggle.com/crowdflower/twitter-user-gender-classification def parse_kaggle_data str = File.read('gender-classifier-DFE-791531.csv', encoding: 'ISO-8859-1:UTF-8') lines = str.split("\r").map { |l| l.split(',') } header = lines[0] users = lines.drop(1).map { |l| header.map.with_index { |h, i| [h, l[i]] }.to_h } users = users.select { |u| %w(female male).include?(u['gender']) && u['gender:confidence'] == '1' } [users.map { |u| u['description'] }, users.map { |u| u['gender'] }] end def split_to_words(text_array) text_array.map { |d| d.split(/([\s"]|__REP__)/) }.flatten. map { |w| w.gsub(/^#/, '') }. map { |w| w.gsub(/[^.]\.+$/, '') }. map { |w| w.gsub(/[^!]!+$/, '') }. map { |w| w.gsub(/^\(/, '') }. map { |w| w.gsub(/^\)/, '') }. delete_if { |w| w.length < 2 }. map(&:downcase).sort.uniq end def count_words(text_array, word_array) words_count = Hash.new(0) text_array.each do |d| word_array.each do |w| if d.include?(w) words_count[w] += 1 end end end words_count end descriptions, genders = parse_kaggle_data desc_words = split_to_words(descriptions) desc_words_count = count_words(descriptions, desc_words) filtered_desc_words = desc_words.select { |w| desc_words_count[w] > 2 && desc_words_count[w] < 500 } desc_vectors = descriptions.map { |d| filtered_desc_words.map { |w| d.include?(w) ? 1 : 0 } } File.write('data/description_vectors.txt', desc_vectors.map { |v| v.join(' ') }.join("\n")) labels = genders.map do |g| case g when ''; 0 when 'brand'; 1 when 'female'; 2 when 'male'; 3 when 'unknown'; 4 end end File.write('data/labels.txt', labels.join("\n"))Pythonで機械学習を行う
ナイーブベイズ、ロジスティック回帰、ランダムフォレスト、サポートベクターマシンを試した結果、どれも似たような結果になっている。
手法 精度 ナイーブベイズ(正規分布) 0.5493 ナイーブベイズ(ベルヌーイ) 0.6367 ロジスティック回帰 0.6151 ランダムフォレスト 0.6339 サポートベクターマシン 0.6303 それぞれの手法には元データに対する暗黙の仮定があるが、今回はそれは考慮せず単純に結果を比較している点に注意が必要。
# sudo yum install -y python3 # sudo pip3 install -U pip numpy sklearn ipython import numpy as np from sklearn.naive_bayes import GaussianNB from sklearn.naive_bayes import BernoulliNB from sklearn.linear_model import LogisticRegression from sklearn.ensemble import RandomForestClassifier from sklearn.svm import SVC from sklearn.model_selection import GridSearchCV from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report, accuracy_score from sklearn.metrics import confusion_matrix import pickle description_vectors = np.loadtxt('data/description_vectors.txt') labels = np.loadtxt('data/labels.txt') (x_train, x_test, y_train, y_test) = train_test_split(description_vectors, labels) clf = GaussianNB().fit(x_train, y_train) clf = BernoulliNB().fit(x_train, y_train) clf = LogisticRegression().fit(x_train, y_train) clf = RandomForestClassifier().fit(x_train, y_train) clf = SVC(C = 1.0).fit(x_train, y_train) y_pred = clf.predict(x_test) np.mean(y_test == y_pred) # Grid search # best params: {'C': 1.0, 'gamma': 'scale', 'kernel': 'rbf'} parameters = [{'kernel': ['linear', 'rbf', 'poly', 'sigmoid'], 'C': np.logspace(-2, 2, 5), 'gamma': ['scale']}] clf = GridSearchCV(SVC(), parameters, verbose = True, n_jobs = -1) clf.fit(x_train, y_train) # best params: {'max_depth': 100, 'n_estimators': 300} parameters = [{'n_estimators': [30, 50, 100, 300], 'max_depth': [25, 30, 40, 50, 100]}] clf = GridSearchCV(RandomForestClassifier(), parameters, verbose = True, n_jobs = -1) clf.fit(x_train, y_train) print(clf.best_params_) print(clf.best_score_) print(clf.best_estimator_) print(classification_report(y_test, y_pred)) print(accuracy_score(y_test, y_pred)) print(confusion_matrix(y_test, y_pred)) # Model persistence pickle.dump(clf, open('model.sav', 'wb')) clf = pickle.load(open('model.sav', 'rb'))関連リンク
Twitter User Gender Classification | Kaggle
Using machine learning to predict gender
- 投稿日:2020-04-05T10:16:13+09:00
ユーザーの更新・表示・削除機能を追加します〜Railsチュートリアル10章〜
この章ではユーザー機能を充実させてRESTアクションを完成させます。
この章で行うことは
プロフィールの更新、認可モデルの実装。ユーザー一覧の追加。ユーザーの削除機能を追加していきます。ユーザーを更新する
ユーザー情報を編集するパターンは新規ユーザーの作成と似通っている。
ユーザーを編集するためのeditアクションを作成する。
patchリクエストに応答するUpdateアクションを作成する。
またユーザー情報を更新できるのはそのユーザー自身だけであるよう設定する。まずeditに対応するアクションとビューを追加する。
アクション
def edit @user = User.find(params[:id]) endビュー
<% provide(:title, "Edit user") %> <h1>Update your profile</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= form_for(@user) do |f| %> <%= render 'shared/error_messages' %> <%= f.label :name %> <%= f.text_field :name, class: 'form-control' %> <%= f.label :email %> <%= f.email_field :email, class: 'form-control' %> <%= f.label :password %> <%= f.password_field :password, class: 'form-control' %> <%= f.label :password_confirmation, "Confirmation" %> <%= f.password_field :password_confirmation, class: 'form-control' %> <%= f.submit "Save changes", class: "btn btn-primary" %> <% end %> <div class="gravatar_edit"> <%= gravatar_for @user %> <a href="http://gravatar.com/emails" target="_blank">change</a> </div> </div> </div>WebブラウザはネイティブではPATCHリクエスト (RESTの慣習として要求されている) を送信できないので、RailsはPOSTリクエストと隠しinputフィールドを利用してPATCHリクエストを「偽造」しています
ユーザーのeditビューで使われているtarget="_blank"ですが、これを使うとリンク先を新しいタブ(またはウィンドウ)で開くようになるので、別の Webサイトへリンクするときなどに便利です。target="_blank"で新しいページを開くと、フィッシングサイトのような、悪意のあるコンテンツを導入させられてしまう可能性があります。対処方法は、リンク用のaタグのrel(relationship)属性に、"noopener"と設定します。
編集の失敗
編集に失敗した場合について扱う。まずupdateアクションの作成から始める。update_attributesを使って送信されたparamsハッシュに基いてユーザーを更新します。無効な情報が送信された場合、更新の結果としてfalseが返され、elseに分岐して編集ページをレンダリングします。
def create @user = User.new(user_params) if @user.save log_in @user flash[:success] = "Welcome to the Sample App!" redirect_to @user else render 'new' end end def edit @user = User.find(params[:id]) end def update @user = User.find(params[:id]) if @user.update_attributes(user_params) # 更新に成功した場合を扱う。 else render 'edit' end end編集失敗のtest
require 'test_helper' class UsersEditTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) end test "unsuccessful edit" do get edit_user_path(@user) assert_template 'users/edit' patch user_path(@user), params: { user: { name: "", email: "foo@invalid", password: "foo", password_confirmation: "bar" } } assert_template 'users/edit' end endtest内容
1 そのユーザーにとっての編集画面を開ける
2 user/editのビューが表示される
3 編集した内容(無効な情報)をpatchリクエストとして送る
4 user/editのビューが表示される編集成功のtest
require 'test_helper' class UsersEditTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) end . . . test "successful edit" do get edit_user_path(@user) assert_template 'users/edit' name = "Foo Bar" email = "foo@bar.com" patch user_path(@user), params: { user: { name: name, email: email, password: "", password_confirmation: "" } } assert_not flash.empty? assert_redirected_to @user @user.reload assert_equal name, @user.name assert_equal email, @user.email end endこのままでは長さに対するバリデーションが有効となっているためREDとなってしまう。パスワードのバリデーションに対して例外処理を加える。
ユーザーmodelsへ
validates :password, presence: true, length: { minimum: 6 }, allow_nil: trueを追加する、これは新規ユーザー登録時には有効とならない処理である。
認可
認証 (authentication) はサイトのユーザーを識別することであり、認可 (authorization) はそのユーザーが実行可能な操作を管理すること。
editアクションとupdateアクションはセキュリティ上欠陥がある。それはどのユーザーでもあらゆるアクションにアクセスできるため、誰でも (ログインしていないユーザーでも) ユーザー情報を編集できてしまう。そこでユーザーにログインを要求し、かつ自分以外のユーザー情報を変更できないように制御する(こういったセキュリティ上の制御機構をセキュリティモデルと呼ぶ)。まずログインしたユーザーが保護されたページへアクセスしようとした際にログインページへ転送する方法と許可されていないページに対しアクセスするログイン済みのユーザーにはルートURLにリダイレクトさせるようにする。
ユーザーに対しログインを要求する
Usersコントローラの中でbeforeフィルターを使い、転送させる仕組みを作る。
class UsersController < ApplicationController before_action :logged_in_user, only: [:edit, :update] # ログイン済みユーザーかどうか確認 def logged_in_user unless logged_in? flash[:danger] = "Please log in." redirect_to login_url end endデフォルトでは、beforeフィルターはコントローラ内のすべてのアクションに適用されるので、ここでは適切な:onlyオプション (ハッシュ) を渡すことで、:editと:updateアクションだけにこのフィルタが適用されるように制限をかけている。unlessは条件が偽の時に対応する処理が作動する。
editとupdateアクションの保護に対するtest
class UsersControllerTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) end . . . test "should redirect edit when not logged in" do get edit_user_path(@user) assert_not flash.empty? assert_redirected_to login_url end test "should redirect update when not logged in" do patch user_path(@user), params: { user: { name: @user.name, email: @user.email } } assert_not flash.empty? assert_redirected_to login_url end end正しいユーザーを要求する
ログインを要求するだけでは不十分であり、ユーザーが自分の情報だけを編集できるようにする必要がある。そこでUserコントローラのtestを補完するようにtestを追加する。
まずfixtureファイルに2人目のユーザーを追加する。
次に9章で定義したlog_in_asメソッドを使ってeditアクションとupdateアクションをtestする。require 'test_helper' class UsersControllerTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) @other_user = users(:archer) end . . . test "should redirect edit when logged in as wrong user" do log_in_as(@other_user) get edit_user_path(@user) assert flash.empty? assert_redirected_to root_url end test "should redirect update when logged in as wrong user" do log_in_as(@other_user) patch user_path(@user), params: { user: { name: @user.name, email: @user.email } } assert flash.empty? assert_redirected_to root_url end end@other_user(二人目のユーザー)を定義。
上段のtestは2人目のユーザーが1人目のユーザーのedit_user_pathに入った場合、フラッシュがでるか。ルートURLへリダイレクトするのかを確認している。
下段のtestは二人目のユーザーが一人目のユーザーの情報を変更する内容のpatchリクエストを送信した場合にフラッシュがでてルートURLへ値ダイレクトされるかを確認している。下段のtestをパスするため、correct_userというメソッドを作成し、beforeフィルターからこのメソッドを呼び出せるようにする。
before_action :correct_user, only: [:edit, :update] # 正しいユーザーかどうか確認 def correct_user @user = User.find(params[:id]) redirect_to(root_url) unless @user == current_user endフレンドリーフォワーディング
これまでは保護されたページにアクセスしようとすると問答無用で自分のプロフィールページに移動させられてしまう。別の言い方をすればログインしていないユーザーが編集ページにアクセス使用としていたならば、ユーザーがログインした後にはその編集ページにリダイレクトされるようにするのが望ましい動作である。
フレンドドリーフォワーディングのtest
require 'test_helper' class UsersEditTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) end . . . test "successful edit with friendly forwarding" do get edit_user_path(@user) log_in_as(@user) assert_redirected_to edit_user_url(@user) name = "Foo Bar" email = "foo@bar.com" patch user_path(@user), params: { user: { name: name, email: email, password: "", password_confirmation: "" } } assert_not flash.empty? assert_redirected_to @user @user.reload assert_equal name, @user.name assert_equal email, @user.email end end編集URLへアクセス。ログインした時、編集ページにリダイレクトされているか?
失敗するテストが書けたので、ようやくフレンドリーフォワーディングを実装する準備ができた。ユーザーを希望のページに転送するには、リクエスト時点のページをどこかに保存しておき、その場所にリダイレクトさせる必要があります。この動作をstore_locationとredirect_back_orの2つのメソッドを使って実現する。これらのメソッドはSessionsヘルパーで定義していますmodule SessionsHelper . . . # 記憶したURL (もしくはデフォルト値) にリダイレクト def redirect_back_or(default) redirect_to(session[:forwarding_url] || default) session.delete(:forwarding_url) end # アクセスしようとしたURLを覚えておく def store_location session[:forwarding_url] = request.original_url if request.get? end end転送先のURLを保存する仕組みは、ユーザーをログインさせたときと同じで、session変数を使います。requestオブジェクトも使っています (request.original_urlでリクエスト先が取得できます)。store_locationメソッドでは、 リクエストが送られたURLをsession変数の:forwarding_urlキーに格納しています。ただし、GETリクエストが送られたときだけ格納するようにしておきます。これによって、例えばログインしていないユーザーがフォームを使って送信した場合、転送先のURLを保存させないようにできる。
ログインユーザー用beforeフィルターにstore_locationを追加する。
class UsersController < ApplicationController before_action :logged_in_user, only: [:edit, :update] before_action :correct_user, only: [:edit, :update] def edit end private def user_params params.require(:user).permit(:name, :email, :password, :password_confirmation) end # beforeアクション # ログイン済みユーザーかどうか確認 def logged_in_user unless logged_in? store_location flash[:danger] = "Please log in." redirect_to login_url end end # 正しいユーザーかどうか確認 def correct_user @user = User.find(params[:id]) redirect_to(root_url) unless current_user?(@user) end endフォワーディング自体を実装するには、redirect_back_orメソッドを使います。リクエストされたURLが存在する場合はそこにリダイレクトし、ない場合は何らかのデフォルトのURLにリダイレクトします。デフォルトのURLは、Sessionコントローラのcreateアクションに追加し、サインイン成功後にリダイレクトします このコードは、値がnilでなければsession[:forwarding_url]を評価し、そうでなければデフォルトのURLを使っています。
またリストではsession.delete(:forwarding_url) という行を通して転送用のURLを削除している点にも注意してください。すべてのユーザーを表示する
indexアクションを追加していく。ここではすべてのユーザーの一覧表示を行っていく。
データベースにサンプルデータを追加する方法
ユーザー出力のページネーション用リンクの追加をするユーザーの一覧ページを実装するためにまずはセキュリティモデルについて考える。ユーザーのshowページは今後もサイトを訪れた全てのユーザーから見えるようにするがindexページはログインしたユーザーにしか見せないようにし、未登録のユーザーがデフォルトで表示できるページを制限する。
indexページを不正なアクセスから守るためにindexアクションが正しくリダイレクトするか検証するtestを書いてみる。
require 'test_helper' class UsersControllerTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) @other_user = users(:archer) end test "should redirect index when not logged in" do get users_path assert_redirected_to login_url end . . . end次にbeforeフィルターのlogged_in_userにindexアクションを追加してこのアクションを保護する。
そしてすべてのユーザーを表示するために、全ユーザーが格納された変数を作成し、順々に表示するindexビューを実装します。
<% provide(:title, 'All users') %> <h1>All users</h1> <ul class="users"> <% @users.each do |user| %> <li> <%= gravatar_for user, size: 50 %> <%= link_to user.name, user %> </li> <% end %> </ul>そしてユーザー一覧ページのリンクをheaderに更新して動かせるようにする。
サンプルユーザーの追加
今のままでは一人しかユーザー一覧がないため、ここからはサンプルのユーザーを追加する。
サンプルのユーザーを作成するにはGemfileにFakergemを追加する。データベース上にサンプルユーザーを生成するRailsタスク
db/seeds.rb User.create!(name: "Example User", email: "example@railstutorial.org", password: "foobar", password_confirmation: "foobar") 99.times do |n| name = Faker::Name.name email = "example-#{n+1}@railstutorial.org" password = "password" User.create!(name: name, email: email, password: password, password_confirmation: password) endExample Userという名前とメールアドレスを持つ1人のユーザと、それらしい名前とメールアドレスを持つ99人のユーザーを作成します。
ページネーション
これで、最初のユーザーにも仲間ができたが、今度は逆に1つのページに大量のユーザーが表示されてしまっている。これを解決するのがページネーション (pagination) というもので、この場合は、例えば1つのページに一度に30人だけユーザーを表示するというものです。
これを使うためには、Gemfileにwill_paginate gem とbootstrap-will_paginate gemを両方含め、Bootstrapのページネーションスタイルを使ってwill_paginateを構成する必要があります。gem 'will_paginate', '3.1.6' gem 'bootstrap-will_paginate', '1.0.0'indexアクションにあるUser.allを、ページネーションを理解できるオブジェクトに置き換える必要もあります。まずは、ビューに特殊なwill_paginateメソッドを追加します。
app/views/users/index.html.erb <% provide(:title, 'All users') %> <h1>All users</h1> <%= will_paginate %> <ul class="users"> <% @users.each do |user| %> <li> <%= gravatar_for user, size: 50 %> <%= link_to user.name, user %> </li> <% end %> </ul> <%= will_paginate %>indexアクションでUsersをページネートする。
def index @users = User.paginate(page: params[:page]) endユーザー一覧のtest
今回のテストでは、ログイン、indexページにアクセス、最初のページにユーザーがいることを確認、ページネーションのリンクがあることを確認、といった順でテストしていく。
まずFixtureで30人のユーザーを追加する。次にページネーションを含めたUserIndexのtestを追加
test/integration/users_index_test.rb require 'test_helper' class UsersIndexTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) end test "index including pagination" do log_in_as(@user) get users_path assert_template 'users/index' assert_select 'div.pagination' User.paginate(page: 1).each do |user| assert_select 'a[href=?]', user_path(user), text: user.name end end endユーザーの削除
ここではユーザーを削除するためのリンクを追加する。また削除に必要なdestroyアクションも実装する。しかしその前に、削除を実行できる権限をもつ管理(Admin)ユーザーのクラスを作成する。
まず特権を持つ管理ユーザーを識別するために論理値をとるadmin属性をUserモデルに追加する。こうすると自動的にadmin?メソッドも使えるようになるのでこれを使って管理ユーザーの状態をtestする。destroyアクション
Userリソースの最後の仕上げとしてdestroyアクションへのリンクを追加する。まずユーザーindexページの各ユーザーに削除用のリンクを追加し続いて管理ユーザーへのアクセスを制限する。これにより現在のユーザーが管理者のときに限り[delete]リンクが表示されるようになる。
ユーザー削除用リンクの実装
app/views/users/_user.html.erb <li> <%= gravatar_for user, size: 50 %> <%= link_to user.name, user %> <% if current_user.admin? && !current_user?(user) %> | <%= link_to "delete", user, method: :delete, data: { confirm: "You sure?" } %> <% end %> </li>この削除リンクが動作するためには、destroyアクションを追加する必要があります。このアクションでは、該当するユーザーを見つけてActive Recordのdestroyメソッドを使って削除し、最後にユーザーindexに移動します。ユーザーを削除するためにはログインしていなくてはならないので、で:destroyアクションもlogged_in_userフィルターに追加しています。
destroyアクションでは、findメソッドとdestroyメソッドを1行で書くために2つのメソッドを連結 (chain) している。
結果として、管理者だけがユーザーを削除できるようになります (より具体的には、削除リンクが見えているユーザーのみ削除できる)。しかし、実はまだ大きなセキュリティホールがあります。ある程度の腕前を持つ攻撃者なら、コマンドラインでDELETEリクエストを直接発行するという方法でサイトの全ユーザーを削除してしまうことができるでしょう。サイトを正しく防衛するには、destroyアクションにもアクセス制御を行う必要があります。これを実装してようやく、管理者だけがユーザーを削除できるようにします。
app/controllers/users_controller.rb class UsersController < ApplicationController before_action :logged_in_user, only: [:index, :edit, :update, :destroy] before_action :correct_user, only: [:edit, :update] before_action :admin_user, only: :destroy . . . private . . . # 管理者かどうか確認 def admin_user redirect_to(root_url) unless current_user.admin? end endユーザー削除のtest
Usersコントローラをテストするために、アクション単位でアクセス制御をテストします。削除をテストするために、DELETEリクエストを発行してdestroyアクションを直接動作させます。このとき2つのケースをチェックします。
・ログインしていないユーザーであれば、ログイン画面にリダイレクトされること。
・ログイン済みではあっても管理者でなければ、ホーム画面にリダイレクトされること。管理者権限の制御をアクションレベルでテストする green
test/controllers/users_controller_test.rbrequire 'test_helper' class UsersControllerTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) @other_user = users(:archer) end . . . test "should redirect destroy when not logged in" do assert_no_difference 'User.count' do delete user_path(@user) end assert_redirected_to login_url end test "should redirect destroy when logged in as a non-admin" do log_in_as(@other_user) assert_no_difference 'User.count' do delete user_path(@user) end assert_redirected_to root_url end endassert_no_differenceメソッドを使って、ユーザー数が変化しないことを確認している点に注目してください。
このテストでは、管理者ではないユーザーの振る舞いについて検証していますが、管理者ユーザーの振る舞いと一緒に確認できるとよさそうです。そこで、管理者であればユーザー一覧画面に削除リンクが表示される仕様を利用して今回のテストを追加していくことにします。
assert_difference 'User.count', -1 do delete user_path(@other_user) endassert_differenceメソッドを使ってユーザーが作成されたことを確認しましたが、今回は同じメソッドを使ってユーザーが削除されたことを確認しています。具体的には、DELETEリクエストを適切なURLに向けて発行し、User.countを使ってユーザー数が
1
減ったかどうかを確認しています。したがって、管理者や一般ユーザーのテスト、そしてページネーションや削除リンクのテストをすべてまとめると、次のようになります。
削除リンクとユーザー削除に対する統合テスト green
test/integration/users_index_test.rb require 'test_helper' class UsersIndexTest < ActionDispatch::IntegrationTest def setup @admin = users(:michael) @non_admin = users(:archer) end test "index as admin including pagination and delete links" do log_in_as(@admin) get users_path assert_template 'users/index' assert_select 'div.pagination' first_page_of_users = User.paginate(page: 1) first_page_of_users.each do |user| assert_select 'a[href=?]', user_path(user), text: user.name unless user == @admin assert_select 'a[href=?]', user_path(user), text: 'delete' end end assert_difference 'User.count', -1 do delete user_path(@non_admin) end end test "index as non-admin" do log_in_as(@non_admin) get users_path assert_select 'a', text: 'delete', count: 0 end endとなります。
本日はここまで。
- 投稿日:2020-04-05T01:50:19+09:00
Uglifier::Error: Unexpected character '`' Herokuデプロイ時のエラー解消方法
はじめに
今年の1月末からプログラミングの学習を開始したばかりの初心者です。
今回作成した個人アプリをHerokuでデプロイしてみたのですが、途中で発生したエラー解決について自分用の備忘録として初投稿してみました。※今回はあくまで発生したエラーの解消法のみをシンプルにまとめています。
Herokuでのデプロイ方法については参考にした記事を最下部に貼っているので気になる方はそちらをご確認ください。※開発環境
・Ruby 2.5.1
・Rails 5.2.4.2
・heroku/7.39.2 darwin-x64 node-v12.13.0エラー内容
Herokuでデプロイ中に以下のようなエラー文言が表示されデプロイに失敗しました。
ターミナルremote: ! remote: ! Precompiling assets failed. remote: ! remote: ! Push rejected, failed to compile Ruby app. remote: remote: ! Push failed remote: Verifying deploy... remote: remote: ! Push rejected to (設定したアプリ名). remote: To https://git.heroku.com/(設定したアプリ名).git ! [remote rejected] master -> master (pre-receive hook declined) error: failed to push some refs to 'https://git.heroku.com/(設定したアプリ名).git'ターミナルを遡って見てみると、エラー原因について以下のような記述が。
ターミナルremote: rake aborted! remote: Uglifier::Error: Unexpected character '`' remote: --エラー文言からバックフォート(`)がunexpectedだからダメだよ〜となっているんだなという事は何となく理解したものの、じゃあどうしたら良いのかは分からないので調べてみました。
(ちなみに自分はapp/assets/javascriptのファイル内でバックフォートを使った記述をしていました。)解決方法
config/environments/production.rb内にある以下の記述をコメントアウトしてあげればOKでした。
production.rb# config.assets.js_compressor = :uglifier #この一文をコメントアウト
コメントアウトした後はGithubでコミット&プッシュをしてmasterに反映させます。
GitHub Desktopを使用している場合は、changeに変更内容が上がっていると思うのでコミット&プッシュ。ターミナル上でコマンドを打つ場合は以下のように行う。
①コミットするchange(ファイル)の選択ターミナル$git add -A #コミットするchangeの選択。 -Aは全部ということ②選択したファイルのコミット&プッシュ
ターミナル$git commit -m "コミットメッセージ" #""内には変更内容が分かるメッセージを入力これでmasterに変更が反映されたので、改めてHerokuにデプロイします。
以下のように表示され、無事にデプロイが完了しました!ターミナルremote: Verifying deploy... done. To https://git.heroku.com/(設定したアプリ名).git * [new branch] master -> master最後に
production.rbファイルを変更した後、コミット&プッシュを忘れない!
初めこれをする前に再度デプロイをしてしまって全く同じエラーが出ました。。参考記事
Herokuでのデプロイ方法はこちらの記事を参考にさせてもらいました。
【初心者向け】railsアプリをherokuを使って確実にデプロイする方法【決定版】
- 投稿日:2020-04-05T01:13:49+09:00
【Rails】deviseを使用した簡単なログイン機能まとめ
はじめに
学習中の備忘録です。
概要
新規アプリ作成の際にdeviseのコマンドなど忘れるのでまとめ。
- 導入
- devise設定ファイル作成
- モデル作成
- ビューファイル作成
- deviseによって設定されるPrefixの一部
前提
rails 5.2.3
導入
gemファイルに追記
Gemfilegem 'devise'インストール
ターミナル$ bundle installサーバー再起動
ターミナル$ rails sdeviseの設定ファイルを作成
ターミナル$ rails g devise:install新規作成されるファイル
- config/initializers/devise.rb
- config/locales/devise.en.yml
モデル作成
ターミナル$ rails g devise user新規作成されるファイル
- app/models/user.rb
- db/migrate/20XXXXXXXXXXXX_devise_create_users.rb
- test/fixtures/users.yml
- test/models/user_test.rb
また、config/routes.rbに以下の様な記述が自動的に追記されます。
【例】config/routes.rbRails.application.routes.draw do devise_for :users #以下略devise_for :usersの記述により、ログイン・新規登録で必要なルーティングが生成されます。
作成されたmigrationファイルを実行
ターミナル$ rails db:migrateビューファイル作成
ターミナル$ rails g devise:views新規作成されるファイル
- app/views/devise以下のディレクトリにあるビューファイル各種
deviseによって設定されるPrefixの一部
リクエスト Prefix パス devise/sessions#new new_user_session /users/sign_in devise/registrations#new new_user_registration /users/sign_up devise/sessions#destroy destroy_user_session /users/sign_out あとは好きな場所に上記のリンクをはれば完成です。
まとめ
ユーザー情報の編集などは必要に応じて追記するかもです。