blog.waterlow.work

Ruby, Rails, js, etc...

【LT】Railsのyaml、巨大になる前になんとかしようという内容で発表してきました!

はじめに

最近しごとで巨大なyamlを何とかするという仕事をやっていて、いろいろ思うところがありました。
これは今後の糧にしよう!とおもい、スタートアップRails勉強会で発表してきました。

反応


会話など

  • そもそもmodelが多過ぎる
  • ちゃんとバージョンアップできているのはすごい
  • Railsアプリのコードをきれいに保つにはどうするか

 - 啓蒙や教育を怠らない
 - 機能を絞る(コアドメインで勝負する)

全体通して

抱えていたyaml肥大化問題は、「環境毎に値変えたい」と「秘匿情報でコミットしたくない」と「yamlに書かなくていいことまで書いてる」の3つがあるのかなと気づきました。発表に向けていろいろ整理できたのは良かったです。今後、それぞれに解決策を用意していこうと思います。

Qiitaに初投稿!「hamlでタグを改行しない方法の整理」という記事を書きました

はじめに

タイトルの通り、Qiitaデビューした。前々から投稿したいと思っていたのだが、「ネタがない」「時間がない」などの言い訳をしつつ渋っていましたが、この度初投稿しました。

なんでこの記事を書いたのか

以前働いていたところでは、デザインはデザイナーがhtml, cssまで書き、エンジニアはhamlで実装しているような形でした。 そこではしばしば、デザイナーの書いたhtmlと、hamlで書いて出力されるhtmlが微妙に違うということがしばしばありました。 その原因の1つが、hamlの強制改行によるものでした。

Railsエンジニア1年目の自分は、そもそもhtmlを直接書かないこと、hamlというものがあること、インデントがちょっとずれるだけでエラーでhtmlをrenderできないことなど四苦八苦。改行を操作する方法があるということにたどり着いたのはだいぶ後でした。

そこで、「haml 改行しない」でぐぐると、シンプルな記事がでてくるようになるといいなという思いからこの記事を書きました。

「ワニが改行を食べる」イメージ

答えはいたってシンプルで、> or <の「口が開いている方の改行」を消すという覚え方が良さそうです。 出典は公式ドキュメントです

まとめ

Qiita初投稿を終えて、だいぶハードルが低くなったので、今後も投稿していく

bootstrapとsimple_formのチュートリアル

目的

rails newしてから、bootstrapで管理画面を作るのに一番早い方法が何かを探っていた。
rails_adminやactive_admin等もあるが、今回はある程度自前カスタマイズを想定しているので、全部ビルトインみたいな感じのものは使わない。 bootstrap系のgemとsimple_formを使って、scaffoldでどこまでがんばれるかを見てみる。

対象読者

railsチュートリアル2章までを終えて、generateコマンド等が実行できる人。

バージョン

$ ruby -v
ruby 2.3.3p222 (2016-11-21 revision 56859) [x86_64-darwin14]
$ rails -v
Rails 5.0.1

サンプルなのでrubyバージョンは前後していても動くかと思います!

1. rails new〜bundle install

まずアプリケーションを作りましょう。今回は、テスト書かない、bundle installは後ほどまとめてということで-B-Tというオプションを渡します。
(詳しいオプションたちはrails new -hしていろいろ見てみましょう。)

$ rails new test_simple_form -B -T
create
create  README.md
create  Rakefile
create  config.ru
...
create  vendor/assets/stylesheets/.keep
remove  config/initializers/cors.rb
$ cd test_simple_form

次に、Bundlerで使用するGemfileをテキストエディタで編集します。以下の内容に書き換えてください。

source 'https://rubygems.org'

git_source(:github) do |repo_name|
  repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
  "https://github.com/#{repo_name}.git"
end

