20190529のRailsに関する記事は18件です。

Rspecで突然謎のエラーが

circleCIで通常通り動いていたテストコードが突然動かなくなりましたが解決したのでその方法を書いていきます。

開発環境

Ruby 2.6.2
Rails 5.2.3
Docker 18.09.2
docker-compose 1.23.2

今回発生したエラー

Selenium::WebDriver::Error::UnknownError:
    unknown error: session deleted because of page crash
    from unknown error: cannot determine loading status
    from tab crashed

原因

エラーを調べたところ日本語での記事が少なく詳しくはわからなかったのですが、メモリ不足によってクラッシュしてしまうとのこと。
ブラウザのサイズを小さくすれば解決するとの記事を見て試してみたのですが、僕の場合小さくしても解決しませんでした。

解決方法

rails_helperのchromeOptionsのところに--no-sandbox--disable-dev-shm-usageというargumentsを追加してください。--disable-dev-shm-usageと書くことでChromeは/tmpディレクトリを代わりに使用するようになるみたいです。メモリの代わりにディスクが使用されるので、実行が遅くなる場合があるみたいですがそんなに変わってない気がします。

spec/rails_helper.rb
Capybara.register_driver :selenium_remote do |app|
  driver = Capybara::Selenium::Driver.new(
      app,
      browser: :remote,
        desired_capabilities: Selenium::WebDriver::Remote::Capabilities.chrome(
            chromeOptions: {
                args: [
                    'window-size=500,500', #念の為サイズも小さく
                    'headless',
                    '--no-sandbox', # crush回避
                    '--disable-dev-shm-usage' # crush回避
                ]
            }
        ),
        url: 'http://chrome:4444/wd/hub',
    )
end

これで解決すると思います。

もし間違いがあれば訂正お願いいたします。

参考サイト

https://qiita.com/jb-vasseur/items/d3aa33e08ebba3b9231e
https://stackoverflow.com/questions/53902507/unknown-error-session-deleted-because-of-page-crash-from-unknown-error-cannot

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

【1分で読める】プログラミングスクールに通って1ヶ月で学んだこと(できるようになったこと)、感じたこと

WEBエンジニアに転向するため2019年5月から某プログラミングスクールに通っています。執筆時点で約1ヶ月経過したので、学んだことやら、感じたことやらを書き記します。スクール自体はあと数ヶ月続きます。

プログラミングスクールに通うか迷っている人の参考になればと思います。(進捗はあくまでも自分の場合です!周りを見ると、進捗は本当にバラバラ)

なんでプログラミングスクールに入ったのかーとか諸々は卒業後にまたまとめて書こうかなと!

スペックとか

  • 言語:Ruby
  • 年齢:29
  • 性別:男
  • 5月からプログラミングスクールに通ってるけど、4月15日から事前に学習をスタート
  • プログラミング歴はなし!学生時代に授業でやったくらい
  • 勉強時間は7〜10時間/日くらい。頭が疲れたら、基本的にやらない感じ

学んだこと(できるようになったこと)

Ruby(Rails)を使ってtwitter,facebook,instagramのようなアプリは作れるようになった!!!(コードのコピペじゃない!)

投稿機能はもちろん、投稿のお気に入り機能、ユーザのフォローフォロワー機能を実装したり、LINEみたいなメッセージのやりとり機能を実装したりもできるようになった。Ajaxを使ったりも。

さらにherokuを使えば簡単に全世界に公開もできる。すごすぎるぞ、自分!!

デザインとか、サーバのこととか、セキュリティとか考えるとまだまだかもしれないけど、1ヶ月半くらいで、railsアプリ開発の基本的なことはできるようになったのかなと勝手に思ってます。

あとは、リファクタリングとかコーディング規約を守るとか頑張らないとなーって感じ。
(本当はもっとあるんだろうな…)

感じたこと

  • 想像していたものがブラウザ上で形になるのは楽しい!嬉しい!
  • 上っ面のところしか理解できていない感がとてもある
  • 一人で黙々とやっていたらこんなにハイペース?で勉強を進められないだろうなと実感

→同期、メンターの存在のおかげで頑張ろって思えるし、分からないことも教えあえる。また、一人じゃ気付けなさそうなことも会話から気づきを得ることができる。感謝感謝。
一人ならダラダラと勉強しちゃいそう。。

これからのこと

  • サーバ(AWS)のことや、フロント(Vue.js)のことを学んでいくぞい!
  • RubyやRailsも知れば知るほど、新たな疑問が生まれるのでもっともっと理解を深めていくぞい!
  • 就活も始めるぞい!

乞うご期待!

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

GIF画像検索してブラウザでLGTM画像生成とアップロードをしてMarkdown記法でコピーできるツール作った話

今回の開発でわかったこと&この記事で言いたいこと

JavaScriptはすごい。
firebaseもすごい。
個人開発なら、なんでもかんでも自分で作ろうとしないこと。
世の中の既存のサービスにも目を向けること。
GIFバイナリは面白かった。
いやまじでfirebaseはすごい。

ツール開発経緯

この記事に触発された。

ちょっとそこのあなた! 自分だけのオリジナルLGTMをチームで使ってみませんか?

そうだよね!LGTM画像って動く方がいいよね!めっちゃわかる

