- 投稿日:2019-08-27T23:47:03+09:00
Rails6でブログのタグのacts-as-taggable-onがエラーになったので別のタグgem(gutentag tags)を使う
Rails6にアップデートした時の問題
Rails6になってから、今まで使っていた
acts-as-taggable-onがエラーになるようになった。原因は
まあ悪い意味での原因というわけではなく
https://github.com/rails/rails/pull/35933
この部分で
set_attribute_wasが消されたため発生したものまた、これの対応のブランチもあるのだが、初歩的なバグを含むため、ブランチ切り替えの対応してもあまり意味がない。
とはいえ、ライブラリへのPRも全然反映されないし、Gemfileが汚れるのも嫌なので別のやつを実験的に使ってみた。
https://github.com/pat/gutentag
というやつで、スターは315の、RubyGemダウンロード数は54,474というところなので、act-as-taggable-onのわずか6%程度のスター数となり、多少心もとない。
開発当初のブログがあるので、読んでみた
個人的には、act-as-taggable-onしか使ったことなかったので、とりあえず調べていたら、どうも2013年頃に開発されたようだ。
https://freelancing-gods.com/2013/07/11/gutentag-simple-rails-tagging.html
https://github.com/pat/gutentag/blob/master/lib/gutentag/persistence.rb
永続化周りをforwardableを使って委譲して実装してある
使ってみた
環境
- Rails 6.0.0
- ruby 2.6.3
Gemfile
記載したら、
bundle installするgem 'gutentag', '~> 2.5'インストール設定
ここは、よくあるお決まりのやつをただ打っていくだけ
migrateすることで、gutentagにより作られたマイグレーションファイルが実行され、使えるようになる
使う側のモデルにカラムを追加することなく使えるところが楽で良いbundle exec rake gutentag:install:migrations rails db:migrateSchema例
create_table "blogs", force: :cascade do |t| t.datetime "created_at", null: false t.datetime "updated_at", null: false endBlog.rb
今回、ブログにタグ機能をもたせたいので、
ここにGutentag::ActiveRecord.call selfを置くclass Blog < ApplicationRecord Gutentag::ActiveRecord.call self include TagExtensions end FactoryBot.define do factory :blog do tags_as_string { "hoge,fuga,piyo" } end endTagExtensions.rb
これは、いわゆる
attr_accessorの役割をincludeして使うために別途作っただけ
メソッド毎モデルに直で書いても良いが、他のモデルでも使いたかったのでこのようにサクッと作っただけ。(若干気に入らないので後で修正する気がする)module TagExtensions def tags_as_string tag_names.join(', ') end def tags_as_string=(string) self.tag_names = string.split(/,\s*/) end end作成時
View
一般的なinputタグに先程TagExtensionsで作ったgetterメソッドを配置しておく
.form = f.label :tags_as_string = f.text_field :tags_as_string, value: @blog.tags_as_stringController#create
Scaffoldして、必要なところだけ抜粋しておく
フォームから送られて新規作成するところの処理と、Strong Parametersの設置class BlogsController < ApplicationController def create @blog = Blog.new(blog_params) respond_to do |format| if @blog.save format.html { redirect_to blog_url(@blog), notice: 'Blog was successfully created.' } format.json { render :show, status: :created, location: @blog } else format.html { render :new } format.json { render json: @blog.errors, status: :unprocessable_entity } end end end def blog_params params.require(:blog).permit(:tags_as_string) end end一覧表示時
View
tag_namesなどで引っ張ってこれる
- @blogs.each do |blog| - blog.tag_names.each do |tag| = tagController#index
例えば
includesなどをしないとN+1で死ぬので注意class BlogsController < ApplicationController def index @blogs = Blog.includes(:tags) end end挙動確認
実際にテストなどで挙動見てみるとこんなように使える
blog = Blog.create! # ブログ用レコード作る blog.tag_names << 'hogehoge' blog.tag_names #=> ['hoge'] blog.tag_names << 'fuga' << 'piyo' blog.tag_names #=> ['hoge', 'fuga', 'piyo'] blog.tag_names -= ['fuga'] blog.tag_names #=> ['hoge', 'piyo'] blog.save # 保存する感想
gutentag自体は、rails6にすぐ対応したcommitがあるし、issueのclose率も100%なのでけっこういいかなと思った。懸念点はpatという創造者しかほぼ保守していないところ。
acts-as-taggable-onは、使ってる人が多いので、また修正版が上がりそうなので、そのあたり様子見
- 投稿日:2019-08-27T22:58:43+09:00
HerokuのDB更新時のActiveRecord::StatementInvalid (PG::UndefinedColumn: ERROR: column ... does not existの対処方
PG::UndefinedColumn: ERROR
ローカル環境に置けるrails db:migrateはエラーが起きなかったが、
本番環境(Heroku)に heroku run rails db:migrateをすると出るエラー今回起きた要因
これが発生した要因として
今までは普通にできたが、不要なカラができてしまったので削除する。
そしてHerokuも同様に反映させようとしたが今回のエラーが発生。
ゆえに今回はカラム削除のDBファイルがからんでいると推測。エラー内容 &対処法
PG::UndefinedColumn: ERROR: column "string" for relation "users" does not existとでていた。
そこでusersテーブルの削除したファイルを見てみると..._remove_delete_to_usersclass RemoveDelete2ToUsers < ActiveRecord::Migration[5.1] def change remove_column :users, :job, :string remove_column :users, :string, :string remove_column :users, :basic_overtime_pay, :string remove_column :users, :integer, :string remove_column :users, :uid, :string remove_column :users, :string, :string remove_column :users, :image, :string remove_column :users, :string, :string remove_column :users, :encrypted_password, :string remove_column :users, :string, :string remove_column :users, :reset_password_token, :string remove_column :users, :string, :string remove_column :users, :note, :string remove_column :users, :string, :string end endこのようにカラムの型がたくさんある状態になっている。
そこでここを整理整頓してみる。..._remove_delete_to_usersclass RemoveDelete2ToUsers < ActiveRecord::Migration[5.1] def change remove_column :users, :job, :string remove_column :users, :basic_overtime_pay, :integer remove_column :users, :uid, :string remove_column :users, :image, :string remove_column :users, :encrypted_password, :string remove_column :users, :reset_password_token, :string remove_column :users, :note, :string end endここで改めて heroku run rails db:migrateをすると
通った!!!
ロールバックするさいもカラムと型が合わずにうまくいかない!
みたいなことがあるみたいなのです。
今回のように同じエラーがでてるかたは参考にしてみていただけると幸いです。
- 投稿日:2019-08-27T22:07:38+09:00
rails viewの繰り返しでitemの一覧を表示する方法
railsのviewでアイテムをfor文を使って表示しています。
<%for i in 0..4 do%> <li><%=link_to @ranking_articles[i].title,article_path(@ranking_articles[i].id)%><span>(<%=@previews[i]%>)</span></li> <%end%>
- 投稿日:2019-08-27T21:35:58+09:00
Railsの勉強会に行ってきた話
こんにちは。はじめての投稿になるので、よろしくお願いします。
感想
とても楽しい時間を過ごすことができました。
技術的な話としては、MVC構造は1960年代?ごろから使われていたものらしく、結構古いもののようで、それをRailsは未だに使っていて大丈夫なの?っていうのが話題にでたときに、Railsが批判されてる理由が少しわかった気がしました。自由時間に現役のエンジニアの方からアドバイスをもらうことができて、とても参考になりました。きつい時もあるけど、楽しくコードを書いていこうというのが励みになりました。勉強会の後に、数人で食事に行き、業界のことなどいろいろ話ができて楽しかったです。ちなみに奢ってもらっちゃいました笑
Ruby界隈は、親切な人が多いなと思いましたし、いろんな所でイベントがあり、活発なのでまた行ってみたいと思いました。
- 投稿日:2019-08-27T21:28:27+09:00
プログラミング学習(三日目)ProgateでRuby On Rails レッスン3
今日学んだこと
find_byメソッドで、データベースからデータを取得
モデル名.find_by(カラム名:値)で、指定したカラムの値を持つレコードを取得できる
例えば、
>a = Post.find_by(id:3)
で、変数aにpostsテーブルのidが3のレコードを代入できる。
そしてここから、
>a.カラム名
とすれば、idが3の指定したカラム名の値を出力できる。ルーティングのURLに:idで、任意のすべての値を適用
具体的に言うと、
routes.rbget "/home/:id" => "home#about"とすると、URLが「/home/a」でも、「/home/xyz」でも、「/home/1234567890」でも、
homeコントローラのaboutアクションを指定されると言う訳。
ちなみに、ルーティングはファイルの上から順に該当するURLを探すので、:idを使う場合は下の方にやらないとやばい。paramsの使い方
そして上のURLの:idに該当する値は、コントローラ内のアクション内で取得できる。
paramsにハッシュで入っているらしく、params[:id]とやると取得できる。入力フォームを作成し、データベースに保存する方法。
これが少し複雑で難しかった。
「form_tag」と「redirect_to」と「post」を使うまずhtml.erbファイルに入力フォームを作るのだが、
<%= form_tag("送信先のURL") do %>
<textarea name="test"></textarea>
<input type="submit" value="送信">
<% end %>と作る。form_tagで送信先のURLを設定しつつ、入力内容の属性をname属性とし{test:"入力した内容"}というハッシュで、指定したURLに対応するコントローラのアクションへと送られる。
ちなみに、textareaにname属性を付与しないと送信できないらしい。そしてそのアクションへ入力内容が送られるわけだが、まずそこでここではpostsテーブルを用いているので、Postインスタンスを作成し保存する。その際にparamsを使い、任意のカラムが入力された内容になるインスタンスを作成する。
こんな感じ(postsテーブルにはcontentカラムがある)
**def create**
** @inputfile = Post.new(content:params[:test])**
** @inputfile.save**
** redirect_to("送信先のURL")**
**end**ちなみに「redirect_to」とは、アクションに対応するビューファイルが無い時にエラーにならないために、処理?データ?をほかのアクションへ送るメソッドである。
データの並べ替え
orderメソッドを用いて、「order(カラム名:並び順)」と指定するとその指定した並び順になる。昇順が「:asc」、降順が「:desc」
@posts=Post.new.order(id: :asc)とすると、idが1から並ぶ。結構抽象的に書いた分、早く書けたが少しわかりにくい可能性が、、、
- 投稿日:2019-08-27T20:58:52+09:00
#Ruby #Rails で 日付同士の差分が 何週間ぐらいかを 計算しようとしてみた記録 ( ActionView::Helpers::DateHelper が使えない ))
Ruby #Rails record to calculate how many weeks the difference between dates is (ActionView :: Helpers :: DateHelper cannot be used))
ActionView::Helpers::DateHelperが週に対応してないようなのでrequire 'action_view' require 'action_view/helpers' include ActionView::Helpers::DateHelper distance = if some_date.between?(1.weeks.after, 1.month.after) distance_weeks = (some_date - Date.current).to_i / 7 "#{distance_weeks}週間" else distance_of_time_in_words(Time.zone.now, some_date) endhttps://edgeapi.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html
Original by Github issue
- 投稿日:2019-08-27T19:51:11+09:00
RSpecの `contain_exactly` 相当でハッシュを検証する方法に悩んでしまった話
ハッシュの
{ a: 1, b: 2 }と{ b: 2, a: 1 }が、順序を無視して完全に一致するかテストしたかった。
contain_exactlyRSpecには順序を無視してくれる
contain_exactlyがあるが、これは配列に対して使うものらしい。
https://relishapp.com/rspec/rspec-expectations/docs/built-in-matchers/contain-exactly-matcherThe contain_exactly matcher provides a way to test arrays against each other in a way that disregards differences in the ordering between the actual and expected array.
ハッシュでやってみると実際おかしなことになって失敗する。
Failure/Error: expect({ a: 1, b: 2 }).to contain_exactly({ b: 2, a: 1 }) expected collection contained: [{:a=>1, :b=>2}] actual collection contained: [[:a, 1], [:b, 2]] the missing elements were: [{:a=>1, :b=>2}] the extra elements were: [[:a, 1], [:b, 2]]エラーメッセージから考えると、一応
contain_exactly側のハッシュを*で展開すればテストが通る。require 'rspec/core' RSpec.describe 'Hash' do it 'contains exactly' do expect({ a: 1, b: 2 }).to contain_exactly(*{ b: 2, a: 1 }) end endしかしもっと単純な方法があった。
結論
普通に
eqで検証すればいい。require 'rspec/core' RSpec.describe 'Hash' do it 'contains exactly' do expect({ a: 1, b: 2 }).to eq({ b: 2, a: 1 }) end end内容が異なる場合も、エラーメッセージで差分を出してくれるのでわかりやすい。
Failure/Error: expect({ a: 1, b: 2 }).to eq({ b: 2, a: 3 }) expected: {:a=>3, :b=>2} got: {:a=>1, :b=>2} (compared using ==) Diff: @@ -1,3 +1,3 @@ -:a => 3, +:a => 1, :b => 2,理由
参考:https://docs.ruby-lang.org/ja/latest/class/Hash.html
リファレンスマニュアルに明記されてはいないが、ハッシュは
==で比較するときに要素の順序は考慮しない。自身と other が同じ数のキーを保持し、キーが eql? メソッドで比較して全て等しく、 値が == メソッドで比較して全て等しい場合に真を返します。
{ a: 1, b: 2 } == { b: 2, a: 1 } #=> trueなので、
==で比較を行うeqを使えばいい。
ただし、ハッシュは要素の順序を保持しているので、配列に変換したりループを回したりすると違いが現れる。
ハッシュに含まれる要素の順序が保持されるようになりました。ハッシュにキーが追加された順序で列挙します。
{ a: 1, b: 2 }.to_a == { b: 2, a: 1 }.to_a #=> false
- 投稿日:2019-08-27T19:51:09+09:00
Rails & Nuxt.jsのDocker環境をalpineイメージで構築
Ruby on Rails、Nuxt.js、MySQLのDocker環境を作成します。
Rails、Nuxtのalpine環境の構築手順はそれぞれだと多くあるのですが、まとまったものがあまりない見つからなかったので、復習を兼ねてポストを作成します。準備
ディレクトリ作成
作業ディレクトリは任意です。
$ NEW_APP=rails-nuxt-app #任意のアプリ名 $ mkdir ${NEW_APP} $ cd ${NEW_APP} $ mkdir ./backend ./frontend
backendはRails用、frontendはNuxt用のディレクトリです。
まずは下記のファイルを修正していきます。. ├ backend │ ├ Dockerfile │ ├ Gemfile │ └ Gemfile.lock │ ├ frontend │ └ Dockerfile │ ├ docker-compose.yml └ .envdocker-compose.yml
.envdocker-compose.ymlで参照する環境変数を記載します。
ここではMySQLのrootパスワード、RailsおよびNuxt環境のホスト、ポート番号のみ定義します。
RailsとNuxtは、共にデフォルトのポートが3000番なので、後の利便性のためにいずれかを変えておきます。
(本記事ではNuxt側を8080に変更)MYSQL_ROOT_PASSWORD=password BACKEND_HOST=0.0.0.0 BACKEND_PORT=3000 FRONTEND_HOST=0.0.0.0 FRONTEND_PORT=8080
./docker-compose.yml.envで定義した変数を参照しています。
docker-compose.ymlのenvironment、 DockerfileのENVで同じ環境変数が定義されていた場合は、前者が使用されます。
(本記事ではDockerfile単体でもイメージ作成できるように環境変数の記載を残していますが、docker-compose.ymlに定義されていれば問題ありません)下記はDockerボリュームを作成します。
- mysqlのdatadir
- rubyのgem_home
- nodeのnode_modules
docker-compose.ymlversion: '3' services: db: image: mysql:5.7.27 restart: always volumes: - db-data:/var/lib/mysql environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} backend: build: ./backend ports: - ${BACKEND_PORT}:3000 command: /bin/sh -c "rm -f /app/tmp/pids/server.pid && bundle exec rails s -b ${BACKEND_HOST}" volumes: - ./backend:/app - backend-bundle:/usr/local/bundle environment: - HOST=${BACKEND_HOST} - PORT=${BACKEND_PORT} depends_on: - db tty: true stdin_open: true frontend: build: ./frontend ports: - ${FRONTEND_PORT}:8080 command: /bin/sh -c "yarn dev" volumes: - ./frontend:/app - frontend-node_modules:/app/node_modules environment: - HOST=${FRONTEND_HOST} - PORT=${FRONTEND_PORT} tty: true volumes: db-data: backend-bundle: frontend-node_modules:backend
Rails環境構築のためのファイルを準備します。
./backend/GemfileRailsのバージョンのみ指定しておきます。
Gemfilesource 'https://rubygems.org' gem 'rails', '5.2.3'
./backend/Gemfile.lock空ファイルをtouchしておけばOKです。
./backend/DockerfileAlpine Linuxのパッケージは最低限のものだけインストールします。
開発を進めるうちにgemのインストールで依存エラーが発生した場合には、不足パッケージを都度追加しましょう。DockerfileFROM ruby:2.6.3-alpine ENV RUNTIME_PACKAGES "mysql-client mysql-dev tzdata nodejs" ENV DEV_PACKAGES "build-base curl-dev" ENV APP_HOME /app ENV TZ Asia/Tokyo ENV HOST 0.0.0.0 ENV PORT 3000 WORKDIR ${APP_HOME} ADD Gemfile ${APP_HOME}/Gemfile ADD Gemfile.lock ${APP_HOME}/Gemfile.lock RUN apk update \ && apk upgrade \ && apk add --update --no-cache ${RUNTIME_PACKAGES} \ && apk add --update --no-cache --virtual=.build-dependencies ${DEV_PACKAGES} \ && bundle install -j4 \ && rm -rf /usr/local/bundle/cache/*.gem \ && find /usr/local/bundle/gems/ -name "*.c" -delete \ && find /usr/local/bundle/gems/ -name "*.o" -delete \ && apk del --purge .build-dependencies \ && rm -rf /var/cache/apk/* COPY . ${APP_HOME} EXPOSE ${PORT} CMD ["rails", "server", "-b", ${HOST}]frontend
./frontend/DockerfileDockerfileFROM node:12.9.0-alpine ENV APP_HOME /app ENV PATH ${APP_HOME}/node_modules/.bin:$PATH ENV TZ Asia/Tokyo ENV HOST 0.0.0.0 ENV PORT 8080 WORKDIR ${APP_HOME} ADD . ${APP_HOME} RUN apk update \ && apk upgrade \ && yarn install \ && rm -rf /var/cache/apk/* EXPOSE ${PORT} CMD ["yarn", "dev"]アプリケーション作成
Rails、Nuxt環境にプロジェクトを作成します。
docker-compose runを実行したタイミングで、それぞれのDockerイメージがbuildされ、さらにコンテナが立ち上がります。
--no-deps... docker-compose.ymlでdepends_onorlinks指定するサービスは起動しない。
--rm... 処理を終えたコンテナを自動的に削除。backend
アプリケーション作成
rails newでRailsアプリケーションを作成します。
--apiオプションでAPIモードにしていますが、不要な方は外してください。$ docker-compose run --no-deps --rm backend rails new . --force --api --database=mysql --skip-bundleDB接続のため、下記のファイルを修正します。
. └ backend ├ config │ └ database.yml ├ Gemfile └ .env
./backend/.envdocker-compose.ymlで参照している
MYSQL_ROOT_PASSWORDと同じもの設定します。MYSQL_ROOT_PASSWORD=password
./backend/Gemfile.envから環境変数を読み込むdotenv-railsというgemを追加します。
Gemfile~~省略~~ group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] gem "dotenv-rails" #追加 end ~~省略~~
./backend/config/database.ymlDBへのアクセスに使用するパスワードを、環境変数から取得します。
database.yml~~省略~~ default: &default adapter: mysql2 encoding: utf8 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root password: <%= ENV.fetch("MYSQL_ROOT_PASSWORD") { '' } %> #環境変数を参照するように修正 host: db #localhostからdocker-compose.ymlのサービス名に修正 ~~省略~~frontend
アプリケーション作成
npxでNuxtアプリケーションを作成します。
後から追加/変更できるので、このタイミングではEnterキー連打でOKです。$ docker-compose run --rm frontend npx create-nuxt-app . ~~省略~~ create-nuxt-app v2.10.0 ✨ Generating Nuxt.js project in . ? Project name #アプリ名 <Enter> ? Project description #任意 <Enter> ? Author name #任意 <Enter> ? Choose the package manager #Yarn <Enter> ? Choose UI framework #None <Enter> ? Choose custom server framework #None <Enter> ? Choose Nuxt.js modules #(Nothing) <Enter> ? Choose linting tools #(Nothing) <Enter> ? Choose test framework #None <Enter> ? Choose rendering mode #Universal (SSR) <Enter> ? Choose development tools #(Nothing) <Enter>Dockerイメージ作成
アプリケーション作成時にできたDockerボリュームは削除しておきます。
$ docker-compose down --volume # もしくは docker volume rm ボリューム名各ファイルを修正した状態で、Dockerイメージをビルドします。
$ docker-compose builddocker-compose.ymlで
buildを指定しているbackendとfrontendのイメージが作成されたことを確認します。$ docker images --format "{{.Repository}}\t{{.CreatedSince}}" ${NEW_APP}* rails-nuxt-app_frontend About a minutes ago rails-nuxt-app_backend About a minutes agoHello World
最後にDockerコンテナを起動し、Rails、NuxtアプリケーションのHelloWorldを確認します、
docker-compose.ymlで定義したサービスを
-dオプション(デタッチモード)でバックグラウンド起動します。$ docker-compose up -dプロセスが立ち上がっていることを確認します。
$ docker-compose ps Name Command State Ports --------------------------------------------------------------------------------------- rails-nuxt-app_backend_1 /bin/sh -c rm -f /app/tmp/ ... Up 0.0.0.0:3000->3000/tcp rails-nuxt-app_db_1 docker-entrypoint.sh mysqld Up 3306/tcp, 33060/tcp rails-nuxt-app_frontend_1 docker-entrypoint.sh /bin/ ... Up 0.0.0.0:8080->8080/tcp失敗している場合は
docker-compose logsなどで原因を探りましょう。backend
RailsアプリケーションのDBを作成します。
$ docker-compose exec backend rails db:create Created database 'app_development' Created database 'app_test'ブラウザで http://localhost:3000/ を開きます。
frontend
ブラウザで http://localhost:8080/ を開きます。
お疲れさまでした。
次回は GraphQL の導入について投稿したいと思います。
- 投稿日:2019-08-27T19:01:38+09:00
Dockerを使ってRails6環境の構築をしてみる
Rails6のリリースがされていたので、自身のPCに環境を作ってみた。
(環境)
Mac(Mojave 10.14.6)
(参考)
https://docs.docker.com/compose/rails/#define-the-project
に沿って実施。
実施の時は5系の記載だったので、6に置き換えて行う。1.Docker for Macのインストール
(インストール済みならスキップ)
https://docs.docker.com/compose/install/2.Rails / PostgreSQLアプリケーションを設定
Docker Composeを使用してRails / PostgreSQLアプリケーションを設定する。
2-1.プロジェクト定義
開発していくディレクトリにアプリケーションを構築するために必要な4つのファイルを設定する。
・Dockerfile
・Gemfile
・Gemfile.lock
・entrypoint.sh
・docker-compose.yml*今回はPC内のUserフォルダ配下に作成
*プロジェクト名をmyappとして設定$ mkdir myapp $ cd myappDockerfileFROM ruby:2.6 RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list RUN apt-get update -qq && apt-get install -y nodejs postgresql-client yarn RUN mkdir /myapp WORKDIR /myapp COPY Gemfile /myapp/Gemfile COPY Gemfile.lock /myapp/Gemfile.lock RUN bundle install COPY . /myapp # Add a script to be executed every time the container starts. COPY entrypoint.sh /usr/bin/ RUN chmod +x /usr/bin/entrypoint.sh ENTRYPOINT ["entrypoint.sh"] EXPOSE 3000 # Start the main process. CMD ["rails", "server", "-b", "0.0.0.0"]
- Rails6でwebpackerが搭載され、yarnのインストールが必要になったので追記
(参考)https://yarnpkg.com/en/docs/install#debian-stableGemfilesource 'https://rubygems.org' gem 'rails', '6.0.0'Gemfile.lock(空のファイル)entrypoint.sh#!/bin/bash set -e # Remove a potentially pre-existing server.pid for Rails. rm -f /myapp/tmp/pids/server.pid # Then exec the container's main process (what's set as CMD in the Dockerfile). exec "$@"docker-compose.ymlversion: '3' services: db: image: postgres volumes: - ./tmp/db:/var/lib/postgresql/data web: build: . command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" volumes: - .:/myapp ports: - "3000:3000" depends_on: - db2-2.プロジェクトの構築
作成した5つのファイルを利用してdocker-compose runを実行し、アプリケーションを生成する。(ターミナルを使って実施)
$ docker-compose run web rails new . --force --no-deps --database=postgresql : : : 最後にWebpackerのインストール成功メッセージが表示される Webpacker successfully installed ? ?アプリケーション生成後は、以下を実施する。
$ docker-compose build : : : 最後に下記のようなSuccess情報が表示される Successfully built aa99bbad99f9 Successfully tagged myapp_web:latest2-3.データベースの設定と作成
データベースの情報を設定するために、config/database.ymlを変更し、コマンドでDBを作成する。
config/database.yml# 設定箇所のみ抜粋 default: &default adapter: postgresql encoding: unicode host: db username: postgres password: pool: 5 : : development: <<: *default database: myapp_development : : test: <<: *default database: myapp_testデータベースを作成する
$ docker-compose run web rake db:create # 作成が成功すると、以下のコマンドが表示される。 Starting myapp_db_1 ... done Created database 'myapp_development' Created database 'myapp_test'2-4.dockerを起動
dockerを起動し、ローカル環境のページにアクセスする。
docker-compose up実行すると以下のコマンドが表示される。
myapp_db_1 is up-to-date Starting myapp_web_1 ... done Attaching to myapp_db_1, myapp_web_1 : : web_1 | => Booting Puma web_1 | => Rails 6.0.0 application starting in development web_1 | => Run `rails server --help` for more startup options web_1 | Puma starting in single mode... web_1 | * Version 3.12.1 (ruby 2.6.3-p62), codename: Llamas in Pajamas web_1 | * Min threads: 5, max threads: 5 web_1 | * Environment: development web_1 | * Listening on tcp://0.0.0.0:3000 web_1 | Use Ctrl-C to stop => Booting Puma => Rails 6.0.0 application starting in development => Run `rails server --help` for more startup options Puma starting in single mode... * Version 3.12.1 (ruby 2.6.3-p62), codename: Llamas in Pajamas * Min threads: 5, max threads: 5 * Environment: development * Listening on tcp://0.0.0.0:3000http://localhost:3000にアクセスして、ようこそページが表示されれば、構築完了。
その他(アプリケーションの停止)
アプリケーションの停止はプロジェクトディレクトリでdocker-compose downを実行する。
・別のターミナルウィンドウを表示し、プロジェクトディレクトリから実施
$ docker-compose down Stopping myapp_web_1 ... done Stopping myapp_db_1 ... done Removing myapp_web_run_b13a7c5899a1 ... done Removing myapp_web_1 ... done Removing myapp_web_run_5ad07400cf63 ... done Removing myapp_db_1 ... done Removing network myapp_default・downするともう一つのターミナル(docker-compose upした側)も停止の実行結果が表示される
web_1 | - Gracefully stopping, waiting for requests to finish web_1 | === puma shutdown: 2019-08-25 23:58:46 +0000 === web_1 | - Goodbye! web_1 | Exiting myapp_web_1 exited with code 1 db_1 | 2019-08-25 23:58:47.254 UTC [1] LOG: received smart shutdown request db_1 | 2019-08-25 23:58:47.278 UTC [1] LOG: background worker "logical replication launcher" (PID 28) exited with exit code 1 db_1 | 2019-08-25 23:58:47.279 UTC [23] LOG: shutting down db_1 | 2019-08-25 23:58:47.353 UTC [1] LOG: database system is shut down myapp_db_1 exited with code 0
- 投稿日:2019-08-27T17:48:37+09:00
.rubocop.yml を無視して rubocop のルールを1個だけ適用する
rubocop のルールを1個だけ適用するには、コマンドラインオプションの only を使う。たとえば文字列リテラルからなる配列を %w で置き換えるルールを適用するには下記のようにする。
rubocop --only Style/WordArray --autocorrect hoge.rbこれは、rubocop.yml に関係なく実行できるので、チーム内では %w に拘ってないのだが、自分だけはこのルールに従って変換したい、というときに使うことができる。
- 投稿日:2019-08-27T16:49:56+09:00
Rails Tutorialの知識から【ポートフォリオ】を作って勉強する話 #12 ActionMailer, アクティベーション編
こんな人におすすめ
- プログラミング初心者でポートフォリオの作り方が分からない
- Rails Tutorialをやってみたが理解することが難しい
前回:#11 プロフィール編集編
次回:準備中こんなことが分かる
- ユーザ新規登録時のメールによるアクティベーションの方法
- メーラーの使い方
- URLによるトークンとダイジェストの認証方法
- 本番/テスト環境ごとによるメールの設定方法
- 文字列よりシンボルが推奨される理由
一緒に勉強していきまっせ
今回の流れ
- アクティベーションのイメージを掴む
- コントローラ、属性、トークン/ダイジェストを用意する
- メールを生成し、メール内のURLにトークンを仕込む
- トークン/ダイジェストを照らし合わせ、アクティベートする
- ログイン時にもアクティベーションを確認する
- アクティベーションに関するテストを書く
アクティベーションのイメージ
今の仕様だとどんなメールアドレスでも登録が完了してしまう。
だから登録時にメールを送ってアクティベーションしたい...
どうすればよい??というわけでどんな風に行えば実装できるのか考えてみよう。
これまでは登録に成功すると/signupから/users/:idにリダイレクトするだけだった。
この間にアクティベーション用のメールを挟みたい。
アプリはホーム画面にリダイレクトする。
先述しちゃったけど、メール用に必要なパスは1つ。
その他必要なビューなどは存在しない。
よってAcountActivationsコントローラと対応するリソースを生成しよう。bash$ rails g controller AccountActivationsconfig/routes.rbRails.application.routes.draw do # 中略 resources :account_activations, only: [:edit] endRESTに従うと、情報を更新するためにはpatchを使用する。
でもユーザにURLをクリックしてもらう以上それはgetだ。
よってupdateアクションではなくeditアクションを使用する。アクティベーションの手順
- 新規作成時にトークンとダイジェストを生成する
- メールを生成する
- メール内のURLにトークンを忍ばせる
- URLをクリックしたらトークンとダイジェストと照らし合わせる
- 正しければアクティベーション済みにする
以上の動作に必要なものを列挙する。
- アクティベーション用トークン/ダイジェスト
- アクティベーション済みかどうかを確認する属性
- アクティベーション用メール
- アクティベーションするための動作コード
ポートフォリオ#9をご覧いただきたい。
アクティベーションに必要なトークンとダイジェストは同じような手順で生成できる。
対してメールを生成するメーラーは新たに使用する。
混乱する前に頭の中で整理しておくことをおすすめする。それでは1つずつ用意していこう。
AccountActivationの属性を用意する
属性として必要なのはこの2つ。
- activation_digest → アクティベーション用ダイジェスト
- activated → アクティベーション済みかどうかの真偽値
bash$ rails g migration add_activation_to_users activation_digest:string activated:boolean真偽値はデフォルトでfalseにしておく。
db/migrate/[timestamp]_add_activation_to_users.rbclass AddActivationToUsers < ActiveRecord::Migration[5.2] def change add_column :users, :activation_digest, :string add_column :users, :activated, :boolean, default: false end endマイグレーションもお忘れなく。
bash$ rails db:migrateトークンとダイジェストを生成する
トークンとダイジェストの生成に関しては、以前作成したUser.new_tokenとUser.digestでまかなえる。
アクティベーション用にcreate_activation_digestメソッドを作り、これらをまとめておこう。加えてactivation_tokenは仮属性なのでattr_accessorに追加する。
/app/models/user.rbclass User < ApplicationRecord attr_accessor :remember_token, :activation_token # 中略 private def create_activation_digest self.activation_token = User.new_token self.activation_digest = User.digest(activation_token) end end # 中略どうやって新規作成時に生成させるか
以前emailを入力させる際before_saveを使用し、save前にアドレスをdowncaseメソッドで小文字化した。
今回はsave前ではなくcreate前に呼び出したい。
お察しのとおりRailsにはbefore_createというメソッドが用意されている。
...使おうついでにリファクタリングとして小文字化の処理もメソッド化しておく。
最終的にはこうなる。/app/models/user.rbclass User < ApplicationRecord attr_accessor :remember_token, :activation_token before_save :downcase_email before_create :create_activation_digest # 中略 private def downcase_email email.downcase! end def create_activation_digest self.activation_token = User.new_token self.activation_digest = User.digest(activation_token) end endアクティベーション用メールを生成する
メールを生成するにはメーラーというものを使う。
早速生成してみよう。
ついでに次の章で紹介するパスワードリセット用のpassword_resetメソッドも生成しておく。bash$ rails g mailer UserMailer account_activation password_reset生成されたメーラーはコントローラのアクションと似ており、メーラーの各メソッドが同名のビューと対応する形。
Tutorialを参考に改変。app/mailers/application_mailer.rbclass ApplicationMailer < ActionMailer::Base default from: 'noreply@example.com' layout 'mailer' endapp/mailers/user_mailer.rbclass UserMailer < ApplicationMailer def account_activation(user) @user = user mail to: user.email, subject: "【重要】Lantern Lanternよりアカウント有効化のためのメールを届けました" end # この部分は次章 def password_reset @greeting = "Hi" mail to: "to@example.org" end end「なんでaccount_activationに引数与えられるの?」
とちょっと疑問に思った。答えはシンプルでコントローラのアクションと違い、メーラーのメソッドはコントローラ内で自発的に使っていくから(っぽい)。
まあ要は普通に定義したメソッドだから使えるよねって話。メール内のURLにトークンを仕込む
ここからはビューでメール本文を作成しよう。
必要なのはトークンとキーにするメールアドレスを仕込んだURL。
以上を踏まえるとこんな感じ。app/views/user_mailer/account_activation.text.erb<%= @user.name %>さんへ Lantern Lanternにいらして下さりありがとうございます。下記のリンクをクリックして認証を済ませてください。 <%= edit_account_activation_url(@user.activation_token, email: @user.email) %>app/views/user_mailer/account_activation.html.erb<h1>Lantern Lantern</h1> <p><%= @user.name %>さんへ</p> <p>Lantern Lanternにいらして下さりありがとうございます。下記の『認証する』をクリックして認証を済ませてください。</p> <%= link_to "認証する", edit_account_activation_url(@user.activation_token, email: @user.email) %>さてこの部分。
edit_account_activation_url(@user.activation_token, email: @user.email)ルートを確認するとedit_account_activationのURLパターンを教えてくれる。
bash$ rails routes | grep edit_account_activation edit_account_activation GET /account_activations/:id/edit(.:format):id部分には引数が入る。でも今回は第2引数まである。
実はこの第2引数にハッシュを使用するとクエリパラメータを付与することができる。
結果的にこんな感じのURLを生み出す。http://www.example.com/account_activations/q5lt38hQDc_959PVoo6b7A/edit?email=foo%40example.comこれでURLにトークンとメールアドレスを仕込むことができた。
メールのプレビュー
メールができたとはいえプレビューを確認したい。
そのためには設定を変える必要がある。まずdevelopment.rbのこの箇所を、
config/environments/development.rb# Don't care if the mailer can't send. config.action_mailer.raise_delivery_errors = false config.action_mailer.perform_caching = falseこうする。
config/environments/development.rb# Don't care if the mailer can't send. config.action_mailer.raise_delivery_errors = true config.action_mailer.delivery_method = :test host = '『〜自分の環境に合わせる〜』' config.action_mailer.default_url_options = { host: host, protocol: 'https' } config.action_mailer.perform_caching = falseここに注目。
host = '〇〇'この箇所には自分の環境によって変更する必要がある。
具体的にはdevelopment環境のURLを挿入する。
本ポートフォリオではこの部分。
参考にさせていただきました↓
rails チュートリアルの11章の2・2にてクラウドIDEのホスト名がわからない
Railsのconfig/enviroments配下を読んでみるあとはプレビュー用のメソッドもいじる必要がある。
spec/mailers/previews/user_mailer_preview.rbclass UserMailerPreview < ActionMailer::Preview # https://〇〇/rails/mailers/user_mailer/account_activation def account_activation user = User.first user.activation_token = User.new_token UserMailer.account_activation(user) end def password_reset UserMailer.password_reset end endこれで下記のリンクでプレビューを確認できるようになった。
https://〇〇/rails/mailers/user_mailer/account_activation新規作成時にメールを送る
それではメールを送ろう。
手順としては、処理を書いた後、本番環境を整える。
しかし本番環境については、完成後に整える方がスムーズなので後回しにする。メールを送る処理を書く
ユーザ新規作成時にメールを送るようにしよう。
メーラーで作成したメールを送るにはdeliver_nowメソッドを使用する。UserMailer.account_activation(@user).deliver_nowここは慣習的にメソッド化しておこう。
app/models/user.rbclass User < ApplicationRecord # 中略 def send_activation_email UserMailer.account_activation(self).deliver_now endあとはcreateアクションに書き込む。
app/controllers/users_controller.rbclass UsersController < ApplicationController # 中略 def create @user = User.new(user_params) if @user.save UserMailer.account_activation(@user).deliver_now flash[:info] = "認証用メールを送信しました。登録時のメールアドレスから認証を済ませてください" redirect_to root_url else render 'new' end endその他、flashメッセージやリダイレクト先などの変更がある。
これによりいくつかのテストが通らなくなる。
記事後半にテスト関係をまとめたので、この問題はあとで修正する。知識を助けていただきました↓
【Rails入門】Action Mailerのメール送信を初心者向けに基礎から解説アクティベーションを完了させる
メール内のURLをクリックしたらアクティベーション完了にしたい。
やることは2つ。
- トークンとダイジェストと照らし合わせるauthenticated?を編集する
- editアクションに処理を書く
authenticated?を編集する
以前remember_me機能を追加する際、authenticated?メソッドでトークンとダイジェストを照らし合わせていた。
アクティベーションでも同じメソッドが使えるよう、カスタマイズする。app/models/user.rbclass User < ApplicationRecord # 中略 # 以前のauthenticated?を書き換え def authenticated?(attribute, token) digest = send("#{attribute}_digest") return false if digest.nil? BCrypt::Password.new(digest).is_password?(token) end第1引数 → rememberに使うかactivationに使うかなど
第2引数 → 指定したトークン
これで汎用性の高いメソッドに生まれ変わった。sendを使うことで文字列をメソッドとして認識してくれる。
そこに#{}を使うことで、引数によってメソッド名を変化させるという仕組み。再び以前書いたテストが失敗する。
これも記事後半に。それよりもauthenticated?を使用していたcurrent_userを編集しよう。
app/helpers/sessions_helper.rbmodule 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 endauthenticated?では第1引数を指定する際にシンボルを使用している。
ん?なんで?文字列よりシンボルが推奨される理由
第1引数に指定する際はシンボルを推奨する。
理由はざっくりいうとこんな感じ。
- Rubyの内部実装は、速度面で名前を整数で管理している
- シンボルはソース上文字列、内部上整数という性質を持つ
- よって文字列ではなくシンボルを使用する
より詳しい解説↓
Rubyの文字列とシンボルの違いをキッチリ説明できる人になりたいeditアクションに処理を書く
ようやくAccountActivationsコントローラに処理を書ける。
以下の記述でactivatedをtrueにするが、user.update_attribute(:activated, true)ここも慣習にならってメソッド化する。
app/models/user.rbclass User < ApplicationRecord # 中略 def activate update_attribute(:activated, true) end終わったらeditアクションに処理を書こう。
app/controllers/account_activations_controller.rbclass AccountActivationsController < ApplicationController def edit user = User.find_by(email: params[:email]) if user && !user.activated? && user.authenticated?(:activation, params[:id]) user.activate log_in user flash[:success] = "Lantern Lanternへようこそ!" redirect_to user else flash[:danger] = "アクティベーションに失敗しました" redirect_to root_url end end endここに注目。
!user.activated?わざわざこうしているのは、アクティベーション済みにも関わらずtrueが可能になると、リンクを盗み出すだけで攻撃でアクティベーションが成功してしまうから。
ログイン時にアクティベーションを確認する
新規登録時のアクティベーションは完了した。
でもまだログイン時にアクティベーション済みかを確認する処理が書かれていない。
Sessionsコントローラを編集しよう。app/controllers/sessions_controller.rbclass SessionsController < ApplicationController # 中略 def create user = User.find_by(email: params[:session][:email].downcase) if user && user.authenticate(params[:session][:password]) if user.activated? log_in user params[:session][:remember_me] == '1' ? remember(user) : forget(user) redirect_back_or user else flash[:danger] = "メールを確認してアクティベーションを済ませてください" redirect_to root_url end else flash.now[:danger] = 'メールアドレスかパスワードが正しくありません' render 'new' end endこれでアクティベーション関係は終了となる。
本番環境でメールが届くようにする
実際のアプリでメールを送信するにはいくつかやることがある。
- Herokuのアドオンを追加する
- production.rbを編集する
Herokuのアドオンを追加する
Herokuからメールを送信できるようにしよう。
そのためにはアドオンを追加する。bashheroku addons:create sendgrid:starterproduction.rbを編集する
production.rbにこういう記述があると思う。
config.action_mailer.raise_delivery_errors = falseこのあたりをこんな感じに編集する。
config/environments/production.rb# 中略 config.action_mailer.raise_delivery_errors = true config.action_mailer.delivery_method = :smtp host = '〇〇.herokuapp.com' config.action_mailer.default_url_options = { host: host } ActionMailer::Base.smtp_settings = { :address => 'smtp.sendgrid.net', :port => '587', :authentication => :plain, :user_name => ENV['SENDGRID_USERNAME'], :password => ENV['SENDGRID_PASSWORD'], :domain => 'heroku.com', :enable_starttls_auto => true }〇〇の部分はHerokuで自分のアプリを開いたときのURLを挿入しよう。
(https除く)host = '〇〇.herokuapp.com'これで無事動作する。
メーラーのテストを書く
最後にメーラーのテストだ。
ただメーラーをテストするまでにいくつかやることがある。
- FactoryBotにactivated属性を与える
- ホスト側にドメイン名を与える
これが終わったら実際にテストといこう。
FactoryBotにactivated属性を与える
activated属性が新たに追加されたので、FactotyBotに追記しよう。
acitvatedがfalseのユーザも加えてみる。spec/factories/users.rbFactoryBot.define do factory :user do name { "Michael Example" } email { "michael@example.com" } password { "password" } password_confirmation { "password" } activated { true } end factory :other_user, class: User do name { "Sterling Archer" } email { "duchess@example.gov" } password { "foobar" } password_confirmation { "foobar" } activated { true } end factory :no_activation_user, class: User do name { "No Activation" } email { "no@activation.co.jp" } password { "foobar" } password_confirmation { "foobar" } activated { false } end endホスト側にドメイン名を与える
今のままではメールの送信元のアドレスがテスト環境に存在しないことになっている。
設定を変更しよう。config/environments/test.rb# 中略 config.action_mailer.delivery_method = :test config.action_mailer.default_url_options = { host: 'example.com' } # 中略メーラーテストを書く
意外と面倒くさいメーラーテスト。
自動生成されたpassword_resetに関してはコメントアウト。spec/mailers/user_mailer_spec.rbrequire "rails_helper" RSpec.describe UserMailer, type: :mailer do let(:user) { create(:user) } describe "account_activation" do it "renders mails" do user.activation_token = User.new_token mail = UserMailer.account_activation(user) expect(mail.subject).to eq("【重要】Lantern Lanternよりアカウント有効化のためのメールを届けました") expect(mail.to).to eq(["michael@example.com"]) expect(mail.from).to eq(["noreply@example.com"]) expect(mail.body.encoded.split(/\r\n/).map{|i| Base64.decode64(i)}.join).to include("Michael Example") end end # describe "password_reset" do # let(:mail) { UserMailer.password_reset } # end endこれが気になる。
expect(mail.body.encoded.split(/\r\n/).map{|i| Base64.decode64(i)}.join).to include("Michael Example")もちろんこうしたいのは山々だけれど、
expect(mail.body.encoded).to include("Michael Example")これだと失敗する。理由はエンコードの関係。
このままだと訳がわからない文字列を参照することになる。
それをデコードする必要があるからこうなった訳だ。参考にさせていただきました↓
Rails つまづき駆動投稿(TDP)
ActionMailerのメール送信テストをRSpecで行うその他のテストを書く
テスト追加と修正を行う。
- アクティベートしていないユーザをログイン失敗にするテストを追加と修正
- 新規作成時のリダイレクト先をルートに修正
- authenticated?のモデルテストを修正
アクティベートしていないユーザをログイン失敗にするテスト
ユーザをログインさせる際、アクティベート済みとそうでないユーザを分けたい。
以前テストでユーザ情報をpostする際にはメソッドを使用した。
それの修正とリファクタリングを行い、テストを完成させる。spec/requests/users_logins_spec.rbrequire 'rails_helper' RSpec.describe "UsersLogins", type: :request do include SessionsHelper let(:user) { create(:user) } let(:no_activation_user) { create(:no_activation_user) } def post_invalid_information post login_path, params: { session: { email: "", password: "" } } end def post_valid_information(login_user, remember_me = 0) post login_path, params: { session: { email: login_user.email, password: login_user.password, remember_me: remember_me } } end describe "GET /login" do it "fails having a danger flash message" do get login_path post_invalid_information expect(flash[:danger]).to be_truthy expect(is_logged_in?).to be_falsey expect(request.fullpath).to eq '/login' end it "fails because they have not activated account" do get login_path post_valid_information(no_activation_user) expect(flash[:danger]).to be_truthy expect(is_logged_in?).to be_falsey follow_redirect! expect(request.fullpath).to eq '/' end it "succeeds having no danger flash message" do get login_path post_valid_information(user) expect(flash[:danger]).to be_falsey expect(is_logged_in?).to be_truthy follow_redirect! expect(request.fullpath).to eq '/users/1' end # 中略post_valid_informationに引数を定義したので、使用する際は引数を与えよう。
(中略している部分にも忘れずに)
新規作成時のリダイレクト先をルートに修正する
あとは修正だけ。
spec/requests/users_signups_spec.rb# 中略 it "is valid signup information" do get signup_path expect { post_valid_information }.to change(User, :count).by(1) expect(is_logged_in?).to be_falsey follow_redirect! expect(request.fullpath).to eq '/' expect(flash[:info]).to be_truthy endspec/systems/signup_spec.rb# 中略 it "is valid because it fulfils form information" do visit signup_path submit_with_valid_information expect(current_path).to eq root_path expect(page).to have_selector '.alert-info' endauthenticated?のテストを修正
spec/models/user_spec.rb# 中略 describe "User model methods" do describe "authenticated?" do it "return false for a user with nil digest" do expect(user.authenticated?(:remember, '')).to be_falsey end end end最後にテストを走らせておこう。
$ rails specこれがグリーンならアクティベーションは完了。
お疲れ様ー。前回:#11 プロフィール編集編
次回:準備中
- 投稿日:2019-08-27T12:27:30+09:00
Rails6でknockを導入してload関連のエラーが発生したときの対処法
Ruby on Rails 6.0.0 で作成したアプリケーションに、JWTトークンの発行・認証を行う目的で knock をインストールしようとしたところ、loadに関するエラーが発生したので対処法をまとめます。
発生したエラー
現象1
rails generate knock:installを実行するとwarningとerrorが出力される。
config/initializers/knock.rbの生成自体はできている。$ rails generate knock:install Running via Spring preloader in process 14337 [WARNING] Could not load generator "generators/knock/install_generator". Error: expected file /home/user/.rbenv/versions現象2
ApplicationControllerにてKnock::Authenticableをincludeさせると、railsが起動できない。$ rails c /home/user/Documents/develop/knock_test/app/controllers/application_controller.rb:2:in `<class:ApplicationController>': uninitialized constant Knock::Authenticable (NameError)原因
Rails6でのautoloadがzeitwerkモードに変更となったことで、autoloadの挙動がRails5以前と変わったことに起因しています。
Rails 6 zeitwerk autoload problem with gem
対処法
対処法1. 明示的にrequireする[推奨]
上記issueにある通り、明示的にソースをrequireすることでエラーを回避できます。
config/initializers/eager_load_knock.rbrequire 'knock/version' require 'knock/authenticable'対処法2. autoloadをclassicモードに戻す[非推奨]
Rails5以前と同じ方法でautoloadさせることでもエラーを回避できます。
基本的には新しいバージョンに追従すべきなので非推奨ですが、他のgemで同様のエラーが発生する場合などはこちらの方が手っ取り早いかもしれません。config/application.rbrequire_relative 'boot' require "rails" require "active_model/railtie" require "active_job/railtie" require "active_record/railtie" require "active_storage/engine" require "action_controller/railtie" require "action_mailer/railtie" require "action_mailbox/engine" require "action_text/engine" require "action_view/railtie" require "action_cable/engine" require "rails/test_unit/railtie" Bundler.require(*Rails.groups) module KnockTest class Application < Rails::Application config.load_defaults 6.0 config.api_only = true config.autoloader = :classic # ★追記 end endまとめ
こちらの issueで何かしら修正が入ることに期待しつつ、まずは上記のような対策でエラーを回避するしかなさそうです。
- 投稿日:2019-08-27T12:18:30+09:00
Rails6 のちょい足しな新機能を試す70(ActiveModel::Errors#slice! 編)
はじめに
(多分)Rails 6 に追加された新機能を試す第70段。 今回は、
ActiveModel::Errors#slice!編です。
Rails 6 では、ActiveModel::Errorsに インスタンスメソッドslice!が追加されました。Ruby 2.6.3, Rails 6.0.0.rc2 で確認しました。Rails 6.0.0.rc2 は
gem install rails -v 6.0.0rc2 --prereleaseでインストールできます。
(Rails 6.0.0 がリリースされましたが、動作確認当時の最新版は、Rails 6.0.0.rc2 でした。悪しからず)
$ rails --version Rails 6.0.0.rc2今回は適切な例を思いつかなかったので、
rails consoleを使って確認します。プロジェクトを作る
rails new rails_sandbox cd rails_sandboxUser モデルを作る
4つの属性
name,country,cityを持つ User モデルを作ります。bin/rails g model User name email country cityヴァリデーションを追加する
単純に必須入力のヴァリデーションを4つの項目に追加します。
app/models/user.rbclass User < ApplicationRecord validates :name, presence: true validates :email, presence: true validates :country, presence: true validates :city, presence: true endマイグレーションを実行する
bin/rails db:create db:migraterails console で確認する
rails consoleで確認します。
User モデルのインスタンスを作って、validateメソッドを呼び出します。irb(main):001:0> user = User.new => #<User id: nil, name: nil, email: nil, country: nil, city: nil, created_at: nil, updated_at: nil> irb(main):002:0> user.validate => false
slice!メソッドで、countryとcityのメッセージだけ残します。irb(main):003:0> errors = user.errors.slice!(:country, :city) => {:name=>["can't be blank"], :email=>["can't be blank"]}
full_messagesでエラーメッセージを確認するとcountryとcityのエラーメッセージだけになっていることがわかります。irb(main):004:0> user.errors.full_messages => ["Country can't be blank", "City can't be blank"]なお、
ActiveModel::Errors#slice!の戻り値はHashオブジェクトのため、 戻り値に対してfull_messagesは使えません。irb(main):005:0> errors.full_messages Traceback (most recent call last): 1: from (irb):5 NoMethodError (undefined method `full_messages' for {:name=>["can't be blank"], :email=>["can't be blank"]}:Hash)`)試したソース
試したソースは以下にあります。
https://github.com/suketa/rails_sandbox/tree/try070_active_model_errors_slice参考情報
- 投稿日:2019-08-27T08:46:33+09:00
個人開発「positiveな改善案の口コミサイト」
はじめに
未経験からエンジニアを目指しており、勉強中に開発をした個人アプリについて作成理由や機能の解説になります。現在も開発を続けておりますので、途中経過を含めて内容については開発次第、随時更新をしていきます。
※何故やりたいのか、というのは個人的に結構重要視をしているので、若干サービス開発にかける想いの部分が若干長めかと思います。
作成アプリケーションの内容
開発環境
サーバーサイド言語:Ruby2.5.1
フレームワーク:Rails:5.2.2.1
フロントエンド:HTML
CSS (scss)
JavaScript (jQuery)
CSSフレームワーク:bootstrap
データベース:MySQL
サーバー:AWS(EC2 + S3)開発のきっかけ(興味が薄ければ飛ばしていただければと思います)
食べログやオープンワーク等に代表をされる、口コミサイトは今までサービスに対しての情報が全くなかったのを見える化したことで、すごく生活を便利にしてくれたと思います。
個人的には食べログは社会人になってからずっと有料会員で、一定以上に評価をされているかを基準にすることで失敗をしない店の選び方もできるようになりました。
すごく存在価値のあることだと思っています。ただ、段々と既存の口コミサイトを利用するにつれて、問題意識を感じるようになったため、今回開発をしてみました。
特に課題に感じたのが2点あります。①影響力の大きさを要因とした、評価の不確実性
私は前職が人材紹介の法人営業だったのですごく感じるのですが、クチコミサイトは1つの投稿が持つ、影響力がすごく大きいです。選考途中に辞退になるケースの中で、口コミサイトの評判をみて不安になってしまった、という辞退理由は実は結構多いです。
本来はこういった評価は真摯に受け止めて、しっかりと改善をしていくことが重要だと思っています。しかし、現実にはそうではない企業もあり、自社で評価を操作をしてしまっている企業が多いことも事実です。
※正規分布という確率分布があるので、一定の投稿数があれば通常は評価の点数はなだらかな評価の割合になります。
例えば5点満点で、十分なサンプルがありながら1と5の評価が2〜4と比べて飛び抜けて多い口コミの企業とかは要注意です。また、不自然にならないように自然を装って、釣り上げる業者も存在をしています。
こういった企業が多くなると必然的に本質的な改善を目指している企業がますます評価をされない、という状況になってしまいます。本来、口コミサイトが目指す世界観としては「ユーザー側からの評価をして、透明度を増すことで誰にとってもいいサービスを作っていくこと」だと思っていますが、完全にずれてしまっていると思います。②いつまでも残り続ける低評価が、改善をしても企業につきまとってしまう
悪い口コミがいつまでも残って影響を与え続けてしまうのは個人的には企業がすごく可愛そうだと思っています。これもまた、前職での個人的な経験からすごく感じていることです。
こんな経験がありました。
ある上場企業様から、すごく高い評価での内定が出ていた求職者の方がいたのですが、急に辞退をしたい、との連絡をいただいてしまいました。理由は案の定、クチコミサイトを見て評価が低かったのと(3年前の投稿)、過去に不正行為があったからとおっしゃっていました。
企業側の評価も元々かなり高く、何もせずに辞退になってしまうことも企業側にすごく申し訳なかったので、求職者の方に交渉をして、カウンセラーも何とか次の日の20時開始の時間で企業との面談の時間を確保しました。
企業担当の私も正直に相談をしたところ、企業側の回答は「正直に話してもらいありがとうございました、何とかします!」とのことでした。翌日、面談後に求職者の方から連絡があったのですが、なんと辞退は取り消します、とのことでした。
面談の際に何があったのかを聞くと翌日の調整依頼だったのにも関わらず、人事担当の方が急いで社長の面談をセッティングしていただき、面談では「何故不正が当時起きてしまったのか」、「経営陣を一掃した今の経営体制の強固さ」を真摯に話してくれたそうです。それに心打たれて、是非とも入社をしたい、という考えに変わったとのことでした。こちらの企業様のこの姿勢には私自身すごく好感を持ち、それから何名も入社をしていただくことができました。この時にすごく感じたのが、「改善をしていくことの大切さ」や「真摯に対応をしていくことが人との結びつきを強くする」ということです。
この企業様にはすごくよくしていただいていたのですが、反対に応募段階で過去のクチコミサイトの情報だけで判断をされるケースもすごく多く、個人的にはかなりもったいないなと感じました。
この例では上場企業でしたが、特に創業間もない企業は課題は山積みです。そんな中でその時の評価だけを元にして判断をされていては育つ企業も育たないと思います。本アプリの目指す世界観
上記の個人的に感じた課題感を解決して、いいサービスを提供をする意志のある企業が報われる、そんな口コミサイトを作っていければと考えております。
①改善をしていくことがいいサービス
ユーザーが現時点での企業側の評価をされるのではなく、改善をしていくことが評価をされるサービスにします。
ITの進歩で変化の早い世界になっており、現状維持はもはや退化と言われるような中では、いかにして変わっていくことができるかが重要です。
飲食店のアンケートでは、改善案の欄がよくあると思います。ただ、積極的に書いている人をほとんど見たことがありません。
中々面と向かって渡すアンケートではやりづらさもあると思いますが、企業側はすごく意見がほしいと思います。
アプリであれば心理的なハードルが下がると思うので、改善案を提言をしていくスタイルにしていきます。
ユーザー側からしても自分が提言をした内容が、実施をされればすごく嬉しいと思いますし、そうやって作っていったストーリーは唯一無二のものになってくると思います。②前向きな表現を使う
改善案を言うということは悪く考えれば、現時点でのそのサービスにおける弱みの指摘です。ただ、本アプリではユーザーが一方的に評価をするのではなく、応援をしていけるようにしていきたいと思っています。
そのため、攻撃的な投稿が出ないようにすべてポジティブな言い回しにしています。
サービスを利用していてよかった点をGoodPointにしているのはそれほど違和感はない、と思いますが改善点をChance Pointにしているのはそのためです。
また、本アプリではGCP:cloud language natural APIを使うことによって、投稿をした内容がpositiveなものかnagativeなものか判定ができるようになっています。③投稿された改善案の数によってマネタイズをする
本質的な改善を目指すためには双方向でのやり取りが必要不可欠になってきます。
ユーザーが勝手に投稿をしていくのは簡単ですが、それだと改善はまずしないと思うので、企業側のサービスの登録を必須にして、基本的に企業から金銭をもらうモデルを想定しています。
その際に、例えば投稿数3件もらうまでは企業側は無料、といった形にしていけば新規でやり取りをしやすいはずです。
無料でサービスを良くする改善案が入ってくるのなら企業側はまずやると思います。
そして、いくつか入ってきた口コミが良質であれば、続けたくなる(はず)ので、そのまま4件以上の投稿をしてもらうためには有料プラン、みたいな形式にしたいです。
個人的に感じることなのですが、改善をするのはすごく気持ちいいことだと思っています。「あのプロジェクト実は俺がやった」とか「あれは私が変えたんだ」とかのワードの言葉は誰しも聞いたことがあるかと思います。
いつまでも残っている、というのは特に企業側の導入を決めた人にとってはすごく誇らしいものでもあるので、離脱率はそこまで高くないのではないかと勝手に考えています。実装内容
①実装済機能
・一覧表示機能
・詳細表示機能
・投稿機能
・削除機能
・編集機能
・管理ユーザ登録機能(deviseで2つのモデルを使用)
・画像ファイルアップロード機能
・DBテーブルリレーション管理
・ページネーション機能
・コメント機能
・ユーザー登録、削除機能ポジティブ・ネガティブ判定(GCP:cloud language natural API)
・検索機能(ransack)②未実装機能(実装予定)
課金機能(payjp)
フォロー機能
単体テスト
統合テストまとめ
完成途中ではあるものの、スクールの課題と違ってゴールが決まっていないものを作るのはすごく楽しいなと感じました。まだまだ技術力的にできることに限りがあるので、もやもやを感じることは多いですが、色々思いついたことをすぐに実現をできるのはすごく魅力的です。
おまけ(似ている部分があると思ったアプリ)
サービスの質から言えば恐れ多いにもほどがありますが、個人的に似ている部分があるなと思ったサービスです。私のサービスに対してのイメージもしやすくなるかと思うので、いくつか挙げてみます。
①Insight Tech
不満を集めて「お客さまの声」や「レビューデータ」等をAIで分析をして改善につなげるサービスです。この解析をもとに実際にコンサルとかもしているそうです。大量のデータがある分、効果的な提案ができそうです。
最も概念が似ているサービスだと思うのですが、差別化のためにはこちらのサービスが大量のデータを元に分析をしているのに対して、一人ひとりとのストーリーをとにかく着目をしていく必要があると思いました。
②Unipos
前職の時に使っていたサービスです。社内で相手に感謝を伝えるサービスです。何かいい動きをした同僚に対してポイント付きのメッセージを送ることができます。同僚対同僚でのやり取りに使うサービスなので、ユーザー対企業であれば差別化にはつながるかな、とは勝手に思っています。
- 投稿日:2019-08-27T01:47:59+09:00
Ruby on Rails 初級
本日のRailsおさらい
順不同
考え方のみ。
細かなコマンドは別日1、データベース
DBの中には、テーブルという物にデータが保存
されている。またデータをみやすくする為
にレコード(行)とカラム(列)などIDを作
って紐つける。2、DBへのアクセス
DBへはモデルがやりとりを行う。
コントローラーがモデルに接続し、引っ張り出
してビューにわたす。3、DB引き出し方
モデルからDB情報引き出す時は、
モデルの命名規則にそって行う。
●モデルクラス名
先頭は大文字の単数形=>Tweet
モデル作成する時は全部小文字●モデルクラスのファイル名
先頭は小文字の単数形=>tweet.rb
作成してできるファイルはこれ!●テーブル名
先頭は小文字の複数形=>Tweets4、マイグレーションファイル
DBにあるテーブルの設計図
レコード(行)、カラム(列)の表を作る所●レコードは大体数字
1、2、3など
●カラムは名称が多い
名前、年齢、性別など
カラムは一緒に 型 も設定するよ●スキーマファイル
マイグレーションファイルのバージョン
確認用5、DBのテーブル編集
今までで、テーブル作って、テーブルに名称
も作ったから次は編集●Sequel Pro
これ使って書き込みます。
以上!●ターミナルでもできるよ
その事をコンソールって言うねんて6、クラスの継承
よーわからんからまた勉強します。
モデルの作成で作ったやつは全部
アプリケーションリコードを継承してるみたい7、間違えてた所
rails c
コンソール起動してできることは
コード実行できる
メゾッ ド実行できる。
- 投稿日:2019-08-27T01:04:02+09:00
OSSマッチングサイトの開発(未完成)
2019年8月26日時点未完成 開発中!
こんにちは、ITエンジニアの田中です。
マッチングサイトのOSSとしては、Osclassが有名ですが、、、
正直使いづらい。。。でも、それ以外は有名なものがないと思っています。そんな訳で、Ruby on Railsで汎用マッチングサイトを作ってみたいと思います。
概要
次のようなロールを想定しています。
- サイト管理者
- 募集主
- 応募者
募集主が募集アイテムを作成します。
応募者は、募集アイテムを検索し応募することができます。
サイト管理者は、募集アイテムに自由に属性を作成することができます。募集アイテムの基本属性
- 内容
ユーザストーリー
- 応募者がサイトに登録できる
- 応募者がサイトにログインできる
- 募集主がサイトに登録できる
- 募集主がサイトにログインできる
- 募集主がサイトに募集アイテムを登録できる
- 応募者が募集アイテムを検索できる
- サイト運営者がサイトにログインできる
- サイト運営者が募集アイテムに数値の項目を追加できる(汎用項目)
- サイト運営者が募集アイテムに文字列の項目を追加できる(汎用項目)
- サイト運営者が募集アイテムに列挙型の項目を追加できる(汎用項目)
- 応募者が汎用項目で検索できる。
- 応募者が応募できる
- 募集主が成約できる
- 応募者と募集主が連絡できる
開発環境
下記のバージョンで環境を構築。
ruby 2.6.1p33 (2019-01-30 revision 66950) [x86_64-linux] rails 5.2.3基本機能の実装
下記のチュートリアルにて、ログイン関連の基本機能を実装
https://railstutorial.jp/具体的には、、、
- ユーザCRUD
- ログイン・ログアウト
- 投稿アイテムのCRUD
データベース設計
第一ステップ
汎用募集アイテムの構造第二ステップ
応募の構造第三ステップ
成約ソース
https://github.com/you1978/generic_mattaching
MITライセンスのオープンソースプロジェクトなので
プルリクエストガンガン作ってください!お待ちしています。
- 投稿日:2019-08-27T00:41:12+09:00
Ruby on Railsを使って個人開発ver1
Ruby on Rails で個人開発
【目標】
Ruby on Railsを使って簡単なWebサイトを作成する今回は、RailsにPostgreSQLを使用するために少しつまずいた部分を書きます。
【前提】
・Vagrant上にRails、PostgreSQLをインストール済み
・rails new #{アプリケーション名}を実行し、railsサーバーを立ち上げる
・ブラウザにアクセスすると、role "vagrant" does not exist の文字解決方法
1.sudo -u postgres createuser --createdb vagrant を実行
postgres というユーザ名でPostgreSQLにログインする。
そしてcreatedb vagrant でDBを作成する。
これはcreatedbのrole権限の登録を行っているはず。2.rails db:create を実行
データベースを作成する。3.ブラウザへアクセス
無事Railsサーバーが立ち上がり、エラーが出ずにアクセスすることが出来た。
- 投稿日:2019-08-27T00:41:12+09:00
Ruby on Railsを使って個人開発SQL編
Ruby on Rails で個人開発
【目標】
Ruby on Railsを使って簡単なWebサイトを作成する今回は、RailsにPostgreSQLを使用するために少しつまずいた部分を書きます。
【前提】
・Vagrant上にRails、PostgreSQLをインストール済み
・rails new #{アプリケーション名}を実行し、railsサーバーを立ち上げる
・ブラウザにアクセスすると、role "vagrant" does not exist の文字解決方法
1.sudo -u postgres createuser --createdb vagrant を実行
postgres というユーザ名でPostgreSQLにログインする。
そしてcreatedb vagrant でDBを作成する。
これはcreatedbのrole権限の登録を行っているはず。2.rails db:create を実行
データベースを作成する。3.ブラウザへアクセス
無事Railsサーバーが立ち上がり、エラーが出ずにアクセスすることが出来た。
- 投稿日:2019-08-27T00:31:30+09:00
カラムのデータ型の変更(Rails)
環境
windows(64bit)
ruby 2.6.3p62
Rails 5.2.2
Cloud9上で開発はじめに
$ rails g model ... などのコマンドでカラムを作成して、後からカラムのデータ型を間違えてしまったことに気づくことがあると思います。
そんな場合のために、この記事ではカラムのデータ型の変更についてご紹介します。1. 空のマイグレーションファイルの作成
以下のようにして空のマイグレーションファイルを作成します。
$ rails g migration change_data_<カラム名>_to_<テーブル名>僕の場合はEventのテーブルdateカラムのデータ型を変更したかったので次のようにしました。
$ rails g migration change_data_date_to_eventsすると、以下のようなマイグレーションファイルが作成されます。
db/migrate/〇〇_chenge_data_date_to_events.rbclass ChangeDataDateToEvents < ActiveRecord::Migration[5.2] def change end end2. データ型を変更する
僕の場合、dateカラムのデータ型をdatetime型からstring型へ変更したかったので、上記のマイグレーションファイルを次のように変更しました。
db/migrate/〇〇_chenge_data_date_to_events.rbdef change change_column :events, :date, :string end「これで変更完了!」といいたいところですが、最後に一番大事なことを忘れずに!
3. データベースに変更内容を反映
$ rails db:migrateおしまいです。
コンソールなどで変更内容が反映されているか確認してみてください。