gem 'rails', '5.0.1'
gem 'sqlite3', '1.3.13'
gem 'puma', '3.7.0'
gem 'sass-rails', '5.0.6'
gem 'uglifier', '3.0.4'
gem 'coffee-rails', '4.2.1'
gem 'jquery-rails', '4.2.2'
gem 'turbolinks', '5.0.1'
gem 'jbuilder', '2.6.1'
gem 'record_tag_helper', '1.0.0'
gem 'bootstrap-sass', '3.3.7'
gem 'simple_form', '3.4.0'

group :development, :test do
  gem 'byebug', '9.0.6', platform: :mri
end

group :development do
  gem 'bootstrap-generators', '3.3.4'
  gem 'web-console', '3.4.0'
  gem 'listen', '3.0.8'
  gem 'spring', '2.0.1'
  gem 'spring-watcher-listen', '2.0.1'
end

gemをインストールします。

$ bundle install

今回のチュートリアルではbootstrap-sasssimple_formbootstrap-generatorsの機能がみそになります。bootstrap-generatorsは今日時点でrails5対応が終わっていないようなので、generateだけやってgemfileからけしてもらってもOK

2. generate xx

各種初期化用のgenerateコマンドを実行していきます。

まずはbootstrapのスタイルシートとlayoutファイル、scaffold用のテンプレートを作成するbootstrap:installです。
途中application.html.erbを上書くかどうか聞かれますが、OKなのでYを入力してenterを押しましょう。

$ rails g bootstrap:install
...
Overwrite /path/to/app/test_simple_form/app/views/layouts/application.html.erb? (enter "h" for help) [Ynaqdh] Y
...

次にsimple_formの設定ファイルをテンプレート作るコマンドです。
今回はbootstrap用の物を作るので、--bootstrapというオプションを渡します。
途中lib/templates/erb/scaffold/_form.html.erbを上書いていいか聞かれるので、Yを押してenter。bootstrap-generatorsがつくった_form.html.erbもありますが、今回はsimple_formのものを使います。

$ rails g simple_form:install --bootstrap
...
Overwrite /path/to/app/test_simple_form/lib/templates/erb/scaffold/_form.html.erb? (enter "h" for help) [Ynaqdh] Y
...

リソース作成

scaffoldで何かリソースを作ってみましょう

$ rails g scaffold post title body:text published:boolean
$ rails db:create
$ rails db:migrate

そしてサーバを立ち上げていくつかリソースを追加してみましょう! f:id:waterlow2013:20170129234346p:plain

その他

今回のチュートリアルだけで行くと、simple_formは必要ありません。しかし、今後いろいろなformを作るにあたり、アプリケーション全体のformをsimple_formで統一しておくと色々ご利益があるということで、追加しています。

まとめ

railsで開発する場合は、最低限のものを提供するのにものすごく早いスピートで作ることが可能です。
要件が複雑な場合はそのままは使えないのですが、うまく仕事にとりいれていきたいですね。

これだけ抑えればOK!権限管理のDB設計デザインパターン

目的

最近仕事で権限管理の設計をやっていたのだが、設計でかなりはまってしまった。
今後ははまらないように、DB設計や判断基準をまとめておく。

ベースとなるパターン

f:id:waterlow2013:20170127232245p:plain:h300

ユーザとロールは多対1で、ロールとアビリティは多対多に関連している。
権限管理やりましょうという場合にはこれにしておけば大抵の複雑な要求には対応できる。
もしユーザが増えてロールとアビリティの管理が複雑になってきても、DB管理なのである程度自由にやりことができる。
ただし、ちとファーストステップとしてはやりすぎか。

ユーザ-ロールが多対多パターン

f:id:waterlow2013:20170127232544p:plain:h300

前の例に加え、ユーザもロールを多数持つパターン。各権限が互いに素に近い状態、例えばカスタマーサポートとマーケティング、そのどちらも担当する人みたいなケースが有る場合に使える。
アプリケーションが大きくなっていて、権限の数も数十くらいになってきた場合はこれか。

直接アビリティパターン

f:id:waterlow2013:20170127232723p:plain:h300

ユーザ一人ひとりに応じて、できることが完全に異なるパターン。業務が多岐に渡っていて、かつ分業が進んでいる場合に使える。
ただし、ベースパターンでも同様のことはできるため、このパターンが活躍する機会は少ない。