でもLGTMって書かれていないGIF画像って素っ気ないよね!

じゃGIF画像検索できて、それをすぐにLGTM画像に生成してMarkdown形式でコピペしたい!!

何ができたのか

できたツールがこれ。

GIF検索してコピペできるLGTM画像生成ツールを作った

ChromeFirefoxでしか確認していません。。

大まかにどうやっているのか

  1. GIF画像をバイナリで取得する。
  2. バイナリを分析して一枚の画像ごとに分ける。
  3. LGTMの文字と一枚の画像をcanvasに描画していく。
  4. 全部くっつけてLGTM GIF画像が出来上がる。
  5. サーバーにLGTM画像をアップロードしURL取得して終わり。

サーバーでは画像を保存することしかしていません。

ブラウザのみでGIF検索、LGTM画像生成、アップロード、Markdown記法クリップボードにコピーをしています。

具体的にどうやってるのか

  1. GIPHY APISearch APIでGIF画像を検索する。
  2. クリックされたら幅200pxのGIF画像をarraybuffer形式でファイルを取得する。
  3. それをomggif.jsを使用して一枚ずつの画像にする。
  4. gif.js で一枚になった画像とLGTM文字をcanvasdrawする。
  5. GIF画像の枚数分、LGTM文字付きの画像をgif.jsaddFrameしたらrenderする。
  6. gif.jsrenderすると完成したGIF画像がblobに変換される。
  7. blobfirebase storageにそのままアップロードする。
  8. 最後にfirebase storagedownloadUrlを取得してクリップボードに保存する。

これを実装するまでの物語

目的:GIF画像検索してそれをLGTM画像にして簡単にMarkdown記法でコピーしてGithubに貼り付けたい!

よくRuby on Rails使っていたのでrmagickとかいう画像編集gemで実装してみる。
できたが、実装が悪いのかGIF画像生成に時間がかかった。
これではたくさん画像生成するとなると遅くなってしまう。。
うーむ。
もっと早く画像編集する方法を探る。
PythonOpenCVに出会う。
早い気がするが、結局画像生成に時間がかかる。
うーむ。
もっともっと早く画像編集したい!
Halideに出会う。
少し触って、「あれ、なにしようとしてたんだっけ??」ってなる。

--数年後--

ちょっとそこのあなた! 自分だけのオリジナルLGTMをチームで使ってみませんか?を見る。
そうだよな。LGTM画像ってLGTMの文字が入ってこそだよなぁ。
うーむ。

--数日後--

そっか、使ってもらう人に画像生成してもらえばサーバーの負荷減るなー。
今に至る。

ライセンス

https://github.com/deanm/omggif MIT License
https://github.com/jnordberg/gif.js MIT License

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

Ruby/Ruby on rails/MysqlをDockerで環境構築

DockerでRuby/Ruby on rails/mysqlの環境構築からデプロイまでできたのでメモしておきます。

(1)Dockerfile作成

Dockerfile

FROM ruby:2.5.3


RUN apt-get update -qq && \
    apt-get install -y build-essential \ 
                       libpq-dev \        
                       nodejs           

RUN mkdir /app_name 

ENV APP_ROOT /app_name 
WORKDIR $APP_ROOT

ADD ./Gemfile $APP_ROOT/Gemfile
ADD ./Gemfile.lock $APP_ROOT/Gemfile.lock


RUN bundle install
ADD . $APP_ROOT

(2)docker-compose.yml作成

docker-compose.yml
version: '3'
services:
  db:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: root
    ports:
      - "3306:3306"

  web:
    build: .
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    volumes:
      - .:/app_name
    ports:
      - "3000:3000"
    links:
      - db

(3)Gemfile作成

Gemfile
source 'https://rubygems.org'
gem 'rails', '5.2.2'

Gemfile.lockも作成
$ touch Gemfile.lock

(4)新しいアプリを作成

$ docker-compose run web rails new アプリ名 . --force --database=mysql --skip-bundle

databese.ymlを編集

database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: password 
  host: db 

Gemfileをbundle install

$ docker-compose run web bundle install

(5)コンテナをビルドと起動

$ docker-compose build # コンテナをビルド

$ docker-compose up # コンテナの一斉起動

※bundle installの後にbuildする

(6)DB作成

$ docker-compose run web rails db:create

DBを作らないとエラーが出る

DBを作成後localhost:3000で初期画面が表示される!

()Herokuでデプロイ

herokuの登録,heroku cliはインストール済みとする

Herokuにアプリ作成

$ heroku create

データベースをMySQLに変更

$ heroku addons:add cleardb

ClearDBアドオンとは,ClearDBというデータベースサービスが提供している,MySQLを使うためのもの。

$ heroku config | grep CLEARDB_DATABASE_URL
mysql:// 〜 reconnect=trueがデータベース情報

mysqlの部分をmysql2に変更

環境変数を変更

$ heroku config:set DATABASE_URL=mysql2:// 〜 reconnect=true     //先ほどのデータベース情報全部
$ git add .
$ git commit -m "update for upload to heroku"

アプリをデプロイ

$ git push heroku master

マイグレーションファイル作成

$ heroku run rake db:migrate

参考

丁寧すぎるDocker-composeによるrails + MySQL on Dockerの環境構築

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

RailsでのセッションとCookieについて

Railsチュートリアルを進めていてセッションとCookieについて整理したかったので自分用メモとして書いた。

セッション

WebアプリケーションではブラウザからサーバへHTTPリクエストを送り、HTTPレスポンスを受け取って画面表示を行う。しかしHTPPはステートレスなプロトコルであるため、同じユーザーから送られた1つ目のリクエストから2つ目のリクエストの情報を引き継ぐことができない。そのためセッションを用いることで、1つのブラウザから連続して送られている一連のリクエスト間で状態を共有できるようにしている。

Railsではsessionメソッドを使うことで実現できる。sessionはハッシュのように扱うことができ、セッションにデータを入れるには任意のキーを指定して値を格納する。

session[:user_id] = @user.id

値を取り出すには下記の通り書く。

@user.id = session[:user_id]

Cookie

複数のリクエストの間で共有したい状態をブラウザ側に保存する仕組み。クライアントが情報を持っているのでブラウザを閉じても破棄されない。また暗号化しないと簡単に読み取られる。ログイン情報をセッションのみで扱うとブラウザを閉じるたびにログインしないといけなくなる。そのためCookieにもログイン情報を持たせてしまい、ブラウザを閉じても状態を保持させるという使い方が一般的。Railsではcookiesメソッドを使ってこの機能を実現する。

RailsでのセッションとCookieの関係

Railsのセッションの仕組みの一部はCookieによって実現されている。ブラウザを閉じると破棄されるようなCookieという形で暗号化されたセッション情報をクライアント側に保存するという仕組み。またRailsではセッションデータの保管場所を複数の選択肢から選ぶことができるが、デフォルトでCookieになっている。そのためブラウザ側で対応するCookieを消せば、セッションはリセットされることになる。

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

サービスクラス(Trailblazerも含めて)での実装がときめかない

はじめに

私のボスが「Trailblazerを使い、Railsのモデルの肥大化問題からサヨナラする」の記事をSlackで共有してきました。それを読んだエンジニア歴が浅い新人がプロジェクトに導入すべきかどうか悩んでいます。

私は直感的に思いました。「ダメだ。私たちにはまだ早い! いきなりTrailblazerでサービスクラスなんか書いていたら、WEBアプリケーションの設計の美しさに気づくことなく一生を終えてしまう。」

なお、ここでいうサービスクラスというのは、「俺が悪かった。素直に間違いを認めるから、もうサービスクラスとか作るのは止めてくれ」の記事にあるような、すべてのビジネスロジックを手続き型プログラミングのように書く実装を意味しています。そして、私にとっては、流行りのTrailblazerのOperationでさえも、そのようなサービスクラスをきれいに整えただけ1のように見えて、ときめきを感じないのです。

Trailblazerについての人気記事のコードを見てみる

先ほどの記事のようなサービスクラスのコードは、日々Qiitaを読んでいるような皆さんにとってはうんざりでしょう。

一方、そのサービスクラスを進化させることによってFat Model問題を解決しようとしているTrailblazerのコードについてはどのような印象をお持ちでしょうか? 

Operationのサンプルコード

はじめにで出てきたTrailblazerについての人気記事「Trailblazerを使い、Railsのモデルの肥大化問題からサヨナラする」で書かれているコードを引用しながら見ていきましょう。

コントローラー

まず、コントローラーは以下のようになっています。

def create
  # ユーザー認証機能をDeviseで作った場合、ユーザーはcurrent_userで取得できる
  run Post::Create, params[:post], current_user: current_user do
    # バリデーションに成功したら(全Stepが成功したら)こちらに入る
    redirect_to action: :index and return
  end

  # バリデーションに失敗したら(いずれかのStepが失敗したら)こちらにくる
  render :new
end

注目のサービスクラス

そして、こちらがサービスクラス(Operation)のコードです。コメント部分はすべて私が書きました。注目すべきは、フォームのバリデーションなども含めたすべてのビジネスロジックが、まるで手続き型プログラミングのように書かれていることです。そして、それらのステップの記述にはstepと呼ばれるDSLが使われています。

app/concepts/post/operation/create.rb
# Operationというのはサービスクラスのこと
class Post::Create < Trailblazer::Operation
  # DSLを使う
  extend Contract::DSL

  # Postモデル用のフォームオブジェクト(Reformと呼ばれる)を記述する。
  # Reformではモデル単位ではなく、フォーム単位でバリデーションを行う。
  contract do
    model "post"

    property :title, validates: { presence: true, length: { maximum: 100 } }
    property :body, validates: { presence: true, length: { maximum: 2000 } }
  end

  # ここのDSLが特徴的。ビジネスロジックをステップごとに記述する。 
  step Model(Post, :new)     # Postモデルを生成
  step :assign_current_user! # post.user = current_user と同等
  step Contract::Build()     # フォームオブジェクト(Reform)をビルド
  step Contract::Validate()  # フォームオブジェクト(Reform)をバリデート
  step Contract::Persist()   # フォームオブジェクト(Reform)を使ってモデルを保存
  step :notify_author!       # ユーザーへの通知

  def assign_current_user!(options)
    post = options["model"]
    post.user = options["current_user"]
  end

  def notify_author!(options)
    # ユーザーへの通知処理
  end