ロールだけパターン

f:id:waterlow2013:20170127232816p:plain:h300 f:id:waterlow2013:20170127232819p:plain:h300 f:id:waterlow2013:20170127232822p:plain:h300

この場合はロールを表すテーブルもしくはカラムを用いてロールの情報のみ保存し、何ができるかはプログラム側で判断する。
アビリティの変化、増減や見直しが少ない場合(社内用管理画面等)に向いている
ユーザ-ロールが多対多、多対1、ユーザにカラム追加など、いろいろあり
このパターンがcancancanと組み合わせるのに良さそう。

admin:booleanパターン

f:id:waterlow2013:20170127232919p:plain:h300

割合的に、管理機能が少なく、管理画面みたいなものが存在しない場合に用いる。
railsチュートリアルとかで出てくる。

no権限パターン

アカウント持っている人は全員がすべてのことをできるパターン。
そもそも使用する人数が少ない場合。
内部向け管理画面はこっちのケースも多い。そもそも必要ない人にはアカウントを発行しないパターン。
できればこれで乗り切りたいので「権限管理したい」という話がきたら、「その権限、本当に必要ですか?」と聞いてあげよう。

まとめ

ベースパターン、ロールだけパターン、admin:booleanパターンだけ覚えておけば、Rails小〜中規模アプリケーションならほぼ対応できそう!

【Railsチュートリアル】慣習的に正しいコードの書き方

目的

Railsチュートリアルで「このコードは慣習的に正しくない」という記述があるが、なぜなのかを説明する。

背景

Railsを仕事で書いていく上で、モデル同士の関連の定義とその使い方をわりと意識しているのだが、いつどこで勉強したのか怪しかった。
出典を明らかにしたかった。

該当の内容

Railsチュートリアルの一説。

https://railstutorial.jp/chapters/user_microposts?version=5.0#code-micropost_validity_test

前提として、以下のような、関連を持つモデルUserMicropostがあるとする。(DBのカラム等は自然に定義されているものとする)

# app/models/user.rb
class User
  has_many :microposts
end

# app/models/micropost.rb
class Micropost < ActiveRecord::Base
  belongs_to :user
end

このときuserに紐づくmicropostを作りたいときは、まず思いつく方法としては以下のようなもの。

user = User.first
Micropost.new(content: "test", user_id: user.id)

しかしこのやりかたは、このコードは慣習的に正しくないRailsチュートリアルで言及されている。
実際は以下のようにする。

user = User.first
user.microposts.build(content: "test")

習慣的に良いコードは何がいいのか

関連が明確になる

まずuser.micropostsというコードはUserからMicropostへの関連を定義していないと動かない。user.micropostsというコード を見るだけで関連が定義されていることが明確になる。

引数を減らせる

user_idを渡さなくて良い

主語をuserにすることができる

最初のレシーバがuserになるため、「ユーザがマイクロポストを作る」という見方ができる

依存が少なくなる

Micropost.newする場合はMicropostというクラス、user_idという属性に依存しているが、user.micropostsの場合はUsermicropostsという関連(インタフェース)に依存させられる。

まとめ

いろいろ理由をつけてみましたが、やはり一番は「慣習的に」なのでしょう。
業務でRailsを書いていると、先人の書いたコードに引っ張られついついこのあたりの「自然な書き方」を忘れがち。
誰に見せても恥ずかしくないようなRailsのコードを書けるよう、日々心がけたいものです。

その他

この辺はRubyMine使うと指摘してくれるのかな?

sendgrid-rubyを使ったSendGridでのメール送信

はじめに

この記事は「Sansan Advent Calendar 2016」7日目の記事です。
昨日はerikoobeさんによる「エンジニア未経験者が Ruby を学んでみた件」でした。
特に「2. 未経験者から見た、エンジニアの世界」は共感できたのと同時に、やる気のある人へのサポートはやっぱり大切だなと感じました。

この記事の概要