end

その他のコード

フォームオブジェクトであるContractのコードは、元記事をご覧ください。フォームやモデルのバリデーションだけでなく、Strong Parametersの処理もやってくれます。

感想: これが主流になると悲しい...

TrailblazerのOperationでのサービスクラスは、とてもすっきり書かれていて、何をやっているのかがわかりやすいです。プログラマーじゃなくても概要を理解できるレベルだと思います。

しかしですよ! MVCモデルのWEBアプリケーションフレームワークの集大成であり、WEBアプリケーション設計におけるノウハウの塊であるRuby on Railsのクールな機能がほとんど使われていないじゃないですか? 私たちが好きなのはオブジェクト指向ですよ、デザインパターンですよ、そして、オブジェクト同士がメッセージを送り合うコールバックを使う設計ですよ! 手続き型プログラミングみたいに書いてんじゃないよ(涙)

さらに、テストケースを書くならまだしも、一番大切なメインのコードを書くのに独自言語(DSL)を使うのですか! Ruby on Rails作者のDavid Heinemeier Hansson氏は美しいコードを書けるからRubyを選んだと言っています。その美しいコードを書けるRubyを捨ててまでやるのですか(涙)

Rails標準の機能を使って書いてみる

Trailblazerとの比較のために、同じことをRails標準の機能を使って書いてみます。

コントローラー

まずはコントローラーです。Railsの標準で書いた場合、コントローラーのコード量はTrailblazerと比べて少し増えてしまいます。ただ、これから述べるようにモデルをしっかりと書けば、コントローラーが肥大化することはありません。

def create
  form = NewPostForm.new(post_params)
  # ユーザー認証機能をDeviseで作った場合、ユーザーはcurrent_userで取得できる
  form.user = current_user 

  # ここでバリデーション、モデルの保存、ユーザーへの通知が行われる
  if form.save
    redirect_to action: :index and return
  end

  render :new
end

def post_params
  params.require(:post).permit(:title, :body)
end

注目のモデル

そして、注目のモデルです。Railsではコールバックの仕組みが積極的に使われます。ここでは、そのコールバックと、それをバリデーションの目的に特化したその名もバリデーションという仕組みを見ていきます。

コールバック

まずはコールバックです。

普通に書く場合

app/models/post.rb
class Post < ApplicationRecord
  after_save :notify_author
  # ...
  def notify_author
    # ユーザーへの通知処理
  end
end

モデルの保存後にafter_saveで定義したメソッドがコールバックされます。ここでは、投稿(Post)が完了したら、ユーザーにメールなどで通知を行うようになっています。

つまり、ユーザーモデルを保存するだけで、必要なメソッドが必ず実行されるわけです。サービスクラスに書いた場合、コードをコピペして新しいサービスクラスを作ったときに、必要な処理もうっかり消してしまうことがあります。モデルに書いておくとそんな心配もありません。

Fat Model対策をする場合

モジュール

もしモデルの肥大化が気になる場合は、Railsのモジュールの仕組みを使って、モデルを分割することができます。

app/models/post.rb
class Post < ApplicationRecord
  include PostNotificator # モデルにはこれを書くだけ
  # ...
end
app/models/concerns/post_notificator.rb
# 機能をモジュールとして、別のファイルで定義する
module PostNotificator
  extend ActiveSupport::Concern

  included do
    after_save :notify_author
  end

  def notify_author
    # ユーザーへの通知処理
  end
end

元のコードでは、モデルを保存した後の処理はユーザーへの通知だけでしたが、他にも、たとえば「モデルを保存した後にKVSの値も更新する」などの処理も、モデルにinclude KVSUpdaterと記述して、KVSUpdaterモジュールを定義すれば、Postモデルにすっきりと機能を追加することができます。

app/models/post.rb
class Post < ApplicationRecord
  include PostNotificator
  include RedisUpdater # モデルに機能を追加
  # ...
end
app/models/concerns/kvs_updater.rb
module KVSUpdater
  extend ActiveSupport::Concern

  included do
    after_save :update_kvs
  end

  def update_kvs
    # ここにKVSを更新するコード
  end
end
デリゲート

デリゲートの仕組みを使って、特定の処理を別クラスに移譲させることもできます。

app/models/post.rb
class Post < ApplicationRecord
  # notify_authorメソッドの処理を移譲する
  delegate :notify_author, to: :notify
  def notify
    PostNotificator.new(self).notify
  end
  # ...
end

バリデーション

コールバックに続いて、バリデーションです。バリデーションの使い方は、カラムのフォーマット合っているかというようなシンプルものだけではありません。外部APIを呼び出して、その結果によってはモデルを保存しないようなこともバリデーションクラスに書けばすっきりします。

app/models/post.rb
class Post < ApplicationRecord
  # モデルの保存前(バリデート時)にコールバックされる
  validates_with SubscriptionValidator
  # ...
end
app/validators/subscription.rb
class SubsctiptionValidator < ActiveModel::Validator
  def validate(record)
    # このユーザーが課金しているかどうかを外部APIを使って調べるなどの複雑な処理
  end
end

その他のコード

フォームオブジェクトであるTrailblazerのReformは、モデル単位ではなくフォーム単位でバリデーションを行うのですが、Rails標準のActiveModelでも同じようなことができます。

ただ、フォームオブジェクトに関してはReformの方が便利で使いやすいと思います。簡潔に書けるだけでなく、Strong Parametersの処理までやってくれます。元記事のContract(Reform)をご覧ください。

本記事の趣旨からは外れますが、ActiveModelを使ったフォームオブジェクトの実装も書いておきます。

app/forms/new_post_form.rb
class NewPostForm
  include ActiveModel::Model
  include ActiveModel::Attributes # Rails5.1以前ではActiveModelAttributesのgemを使う

  # モデルにあるアトリビュート
  def self.attributes
    [:name, :body, :user]
  end

  attr_accessor *self.attributes

  # モデルにないアトリビュート
  attribute :privacy_policy, :boolean, default: false

  # バリデーション
  validates :privacy_policy, acceptance: true

  def to_model
    Post.new(to_hash)
  end

  def save
    return false unless valid?
    to_model.save
  end

  def to_hash
    self.class.attributes.inject({}) do |hash, key|
      hash.merge({ key => self.send(key) })
    end
  end
end

上記の実装はとても大変な感じがしますね。ActiveModelをTrailblazerのReform並に便利にするには、たくさんハックする必要があります。以下のURLを参考にすれば、self.attributesto_hashのあたりが各フォーム間で共通化できると思います。
ActiveModel attributes - Stack Overflow

感想: やっぱりRailsはクール!

ActiveModelのコードを除き、上記のモデルの書き方はやっぱりクールです。必要なコールバックを定義して、そこをトリガーにして動かすのです。

コントローラーの主な処理は、渡されてきたパラメーターでモデルを作成/更新/破棄することだと思います。これに加えて、モデルを扱う前後でいくつかの処理が必要となります。Railsのモデル(ActiveRecord)では、その前後の処理をコールバックを使うことでロジックを分けて書けるようになっているのです。

コールバックの種類はこちらを参照してください。
Active Record コールバック - Rails ガイド

サービスクラスを作った方がよいケースもある?

ここまで書いておいてアレですが、サービスクラスを作った方がいいケースがあることは認めます。代表的なものはバッチ処理でしょう。

ただ、よく(コントローラーの1つのアクションで)複数モデルを扱う場合もサービスクラスが向いているんじゃないかという意見も聞きますが、複数モデル間でリレーションが張られていればやはりモデルだけで済みます。accepts_nested_attributes_forを定義すれば、1つのモデルを更新するコードを書くだけで他の関連付けられたモデルも更新されます。もちろん、その際、それぞれのモデルのコールバックも呼ばれます。

まとめ

特別なケースを除けば、サービスクラスを作るとおもしろくなくなるという私見から、私はTrailbrazerはあまり使いたくないなぁ...と思います。ただ、時代の流れには逆らえませんし、もしかすると食わず嫌いなだけかもしれませんので、今後使う機会があれば、ぜひ一度がっつりとやってみたいとも思っています。


  1. もちろん、ソースコードをきれいに整えて可読性、凝縮性を上げることが何より大切なのは理解しています。 

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

【Rails】ネストされたcontroller宛てのform_withの書き方

namespaceでnestされたcontroller(Admin::Userなど)のアクション宛てformを
form_withで記述したい場合の雛形になります。

書き方

◆ new → create

new.html.erbのviewからAdmin::UsersControllercreateアクションへ送りたい場合、htmlレベルでは

<form action="/admin/users" method="post" >
  <input type="text" name="user[name]">
</form>

こうなっていてほしいが、form_withではどう記述すればよいか?

① model: を使う方法

model:@userを指定し、url: でpathを指定します。

new.html.erb
<%= form_with model: @user, url: admin_users_path, local: true do |f| %>
  <%= f.text_field :name %>
<% end %>

② model: を使わない方法

scope::userを指定し、 url:でpathを指定します。
scope: :userによってパラメーターのuser[]を生成するようです。

new.html.erb
<%= form_with scope: :user, url: admin_users_path, local: true do |f| %>
  <%= f.text_field :name %>
<% end %>

(ちなみに、scope: :userをなくすと)

new.html.erb
<%= form_with url: admin_users_path, local: true do |f| %>
  <%= f.text_field :name %>
<% end %>

↓htmlレベルでは

<form action="/admin/users" method="post" >
  <input type="text" name="name">
</form>

user[name]ではなく、nameになっています。

◆ edit → update

同様に、edit.html.erbからAdmin::UsersControllerupdateアクションへ送りたい場合、htmlレベルでは

<form action="/admin/users/3" method="post"><input type="hidden" name="_method" value="patch" />
  <input type="text" name="user[name]">
</form>

こんな感じになっていてほしいが、form_withではどう記述すればよいか?

① model: を使う方法

model:@userを指定し、url: でpathを指定します。

edit.html.erb
<%= form_with model: @user, url: admin_user_path, local: true do |f| %>
  <%= f.text_field :name %>
<% end %>

② model: を使わない方法

scope::userを指定し、 url:でpath、method::patchを指定します。

edit.html.erb
<%= form_with scope: :user, url: admin_user_path, method: :patch, local: true do |f| %>
  <%= f.text_field :name %>
<% end %>