SendGridが公開しているgemを使って、webapi経由でメール送信する方法をざっくり紹介する。

背景

SendGridとは?

SendGridクラウドベースのメール配信サービスです。smtpサーバを自前で運用することなくメール送信を行うことが出来ます。
この点ではAmazon Simple Email ServiceやMailChimpも同等の機能を持っています。
また、配信だけでなくバウンス後の処理や開封、クリック数取得等のさまざまことをwebapi経由で行えることも特徴の一つです。

なぜこの記事を書いたか

最近までたくさんメールを送る仕事をしていたのですが、smtp→webapiにかえることで配信時間をかなり圧縮出来たので、ぜひ布教していきたいと思いまとめてみました。

前提

SendGridのアカウント取得、apikeyの発行ができている。

サンプル

以下サンプルコードです。(gemのインストールは適宜おこなってください。)

github.com

require 'sendgrid-ruby'
require 'dotenv'
Dotenv.load
API_KEY = ENV['API_KEY']
USERS = [
  { email: 'to1@example.com', name: 'to1', fullname: 'to_user1' },
  { email: 'to2@example.com', name: 'to2', fullname: 'to_user2' }
]

mail          = SendGrid::Mail.new
mail.from     = SendGrid::Email.new(email: 'from@example.com')
mail.contents = SendGrid::Content.new(type: 'text/plain', value: "%name%さん\nあなたのフルネームは%fullname%です")

USERS.each do |u|
  email, name, fullname = u.values_at(:email, :name, :fullname)

  sp = SendGrid::Personalization.new
  sp.to = SendGrid::Email.new(email: email)
  sp.subject = "#{name}さんこんにちは"
  sp.substitutions = SendGrid::Substitution.new(key: '%name%',     value: name)
  sp.substitutions = SendGrid::Substitution.new(key: '%fullname%', value: fullname)
  mail.personalizations = sp
end

sg = SendGrid::API.new(api_key: API_KEY)
response = sg.client.mail._('send').post(request_body: mail.to_json)

登場するclass

SendGrid::Mail

SendGrid::Mail#to_jsonを呼ぶことで、現在持っているインスタンス変数からHash形式でrequest_bodyを作ります。
SendGrid::Mail#personalizations=は、内部ではArray#pushを使っている。インスタンス変数@personalizationsを書き換えるわけではない。

SendGrid::Email

送信先、受信先のメールアドレスや表示名を扱うのに用いる。

SendGrid::Content

メールの本文、type(text, html)を指定するためのクラス。

SendGrid::Personalization

送信先、件名、substitutions等を扱うクラス。どんな項目が指定できるできるかは以下を参考に。
personalizations

気になる点

インタフェースがいけてない

SendGrid::Mail#personalizations=とかSendGrid::Mail#to_jsonとか、パット見と違う挙動なものが多いのでは??

ソースが読みにくい

gemのプロダクトコードはsendgrid/helpers/mail/mail.rbという1つのファイルに集約されている。

その他

本家のreadmeにはsendgrid/helpers/mail/mail.rbを使わない方法も記載されています。
without-mail-helper-class

参考までに上に書いたサンプルコードのhelper使わない版も書いてみました。

mail_info = {
  from: { email: 'from@example.com' },
  content: [
    { type: 'text/plain', value: "%name%さん\nあなたのフルネームは%fullname%です" }
  ]
}

mail_info[:personalizations] = USERS.map do |u|
  email, name, fullname = u.values_at(:email, :name, :fullname)

  { to:            [{ email: email }],
    subject:       "#{name}さんこんにちは",
    substitutions: { '%name%': name, '%fullname%': fullname } }
end

sg = SendGrid::API.new(api_key: API_KEY)
response = sg.client.mail._('send').post(request_body: mail_info)

SendGrid::APIもだいたい40行くらいのコードなので、sendgrid-rubyを使わない方法もありかなとは思いまいた。 その際はsendgrid-rubyが内部的に使っているruby_http_clientを使うもよし、メールを送信するだけなら全て自分で書いてしまってもいいかなと思います。(webapiにリクエスト送るだけだし。)