(ちなみに、method: :patchをなくすと)

edit.html.erb
<%= form_with scope: :user, url: admin_user_path, local: true do |f| %>
  <%= f.text_field :name %>
<% end %>

↓htmlレベルでは

<form action="/admin/users/3" method="post">
  <input type="text" name="user[name]">
</form>

<input type="hidden" name="_method" value="patch" />が無くなりました。
これにより「htmlメソッドはpatchという体裁で送っているよ」というメッセージが消えたので、railsサーバーはPOSTが届いたと認識します。(詳しくはこちら)
methodがPOST、pathがadmin_user_pathの組み合わせは存在しないので、Routing Errorになるはずです。

結論

form_withではmodel:を使えば空気を読んで自動翻訳くれるのでかなり楽。
しかし、nestされている状態とpathまでは空気読みきれないので、mode:url:のセットを使えばOK.

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

gem 'devise'

gemfile.rb
gem 'devise'
terminal.rb
$ bundle install
$ rails generate devise:install
development.rb
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
terminal.
rails g devise モデル名

create    db/migrate/20190529070217_devise_create_users.rb

このようにマイグレーションファイルが作成されるのでこれをdb:migrateして反映させる

== 20190529070217 DeviseCreateUsers: migrating ================================
-- create_table(:users)
-> 0.0015s
-- add_index(:users, :email, {:unique=>true})
-> 0.0008s
-- add_index(:users, :reset_password_token, {:unique=>true})
-> 0.0009s
== 20190529070217 DeviseCreateUsers: migrated (0.0035s) =======================

*localhost3000/users/sign_up

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

gem 'devise' デバイス実装の流れ自分メモ

まずデータベース作成した後

gemfile.rb
gem 'devise'
terminal.rb
$ bundle install
$ rails generate devise:install

$ rails g controller home(このタイミングでコントローラーを作成した)

development.rb
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
terminal.
rails g devise モデル名

create    db/migrate/20190529070217_devise_create_users.rb

このようにマイグレーションファイルが作成されるのでこれをdb:migrateして反映させる

== 20190529070217 DeviseCreateUsers: migrating ================================
-- create_table(:users)
-> 0.0015s
-- add_index(:users, :email, {:unique=>true})
-> 0.0008s
-- add_index(:users, :reset_password_token, {:unique=>true})
-> 0.0009s
== 20190529070217 DeviseCreateUsers: migrated (0.0035s) =======================

*localhost3000/users/sign_upしてみる

application_controller.rb
before_action :authenticate_user!

認証していないとアクセスを弾くコマンド

ただトップページだけは認証されていなくとも表示したいので、

home_controller.rb
class HomeController < ApplicationController
  skip_before_action :authenticate_user!
  def index
  end
end

としておく。

ここからtaskのモデルを作っていこうと思う

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

railsでコメント機能を作ろう

コメント機能の実装

投稿データ => Image model
コメント => Comment model