まとめ

以上、SendGridが公開しているgemを使って、webapi経由でメール送信する方法でした。 apiクライアントの設計に興味が湧いてきたので、他のgemのソースも読んでみたいと思います!

さいごに

明日はTakeruTakahashiさんの記事です!引き続き「Sansan Advent Calendar 2016」をお楽しみに!

【Ruby】【Rails】RAILS ANTIPATTERNS、chapter3 viewのまとめ

目的

RAILS ANTIPATTERNSをのchapter3(view)の箇所を読んだのでまとめました。

Rails AntiPatterns: Best Practice Ruby on Rails Refactoring (Addison-Wesley Professional Ruby Series)

Rails AntiPatterns: Best Practice Ruby on Rails Refactoring (Addison-Wesley Professional Ruby Series)

PHPitis(PHPっぽくなりがち)

ドメインロジックとか複雑な表示ロジックとか、全部ビュー(app/viewsの下にあるファイル)に書かれがちだよねという話です。

解決策:View Helper勉強しよ!

詳しくは書かないけど、form_for, render, content_for, yield(:symbol) || 'default', content_for?について書かれていました。
form_forは本当にいろいろ使えてすごいのだけど、まだまだ使い切れていない感がある。
content_forの並びで言うと、provideもぜひ使いたいですね。

http://api.rubyonrails.org/classes/ActionView/Helpers/CaptureHelper.html#method-i-provide

解決策:いい感じにモデルにメソッド追加しよ!

以下のようなコードがあるとする。

<% if current_user &&
      (current_user == @post.user ||
      @post.editors.include?(current_user)) && @post.editable? &&
      @post.user.active? %>
  <%= link_to 'Edit this post', edit_post_url(@post) %>
<% end %>

これはモデルにメソッドを定義してあげて、view側は以下のような形にする。

<% if @post.editable_by?(current_user) %>
  <%= link_to 'Edit this post', edit_post_url(@post) %>
<% end %>

すっきり!

メソッドを定義する場所としては、まあまあ当たり前だけどコントローラで使う場合はモデル、そうじゃなければhelperに置くと書いてあった。

そのif文、helperに移動しよ!

app/views/alerts/index.html.erbが以下のようになっているとする。

<div class="feed">
  <% if @project %>
    <%= link_to "Subscribe to #{@project.name} alerts.", project_alerts_url(@project, :format => :rss), :class => "feed_link" %>
  <% else %>
    <%= link_to "Subscribe to these alerts.", alerts_url(format => :rss), :class => "feed_link" %>
  <% end %>
</div>

この場合はapp/helpers/alerts_helper.rbを作って以下のようにメソッドを追加する。(若干仕様変わってる?)

module AlertsHelper
  def rss_link(project = nil)
    link_to("Subscribe to these #{project.name if project} alerts.",
            alerts_rss_url(project),
            class: 'feed_link')
  end

  def alerts_rss_url(project = nil)
    if project
      project_alerts_url(project, format: :rss)
    else
      alerts_url(:rss)
    end
  end
end

view側のapp/views/alerts/index.html.erbは以下。

<div class="feed">
  <%= rss_link(@project) %>
</div>

いつも<div class="feed">が必要な場合は、content_tagを使ってhelperにうつしてしまってもいいとありました。
その場合はlink_toじゃなくてcontent_tag :aをあえて使ってもいいと書いてました。

ちょっとした感想

最後の、「helperに移動しよ!」的な話はいつも悩むところなのと、helperメソッドでcontent_tagをもりもり書くのなら、パーシャルでいいのでは?と思ったりします。
でも今回の= rss_link(@project)が、= render rss_link, project: @projectになるのはイマイチ感がありますね…なんでだろ。

viewにかかわらず、Railsはやりたいことを色んな方法で実装することができるので、一人で作っているときでさえ統一感がなくなることが有ります。rubocopで潰せるところはいいけど、自分が統一感を持って実装出来ているかどうかたまに振り返りながらすすめていきたいですね。