投稿フォームは投稿一覧に作成する(images#show)

Model作成

$ rails g model Comment content:string image:references
  • content => コメント格納用
  • image => imageモデルと関連付けるため

(models/comment.rb)

belongs_to :image
validates :content, presence: true
  • belongs_to => imageモデルに属している(自動記入)
  • precense: true => 空白を不可とする

(models/image.rb)

has_many :comments, dependent: :destoroy
  • has_many => たくさんのコメントを持つことができる(1対多)
  • dependent: :destroy => imageが削除されたらcommentsも削除される

Routes設定

resources :images do 
  resouces :comments, only: [:create, :destroy]
end 

生成されるURL
create =>/images/:image_id/comments
destroy =>/images/:image_id/comment/:id

  • image_idをControllerで活用
  • どの投稿の、どのコメントかを判別する必要がるためこのようになる

Controller作成

$ rails g controller comments create destroy
  • createdestroyの機能を指定する(Viewは削除)
private
  def content_params
    params.require(:comment).permit(:content)
  end 
  • ストロングパラメーターを設定する
def create
  image = Image.find(params[:image_id])
  @comment = image.comments.build(comment_params)
  if @comment.save
    flash[:success] = "コメントしました"
    redirect_back(fallback_location: image_url(image.id))
  else
    flash[:danger] = "コメントできません"
    redirect_back(fallback_location: image_url(image.id))
  end 
end 
  • params[:image_id] => URLから取得する(ルーティングで設定)
  • redirect_back(fallback_location: image_url(image.id)) => 直前のURLにリダイレクト(showで投稿しshowに戻る)
def destroy
  image = Image.find(params[:image_id])
  @comment = image.comments.find(params[:id])
  @comment.destroy
  redirect_back(fallback_location: image_path(image)
end 
  • image.comments.find(params[:id]) => 取得したimageのコメントを:idで探す

View作成

投稿フォーム

<%= form_with(model: [@image, @comment], local: true) do |f| %>
  <%= f.text_field :content %>
  <%= f.submit "コメントする", class: "btn btn-primary" %>
<% end %>
  • model: [@image, @comment] => commentimageに紐づいているモデルのため、配列にして記載する必要がある
#ImageControllerで定義
def show
  @image = Image.find(params[:id])
  @comment = @image.comments.build
end

削除ボタン

<% link_to "DELETE", image_comment_path(image_id: @image, id: comment.id), method: :delete %>
  • image_comment_path => 必要な引数を記載 (/images/:image_id/comment/:id)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RailsのService層のクラスをauto_loadpathに含める

環境

  • Ruby 2.4.2
  • Rails 5.1.4

やりたいこと

app
- services/
- neko.rb
- - utils/
- - - hoge.rb
- - users/
- - - test.rb

みたいなserivcesディレクトリ構成があるとして、全てのディレクトリ内のファイルをオートロードパスに含めたいが、デフォルトでは出来ないので、その方法について記載します。

デフォルトでは、servicesディレクトリの直下のみがオートロードパスの対象となっています。

services.rbを編集

config/initializersディレクトリ内にserices.rbを作成します(自分の環境にはありませんでした)。

services.rbを以下のようにします。
rb
Dir.glob("app/services/**/*.rb") do |file|
require_dependency Rails.root.join(file)
end

これで全てのファイルが対象となっているかと思います。

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

【Haml】条件(if)に応じてClassを変更させる方法

例えば、Eventというモデルの編集ページにおいて、編集不可の要素に対して付与するis-disabledというクラスを用意したとします。

.event-panel.is-disabled

Eventがeditable?でないときのみis-disabledを付与する場合は以下のように書きます。

- event = @event
.event-panel{class:('is-disabled' unless event.editable?)}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails herokuデプロイ時のエラーについて

rails初学者です。
herokuにアプリをデプロイしたところ、特定のページでのみエラーとなります。
試行錯誤を繰り返したのですが、自力での解決が難しいため質問させてください。

下記、「heroku logs -t」でのエラー文です。

PG::DatatypeMismatch: ERROR:  argument of WHERE must be type boolean, not type integer

LINE 1: SELECT  "users".* FROM "users" WHERE (1) LIMIT $1

: SELECT  "users".* FROM "users" WHERE (1) LIMIT $1):

app/controllers/posts_controller.rb:89:in `follows'

上記の文面から、postsコントローラーのfollowsアクションで、「WHERE句はinteger型ではなくboolean型ですよ。」との内容だと思うのですが...

修正が必要と思われる箇所はこちらです↓

posts_controller.rb
  def follows
    @user  = User.find_by(params[:id]) #ここが89行目
    @follows = Follow.where(follower_id: @user.id)
    @posts = []
    if @follows.present?
      @follows.each do |follow|
        posts = Post.where(user_id: follow.followed_id).order(updated_at: :desc)
        @posts.concat(posts)
      end
      @posts.sort_by!{|post| post.created_at}.reverse!
      if @posts.nil?
        flash[:notice]="まだ投稿がありません…"
        redirect_to("/posts/index")
      end
    else
      flash[:notice]="誰かをフォローしてみましょう!"
      redirect_to("/posts/index")
    end

説明に不足・見当違いなどございましたらご指摘いただければ幸いです。
ご回答のほど、よろしくお願い致します!

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

みなさんが必ず使う検索機能について・・・

みなさんこんにちは☀️

検索・・・
それは誰でもしたことあるやつです。

インターネットで調べる時、メルカリで欲しいものを検索したい時など。

現代ではスマートフォンによっていつでもどこでも検索することができます。

僕がプログラミング学習しているときでも、
検索を常にしています。
今日はその検索機能をRubyではどう書くのかみていきたいと思います。

検索機能

RubyではLIKE旬を利用します。
LIKE旬とは、曖昧な文字列を検索することができることです。
これは、Whereメソッドと一緒に使います。

実行サンプルを置いておきます。

aから始まるタイトル
where(‘title LIKE(?)’, “a%”)

bで終わるタイトル
where(‘title LIKE(?)’, “%b”)

cが含まれるタイトル
where(‘title LIKE(?)’, “%c%”)

dで始まる2文字のタイトル
where(‘title LIKE(?)’, “d_”)

eで終わる2文字のタイトル
where(‘title LIKE(?)’, “_e”)

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

【スクール生】11日目:みなさんが必ず使う検索機能について

みなさんこんにちは☀️

検索・・・
それは誰でもしたことあるやつです。

インターネットで調べる時、メルカリで欲しいものを検索したい時など。

現代ではスマートフォンによっていつでもどこでも検索することができます。

僕がプログラミング学習しているときでも、
検索を常にしています。
今日はその検索機能をRubyではどう書くのかみていきたいと思います。

検索機能

RubyではLIKE旬を利用します。
LIKE旬とは、曖昧な文字列を検索することができることです。
これは、Whereメソッドと一緒に使います。

実行サンプルを置いておきます。

aから始まるタイトル
where(‘title LIKE(?)’, “a%”)

bで終わるタイトル
where(‘title LIKE(?)’, “%b”)

cが含まれるタイトル
where(‘title LIKE(?)’, “%c%”)

dで始まる2文字のタイトル
where(‘title LIKE(?)’, “d_”)

eで終わる2文字のタイトル
where(‘title LIKE(?)’, “_e”)

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

Rails6 のちょい足しな新機能を試す26(MySQL utf8mb4 編)

はじめに

Rails 6 に追加されそうな新機能を試す第26段。 今回は、 MySQL の utf8mb4 編です。
Rails 6.0.0 では、 rails new -d mysql を実行(データベースにMySQLを指定)したときに encoding が utf8mb4 になります。

Ruby 2.6.3, Rails 6.0.0.rc1 で確認しました。Rails 6.0.0.rc1 は gem install rails --prerelease でインストールできます。

$  rails --version
Rails 6.0.0.rc1

Rails プロジェクトを作る

-d mysql を指定します。

$ rails new rails6_0_0rc1 -d mysql
$ cd rails6_0_0rc1

database.yml を確認する

yda database.yml を確認すると、 encoding が utf8mb4 になっていることがわかります。

config/database.yml
default: &default
  adapter: mysql2
  encoding: utf8mb4
  ...

User の CRUD を作る

utf8mb4 を試すために、実際に User の CRUD を作ります。

$ bin/rails g scaffold User name

データベースの migration

$ bin/rails db:create db:migrate

rails server を実行してユーザーを登録する

bin/rails s を実行して、 http://localhost:3000/users/new にアクセスして User を登録します。
このとき name に ?と? を入力します。

$ bin/rails s

Rails 6.0.0rc1 では無事に登録できました。

rails600rc1.png

Rails 5.2.3 ではエラーになります。

rails523.png

蛇足ですが

name に絵文字はないだろうとか思ったりしましたが、そこはスルーで。
rails g scaffold Book title とかにした方がまだ良かったかもと思わないでもないです...。

スクリーンショットの絵文字とこの記事の絵文字の見え方が違うのは、多分 OS の(フォント?)の違いです。
スクリーンショットは Ubuntu 16.04 の Chrome で取ったものです。

試したソース

試したソースは以下にあります。
https://github.com/suketa/rails6_0_0rc1/tree/try026_mysql_utf8mb4

参考情報

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

color_fieldすご

Articleモデルカラムに色を登録するカラムcolor_1color_2を追加する

color_field

jqueryのカラーピッカーとかを使って色を登録しようかなんて思っていたところRailsが提供しているcolor_fieldというものを見つけた。

これで色を選択できる!こんなかんじ↓

スクリーンショット 2019-05-29 1.27.09.png

_form.html.erb
  <tr>
    <th scope="row">吹き出し1</th>
    <td><%= f.text_field :balloon_1, size: "90x10"  %>
        <%= f.color_field :color_1 %>
    </td>
  </tr>
  <tr>
    <th scope="row">吹き出し2</th>
    <td><%= f.text_field :balloon_2, size: "90x10" %>
        <%= f.color_field :color_2 %>
    </td>
  </tr>

色の表示

<div class = "balloon1" style="background-color:<%= @article_.color_1 %>">
<div class = "balloon1" style="background-color:#b8e1f5">

スクリーンショット 2019-05-29 1.47.20.png

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

color_fieldとインラインでの擬似要素の表示について

吹き出しの背景色をその時の気分で色を決めたい!

変更前がこちら

スクリーンショット 2019-05-29 21.07.38.png

やること

Articleモデルに色を登録するカラムcolor_1color_2を追加する

color_field

jqueryのカラーピッカーとかを使って色を登録しようかなんて思っていたところRailsが提供しているcolor_fieldというものを見つけた。

これで色を選択できる!こんなかんじ↓

画面収録-2019-05-29-1.32.03.gif

スクリーンショット 2019-05-29 1.27.09.png

_form.html.erb
  <tr>
    <th scope="row">吹き出し1</th>
    <td><%= f.text_field :balloon_1, size: "90x10"  %>
        <%= f.color_field :color_1 %>
    </td>
  </tr>
  <tr>
    <th scope="row">吹き出し2</th>
    <td><%= f.text_field :balloon_2, size: "90x10" %>
        <%= f.color_field :color_2 %>
    </td>
  </tr>

色の表示

<div class = "balloon1" style="background-color:<%= @article_.color_1 %>">
<div class = "balloon1" style="background-color:#b8e1f5">

スクリーンショット 2019-05-29 1.47.20.png

これだとまだ吹き出しの3角の部分の色がstyle.cssで指定したままの色なので未完成です。
この吹き出しのCSSはこのようなコードでできています。

style.css
.detail .article .balloon1{
  position: relative;
  padding: 20px;
  border-radius: 10px;
  background-color: #ffb1cd99;
  width: 75%;
  height: 25px;
}

/* 三角アイコン */
.balloon1::before{
  content: '';
  position: absolute;
  display: block;
  width: 0;
  height: 0;
  left: -15px;
  top: 17px;
  border-right: 15px solid #ffb1cd99;
  border-top: 15px solid transparent;
  border-bottom: 15px solid transparent;
}

::beforeってのが擬似要素ってやつでこれのスタイル指定を別で行わなきゃいけないようです。

擬似要素ってなあに?って方はこちら↓
この方の記事がわかりやすかったです!
いろんな仕様の変更とかで:::が混じってわかりにくい記事も多いです
参考:擬似要素と擬似クラス

  <style>.balloon1::before { border-right: 15px solid <%= @article_.color_1 %>; }</style>
  <div class = "balloon1" style="background-color:<%= @article_.color_1 %>"><%= @article_.balloon_1 %></div>

スクリーンショット 2019-05-29 12.06.31.png

できた!

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