RailsはRubyでWEBサイトを作るためのフレームワーク。
異なる条件の複数の件数を表示したいとき。たとえば↓レコードで、Receiptsに載っているproductの数ごとに集計したい。
- 最終型
products.name | count |
おにぎり | 2 |
お茶 | 1 |
- Receipts
id | product_id | |
1 | 1 | |
2 | 1 | |
3 | 2 |
- Products
id | name |
1 | おにぎり |
2 | お茶 |
こうやって書くと明らかにSQLのgroup関数を使おう、となる。が、実際のコードでは気づかずに条件を変えてeachで繰り返す…としてN+1にしてしまうことがある。こうする。
Receipt.group(Product.arel_table[:id]).count
このようなハッシュテーブルを返す。
// product_id => count
{
1=>2,
2=>1
}
product_idがわかっているので、あとはProductをfindして名前を引けばいい。
ブラウザでのエラー表示は、configで設定できる。 デフォルトのproduction, sandbox, stagingではfalseになっていて、エラーは表示されない。 これらの環境で表示するには、↓を加える。
config.consider_all_requests_local = true
たまに必要なことがある。 controllerオブジェクトが柔軟な感じ。
controller.class # => #<Admin::UserController>
controller_name # => users
controller_path # => admin/users
controller.class.included_modules.include?(User::concern)
Emacsから定義ジャンプしたい。 ほかの言語やフレームワークだとLSPだのを使えばいいが、Railsではどうするのかよくわからない。 TAGSファイルを生成して使う。
gem install ripper-tags
ripper-tags -e -R -f TAGS
あとは、C-. で生成したタグを指定すればOK。
gemも定義ジャンプ対象にしたい場合、作業ディレクトリ内にgemのソースコードを置けばよい。
bundle install --path vendor/bundle
Railsアプリ内をEmacsで自由にタグジャンプ! - Qiita
SerializationErrorになる。 メーラーに対して、シリアライゼーションできないオブジェクトは渡すことができない。 RailsからRedisに渡されるが、Redisはkey-value storeの形でないと保存できないから。 Mail and deliver_later doesn’t work with date argument · Issue #18519 · rails/rails
A -< B -< C -< D みたいな関係。
a = A.joins(b: [c: :d])
.where(a: { flag_a: true })
.merge(D.where(flag_d: true))
同じ意味のダサい書き方。
a = a.joins(b: [c: :d])
.where(a: { flag_a: true })
.where(d: { flag_d: true })
開発用環境のサンプル。サクっと起動したい用。 プロジェクトのあちこちで使ってるので、どこかにまとめたほうがよさそうだな。
FROM phusion/passenger-ruby27:latest WORKDIR /tmp ADD Gemfile /tmp/ ADD Gemfile.lock /tmp/ RUN gem update --system RUN bundle install COPY . /home/app/webapp RUN usermod -u 1000 app RUN chown -R app:app /home/app/webapp WORKDIR /home/app/webapp RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
かぶり防止のため3001番ポートの方を変えてブラウザアクセスする。
version: '3'
services:
rails:
container_name: rails
build: .
command: bash -c 'rm -f tmp/pids/server.pid && bundle exec rails s -b 0.0.0.0'
volumes:
- .:/home/app/webapp
ports:
- "3001:3000"
公式のrailsコンテナ内でrails newすればよい。
docker run -it --rm --user "$(id -u):$(id -g)" -v "$PWD":/usr/src/app -w /usr/src/app rails rails new --skip-bundle --api --database postgresql .
依存がないbundle install。 ruby:2.7.5イメージで走らせる。
docker run --rm -v "$PWD":/usr/src/app -w /usr/src/app ruby:2.7.5 bundle install
factoryはテストで使うので、fixtureほど忘れることはない。作り忘れたり、過去もので漏れているものがたまにあるので検知する。
require "factory_bot_rails"
include FactoryBot::Syntax::Methods
include ActionDispatch::TestProcess # # fixture_file_uploadメソッドでエラーになるため必要。
task factory: :environment do
msg = []
errors = []
Rails.application.eager_load!
ApplicationRecord.subclasses.each do |model|
begin
create(model.name.underscore.gsub(%r{/}, '_')) # factoryのメソッド
# UserPayment -> user_payment
# admin/user_payment -> admin_user_payment
rescue => e
errors << e if e.class == KeyError
end
end
puts errors
raise '登録されてないfactoryがあります' if errors
end
fixtureの作り忘れなどよくあるので、seedを実行したあとにチェックするタスクを走らせるとよい。
task lint: :environment do
msg = []
invalid = false
Rails.application.eager_load!
ApplicationRecord.subclasses.each do |model|
msg << "#{model.name} => #{model.count}"
invalid = true if model.count.zero?
end
puts msg
raise 'レコードがないテーブルがあります' if invalid
end
SeedFu.seedを実行するコンテキストでrequire, includeしておけばメソッドが使える。
require "factory_bot_rails"
include FactoryBot::Syntax::Methods
include ActionDispatch::TestProcess
SeedFu.quiet = true
task lint: :environment do
SeedFu.seed("db/fixtures/#{env}")
end
rakeタスクは、普通ターミナルから実行するが、ほかから実行したいときがある(テストとか)。
config.before(:each) do
Rake.application.tasks.each(&:reenable)
end
Rake.application['namespace:command'].invoke
のようにして、実行できる。
Webにおける非同期処理はメールとか、外部とのAPI連携とか、比較的時間のかかる処理で用いられている。 とりあえず画面を返し、待たせないようにする。
sidekiqは非同期タスクワーカー。 Redisはインメモリデータベース。
たとえばRails上でメールを送る処理が走るとき、railsはそのタスクをredisに送り、保持する(キューする)。sidekiqは、キューされたタスクを順次処理していく。 クラウドサービスのRedisを用いることで、ダウンしても未処理のジョブを失わない。
ややこしいがパフォーマンスを考えるうえで必要なので理解しておく。
メソッド | キャッシュ | クエリ | 用途 |
---|---|---|---|
joins | しない | 単数 | 絞り込み |
eager_load | する | 単数 | キャッシュと絞り込み |
preload | する | 複数 | キャッシュ |
includes | する | 場合による | キャッシュ、必要なら絞り込み |
そのテーブルとのJOINを禁止したいケースではpreloadを指定し、JOINしても問題なくてとりあえずeager loadingしたい場合はincludesを使い、必ずJOINしたい場合はeager_loadを使いましょう。
メソッド | SQL(クエリ) | キャッシュ | アソシエーション先のデータ参照 | デメリット |
---|---|---|---|---|
joins | INNER JOIN | しない | できる | N+1問題 |
preload | JOINせずそれぞれSELECT | する | できない | IN句大きくなりがち |
eager_load | LEFT JOIN | する | できる | LEFT JOINなので、相手が存在しなくても全部ロードしてしまう |
includes | 場合による | する | できる | ただしく理解してないと挙動がコントロールできない |
- preload
- 多対多のアソシエーションの場合
- SQLを分割して取得するため、レスポンスタイムが早くなるため
- アソシエーション先のデータ参照ができない
- データ量が大きいと、メモリを圧迫する可能性がある
- 多対多のアソシエーションの場合
- eager_load
- 1対1あるいはN対1のアソシエーションをJOINする場合
- JOIN先のテーブルを参照したい場合
- joins
- メモリの使用量を必要最低限に抑えたい場合
- JOINした先のデータを参照せず、絞り込み結果だけが必要な場合
- includes
- なるべく使わないほうがいい
- 条件によってpreloadとeager_loadを振り分ける
- ActiveRecordのjoinsとpreloadとincludesとeager_loadの違い - Qiita
Railsをどう書くかの参考になりそうなリポジトリ。
- gitlabhq/gitlabhq: GitLab CE Mirror | Please open new issues in our issue tracker on GitLab.com
- rubygems/rubygems.org: The Ruby community’s gem hosting service.
- discourse/discourse: A platform for community discussion. Free, open, simple.
- mastodon/mastodon: Your self-hosted, globally interconnected microblogging community
- diaspora/diaspora: A privacy-aware, distributed, open source social network.
- forem/forem: For empowering community 🌱
Railsのルーティングをdrawを使ってまとめる - Qiita
ファイル読み込みでルーティングのDSLを評価するメソッドを作る。 これによって、ファイルで名前空間を分割できる。
module DrawRoute
RoutesNotFound = Class.new(StandardError)
def draw(routes_name)
drawn_any = draw_route(routes_name)
drawn_any || raise(RoutesNotFound, "Cannot find #{routes_name}")
end
def route_path(routes_name)
Rails.root.join(routes_name)
end
def draw_route(routes_name)
path = route_path("config/routes/#{routes_name}.rb")
if File.exist?(path)
instance_eval(File.read(path))
true
else
false
end
end
end
ActionDispatch::Routing::Mapper.prepend DrawRoute
namespace :admin do
resources :users
end
Rails.application.routes.draw do
draw :admin
end
RubyとRailsにおけるTime, Date, DateTime, TimeWithZoneの違い - Qiita
ネストしたトランザクションでは内側のロールバックが、無視されるケースがある。 トランザクションを再利用するため。 なので、トランザクションを再利用しないように明示すればよい。
ActiveRecord::Base.transaction(joinable: false, requires_new: true) do
# inner code
end
【翻訳】ActiveRecordにおける、ネストしたトランザクションの落とし穴 - Qiita ActiveRecord::Transactions::ClassMethods
↓以下2つは同じ意味。
Faker::Boolean.boolean
[true, false].sample
usersのdeleted_atカラムをinteger型 から datetime型に変える例。
- 一時カラムを作ってそこで値を作成する
- 旧カラムを削除する
- 一時カラムの名前を変えて新カラムにする
ActiveRecord::Base.connection.execute(sql)
を使うと生のSQLを実行できる。
def up
connection.execute 'ALTER TABLE users ADD deleted_at_tmp datetime'
connection.execute 'UPDATE users SET deleted_at_tmp = FROM_UNIXTIME(deleted_at)'
connection.execute 'ALTER TABLE users DROP COLUMN deleted_at'
connection.execute 'ALTER TABLE users CHANGE deleted_at_tmp deleted_at datetime'
end
view内でブレークポイントを設定し、ブラウザ上でコンソールを立ち上げることができるライブラリ。 Railsにデフォルトで入っている。
<% console %>
あとは該当箇所にブラウザでアクセスするとコンソールが立ち上がる。 再実行性がないので、テストでやるのが一番だとは感じる。
バッチ処理でいじった場合は更新するとよくないことがある。# Active Record レベル
ActiveRecord::Base.record_timestamps = false
# モデルのみ
User.record_timestamps = false
# インスタンスのみ
user.record_timestamps = false
バリデーションが不要なとき、 user.save!(validate: false)
とすると無効化できる。
データを不整合を直したいけどほかのバリデーションにかかる、ようなときに使う。
あるいは assign_attribute
でもよい。
モデルバリデーションがかかっていても、既存のレコードはnilを含む可能性がある。 モデルバリデーションは入出力のみ監視する。だから既存レコードに残っている可能性がある。 この場合、編集できなくて不便。検知してテーブルにも制約をかけると安全になる。DBバリデーションは、既存レコードにも入ってないことを保証できる。
直にテーブルの制約を辿る方法がわからないのでレコードを探索する感じになった。レコードがたくさんある環境で実行すると検知できる。全部辿るのでクソ重い。
msgs = {}
Rails.application.eager_load!
ApplicationRecord.subclasses.each do |model|
presence_validates = model.validators.select { |v| v.class.to_s.include?('ActiveRecord::Validations::PresenceValidator') }
presence_validates.each do |presence_validate|
model.all.find_each do |record|
msgs["#{record.class} #{presence_validate.attributes.first}"] = '❌ presence: trueあるのにnilレコードがある><' unless record.send(presence_validate.attributes.first)
end
end
end
pp msgs.sort # [["User name", "❌ presence: trueあるのにnilレコードがある><"]]
まずnilをなくす。それからテーブルのバリデーションを追加する。
APIサーバとしての利用、バックとフロントの分割が主流になっている。 採用者がRailsのバックエンド開発者を探している、というときはAPI開発経験がある人材を探しているといえる。
system specでスクショをとって確認する。 わざわざ用意して確認しない。
TDDを徹底し、一切ブラウザ確認せずにプロダクトを開発した、という偉人もいる。
zeitwerkはオートロードするgem。Rails 6で使われている。 Railsでrequireしなくていいのはこれを使っているから。 fxn/zeitwerk: Efficient and thread-safe code loader for Ruby
デフォルトスコープを無視できる。
User.order(:id).reorder(:updated_at)
開発用データにはいくつかの方法がある。
- seedデータで用意する。毎回必要なときにresetして開発する
- クリーンな環境で再現性が高い開発を行える。
- 早い
- 本番データに近いデータで行う
- デザインや性能の問題に気づきやすい
- ユースケースがイメージしやすい
- データの準備が楽
- 整合性のメンテナンスが必要
テーブルのコピーを作るので重い。 bulk: trueをつけるとALTER TABLEをまとめるので高速になる。
def up
change_table :legal_engine_forms, bulk: true do |t|
...
end
end
特定の機能に対して、関係したテーブルを複数つくるとき、プレフィクスのような形でモデル名やテーブル名を決めることがある。 admin_user、admin_page、admin_permissionとか。 こうすることの問題点: 衝突を避けるためにmodel名とテーブル名が長くなる。ディレクトリも見にくくなる。一語だとまだいいのだが、複数名になるとつらくなる。
解決のためには、moduleを定義し、内部でtable_name_prefixを設定するといい。
module Admin
def self.table_name_prefix
'admin_'
end
end
module Admin
class User < ApplicationRecord
end
end
こうするとモデル名はAdmin::Userで、テーブル名はadmin_usersになりわかりやすい。
rails runner "User.first"
rails r "User.first"
サービスクラス化したコマンドを実行するときに使える。
constraints(-> (req) { req.env["HTTP_USER_AGENT"] =~ /iPhone/ }) do
resources :iphones
end
ActionDispatch::Routing::Mapper::Scoping
redirect設定やリファクタリングでroutesを大量に変更して、挙動の変更を追いたい場合。 rails routesの結果のdiffを取れば、楽に確認できる。
データベースのユニーク制約を使って作成、できなければ初めの1件を取得する。
find_or_create_byでは作成されるまでに別プロセスによって作成されている可能性があったので、その問題を解決した処理。
create_or_find_by!
はエラーの時に例外が発生する。
User.create_or_find_by(name: 'aaa')
create_or_find_by | Railsドキュメント
assetsは相対パスが利用されないので絶対パスで検索してヒットしなければ未使用と判断できる、とのこと。
namespace :assets do
desc 'prune needless image file'
task 'prune:images' => :environment do
base = Rails.root.join('app/assets/images/')
Dir[Rails.root.join('app/assets/images/**/*.{jpg,jpeg,gif,png,svg}')].each do |path|
target_path = path.to_s.gsub(/#{base}/, '')
puts "execute: git grep '#{target_path}'"
res = `git grep '#{target_path}'`
if res.empty?
puts "execute: rm #{path}"
FileUtils.rm path
puts '=> removed'
else
puts '=> used somewhere'
end
puts
end
end
end
超強い人が言っていたメモ。 コマンドを組み合わせて一気に置換して検討していく。
git grep -l '2\.6\.5' | xargs sed -i 's/2\.6\.5/2.7.1/g'
vendor/bundle を削除して、bundle install。 マイナーバージョンを変更した場合は .rubocop.yml の RUBY_VERSION を修正(parser gemの指定)。
formを共通化しているようなとき。 このカラムはedit時のみ出したい、というようなことがある。
form_for do |f|
f.number_field :position if @content_category.persisted?
end
validates :user_id, presence: true, :on => :create
Time.zone.yesterday
Time.zone.today.ago(7.days)
Blogにuser_idを後から追加したい、みたいなとき。User -< Blog。 最初にnullableで外部キーを作成する。
次に、新規作成時にmodelでvalidationをかける。 すると既存レコードの外部キーはnull、新しくできるレコードは外部キーありという状態になる。 外部キーなしが増えることはない。移行をする。 nullのレコードがゼロになってから外部キー制約をつけて関連カラム追加完了。
レコードがすでに入っているテーブルの関連を変更する場合。 たとえば、blogs >- somethings >- users を blogs >- users というような。somethingsテーブルは何もしてないので削除したい、とする。 何も考えずにやると、一気にすべてを切り替えることになりがち。
悪い例を示す。
- 最初に関連カラムを変更する。
belongs_to :user # 旧 belongs_to :something
- 旧関連を使ってたアプリケーション側をすべて変更する。MVCすべて。
- 新しい関連カラムは空で、旧データを移行しないといけない。移行は↑のデプロイと同時にしないと不整合になる。デプロイと移行スクリプトの間の変更は無視されるから。
- 1~3をまとめて一気にリリースする
ということで、大量な複数層の変更をぶっつけ本番でしないといけなくなる。途中で嫌になるだろうし、運が悪ければミスって大変なことになる。
ではどうするか。根本的なアイデアは、2つの関連を同時に保持しておくことだ。 同時に持っておけば、大丈夫なことを確認してから関連を変更するだけでいい。そうやって遅延させることで、一気にいろいろな変更をしなくてよくなる。
具体的にどうやるか。良い例。
class Blog < ApplicationRecord
before_save do
self.user_id ||= something.user_id
end
end
としておくと、保存時にblog.user_idとblog.something.user_idの両方に関連がコピーされる。somethingsを経由しないでよくなる。
既存データについても処理を追加しておく。
class User < ApplicationRecord
def migrate
self.user_id ||= something.user_id
save!
end
end
そして、全Userでmigrateを実行すれば既存データにも新しいカラムが入る。
既存データと新しく作成されるレコードをおさえたので、新旧2つの関連カラムは完全に同等になる。 ここまででマージ、リリースする。 問題ないことを確認したあとで、新旧カラムが使える状態を活かしてアプリケーション側の変更…実際の関連の変更をやる(一番の目的の箇所)。 ここまででマージ、リリースする。
その後、移行処理とカラムを削除して片付ければ完了。(あるいは移行処理は前の時点で消す) 関連カラムだけでなく、何かカラムを移すときにはすべて同様にできる。
実際のタスクでは、migration処理をする箇所は複数になるので前もって調査が必要。
カラム名変更とアプリケーション側の変更を分け、変更範囲を狭める。 alias_attributeを追加する。すると、新しいカラム名でもアクセスできるようになる。 依存しているほかのアプリケーションの変更をする(new_user_idに書き換える)。
alias_attribute :new_user_id, :typo_user_id
それらを書き換えたらマージ、リリースする。 その後、カラム名を書き換えるマイグレーションを作成する。使っている箇所はないので安全に変更できる。 マイグレーション後、alias_attributeを削除する。
最初にmodel クラス名を変更し、テーブルの参照先に変更前のものを設定する。
class Blog_After < ApplicationRecord
self.table_name = :blog_before
end
すると、アプリケーション側だけの変更で、DBの変更はない状態で動作上の変更はなくなる。 次にアプリケーションの、ほかの依存している箇所を修正する。 ここまで1つのPRにする。
テストが通ったりリリースできたら、テーブル名変更のマイグレーションを作成し、modelでのtable_name設定を削除するPRをつくる。 安全に変更が完了する。 テーブルの変更と、アプリケーションの変更を同時にやらないと安全だし分割できてすっきりする。
paper-trail-gem/paper_trail: Track changes to your rails models 変更や差分、変更時の何らかの情報(つまり、作業者とか)を保存、閲覧できる。
ankit1910/paper_trail-globalid: An extension to paper_trail, using this you can fetch actual object who was responsible for this change paper_trailの拡張。変更したか取得できるようになる。
Railsでいうところの id
のこと。Rails5 からはbigintで設定されている。
主キーとして使う人工的な値、というのがポイント。
サロゲートキー(surrogate key)とは - IT用語辞典 e-Words
サロゲートキーとは、データベースのテーブルの主キーとして、自動割り当ての連続した通し番号のように、利用者や記録する対象とは直接関係のない人工的な値を用いること。また、そのために設けられたカラムのこと。
たいていの場合はコメントでロールバックできないなどと書けばよいが、rollbackが破壊的な動作になる場合があるのでdownに書く。
def down
raise ActiveRecord::IrreversibleMigration
end
Railsのmigrationで後からNULL制約を設定する - Qiita
null制約追加には、 change_column_null
を使う。
null制約だけ追加すると変更前にnullだったレコードでエラーになってしまうので、同時にdefaultを設定するとよい。
class ChangePointColumnOnPost < ActiveRecord::Migration[5.2]
def change
change_column_null :posts, :point, false, 0
change_column_default :posts, :point, from: nil, to: 0
end
end
change_column_null(table_name, column_name, null, default = nil)
migrationファイルは一部DSLが扱われるだけで普通のrubyファイルと変わらない。 データベースの不整合を解消することにも使える。
def up
Blog.unscoped.where(user_id: nil).delete_all
end
というように。 環境別にconsoleでコマンドを実行する必要がないので便利。
unscoped
はdefault_scopeを無効化する。
unscoped (ActiveRecord::Base) - APIdock
class Post < ActiveRecord::Base
def self.default_scope
where :published => true
end
end
Post.all # Fires "SELECT * FROM posts WHERE published = true"
Post.unscoped.all # Fires "SELECT * FROM posts"
Post.unscoped {
Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10"
}
双方向の関連付けの不整合を防ぐ関連オプション。belongs_to, has_many等ではデフォルトでオンになっているよう。
class Category
has_many :blog
end
class Order
belongs_to :category
end
c = Category.first
b = c.orders.first
c.title = "change"
c.title == b.category.title #=> false 値は異なる
c.equal? b.category #=> false 同じオブジェクトでない
inverse_ofを使うと同じオブジェクトを使うようになる。
よくわからない。 全部辿る方法は色々応用が効きそう。
desc '外部キーの整合性を検証する'
task extract_mismatch_records: :environment do
Rails.application.eager_load!
ApplicationRecord.subclasses.each do |model|
model.reflections.select { |_, reflection| reflection.is_a?(ActiveRecord::Reflection::BelongsToReflection) }.each do |name, reflection|
model_name = model.model_name.human
foreign_key = reflection.options[:foreign_key] || "#{name}_id"
unless model.columns.any? { |column| column.name == foreign_key.to_s }
puts "💢 #{model_name} には #{foreign_key} フィールドがありません"
next
end
parent_model_class_name = reflection.options[:class_name] || reflection.name.to_s.classify
parent_model = parent_model_class_name.safe_constantize
unless parent_model
puts "💢 #{model_name} が依存している #{parent_model_class_name} は参照できません"
next
end
parent_model_name = parent_model.model_name.human
begin
# NOTE: 親テーブルのIDとして存在しない外部キーの数を照会
relation = model.unscoped.where.not(foreign_key => parent_model.unscoped.select(:id)).where.not(foreign_key => nil)
sql = relation.to_sql
count = relation.count
if count.zero?
puts "💡 #{model_name} の #{parent_model_name} の外部キーは整合性が保証されています" unless ENV['ONLY_FAILURE']
else
puts "💣 #{model_name} の #{parent_model_name} の外部キーで不正なキーが #{count} 件 設定されています"
end
if ENV['DEBUG']
puts "=> #{sql}\n"
puts
end
rescue StandardError
# NOTE: マスタデータの場合はスキップ
puts "🈳 #{model_name} の #{parent_model_name} の整合性の検証をスキップしました" unless ENV['ONLY_FAILURE']
end
end
end
end
Reflectionクラスはアソシエーション関係のmoduleのよう。 https://github.com/kd-collective/rails/blob/f132be462b957ea4cd8b72bf9e7be77a184a887b/activerecord/lib/active_record/reflection.rb#L49
Reflection enables the ability to examine the associations and aggregations of Active Record classes and objects. This information, for example, can be used in a form builder that takes an Active Record object and creates input fields for all of the attributes depending on their type and displays the associations to other objects.
Reflectionを使用すると、Active Recordのクラスやオブジェクトの関連付けや集計を調べることができます。この情報は、例えば、Active Recordオブジェクトを受け取り、その型に応じてすべての属性の入力フィールドを作成します。他のオブジェクトとの関連を表示するフォームビルダーで使用できます。
Reflectionに関する記事。 Railsのコードを読む アソシエーションについて - Qiita
ネストしてクエリを発行してるときは何かがおかしい。
- parent_category -> category -> blog のような構造
parent_categories.each do |parent_category|
parent_category.categories.each do |category|
category.blogs.each do |blog|
@content << blog.content
end
end
end
- parent_category -> category -> blog
Blog.joins(categories: category)
.merge(Category.where(parent_category: parent_large_categories))
Migrationファイルは変更しないのが基本だが、数が多い場合、 rails migrate:reset
に時間がかかる。
db/schema.rbの内容を、最新のタイムスタンプのマイグレーションにコピーする。
- つまり現在のDB状況が、そのまま1つのmigrationとなる。DSLが同じなので問題ない。
- migrationのタイムスタンプはすでに実行済みのため、動作に影響しない。
Gemfileのgroupキーワードは、指定環境でしかインストールしないことを示す。
group :development do
gem 'annotate', require: false
end
なので環境を指定せずにテストを実行したとき、gem not foundが出る。実行されたのがdevelopment環境で、テストのgemが読み込まれてないから。 RAILS_ENV=test
がついているか確認する。
論理削除は削除したときレコードを削除するのではなく、フラグをトグルするもの。 逆に物理削除はレコードから削除すること。
論理削除のメリットは、データが戻せること。
が、データベースの運用的に、後から問題となることの方が多い。
- 削除フラグを付け忘れると事故になる。削除したはずなのに表示したり、計算に入れたりしてしまう
- データが多くなるためパフォーマンスが悪くなる
Railsではgem act_as_paranoidを使って簡単に論理削除処理を追加できる。deleted_atカラムを論理削除を管理するフラグとして用いる。
- find
- 各モデルのidを検索キーとしてデータを取得するメソッド。モデルインスタンスが返る
- find_by
- id以外をキーとして検索。複数あった場合は最初だけ取る。モデルインスタンスが返る。
- where
- id以外をキーとして検索。モデルインスタンスの入った配列が返る。
acts_as_listは順番を管理するgem。 brendon/acts_as_list: An ActiveRecord plugin for managing lists.
順番の生成と、操作を可能にする。 modelに順番カラムを指定すると、create時に自動で番号が格納される。 逆にフォームで番号格納しているとそれが優先して入るため自動採番されない。 new時には番号フォームを表示しないなどが必要。
pluck
は、各レコードを丸ごとオブジェクトとしてとってくるのではなく、引数で指定したカラムのみの 配列 で返すメソッド。
pluck | Railsドキュメント
select
はカラム指定というところは同じだがオブジェクトを返す。
1つ1つ処理するのではなくて、同時に複数のレコードを処理することで高速化する。
メモリに全体を展開するのでなく、ある数ずつ展開してメモリ消費を抑える。
find_each | Railsドキュメント … 1件ずつ処理。 find_in_batches | Railsドキュメント … 配列で処理。
parallel gemによって。
require 'parallel'
result = Parallel.each(1..10) do |item|
item ** 2
end
- /rails/info/routes routes一覧。
- /letter_opener(自分で設定する) 送信したメール一覧を見られる。 gemが入ってる場合。 ryanb/letter_opener: Preview mail in the browser instead of sending.
- rails/mailers/ Action Mailerのプレビューを見られる。 previewを準備しておくといちいち送信せずとも、ローカルでダミーが入った文面を確認できる。
class_name
は開発環境でしか使えない。
gemによってはそういうパターンで使えないことがあることに注意しておく。
class_name method is defined by yard gem. it works only development env.
rails console -s
としてconsole起動すると、sandbox-modeになりコンソール内のDB操作が終了時にリセットされる。
便利。
springはキャッシュを保存して次のコマンド実行を早くするgem。 テストも高速化できるので便利だが、たまに壊れて反映しなくなったりする。
まずspringを止めて確認する。
bundle exec spring stop
テストがある程度の長さを超えると、メモリの量が足りなくなってエラーを出す。 特にMacだと起こるよう。
ulimit -n 1024
走らせてエラーがないかチェックする。
namespace :db do
namespace :seed_fu do
desc 'Verify that all fixtures are valid'
task lint: :environment do
if Rails.env.test?
conn = ActiveRecord::Base.connection
%w[development test production].each do |env|
conn.transaction do
SeedFu.seed("db/fixtures/#{env}")
raise ActiveRecord::Rollback
end
end
else
system("bundle exec rails db:seed_fu:lint RAILS_ENV='test'")
raise if $CHILD_STATUS.exitstatus.nonzero?
end
end
end
end
どのgemのメソッドかわからないときに source_location
が便利。
https://docs.ruby-lang.org/ja/latest/method/Method/i/source_location.html
character.method(:draw).source_location
環境を指定して、リセットを行う。 データの初期化にseed_fu gemを使っている。
bundle exec rails db:migrate:reset && rails db:seed_fu
gemのupdateやマイグレーションが起きたときにやる。 どこかで定型化して一気に実行するようにする。
git checkout develop && bundle install && bundle exec rails db:migrate
scopeはクラスメソッド的なやつ。
インスタンスには使えない。 User.scope...
Active Record クエリインターフェイス - Railsガイド
スコープを設定することで、関連オブジェクトやモデルへのメソッド呼び出しとして参照される、よく使用されるクエリを指定することができます。
valid?
はAction Modelのバリデーションメソッド。
Ruby on Rails 6.1 / ActiveModel::Validations#valid? — DevDocs
引っかかってたらfalseになる。
オーバーライドしてしまいそうになるメソッド名なのに注意。
特定の条件だけで発動するvalidation + 条件。`with_options: if`内で`if`を使うと、中のif条件が優先して実行されるため、こう書く必要がある。
validates :term_date, date: { after: proc { Time.zone.now } }, if: proc { |p| p.term_date? && p.sellable? }
SQLがたくさん実行されて遅くなること。ループしているとレコードの数だけSQLが発行され、一気に遅くなる。 includesを使うと少ないSQLにまとめられる。 https://qiita.com/hirotakasasaki/items/e0be0b3fd7b0eb350327
Page.includes(:category)
dependent: destroy
だと子のデータもすべて破壊して整合性を保つ。
それでは具合が悪いときもあるので、消さないようにする。
has_many :contents, dependent: :restrict_with_error
あるいは、外部キーをnull更新する方法もある(nullableであれば)。
has_many :contents, dependent: :nullify
文字列で返ってくる真偽値を、booleanオブジェクトとして扱いとき。ActiveModelのmoduleを使用する。 言われてみるとDBでは文字列かをあまり意識せずに使える。
ActiveModel::Type::Boolean.new.cast(value) == true
【Rails】Slimで入れ子になっている要素の親タグのみを分岐させる - Qiita 閉じタグがないため階層の上だけ条件分岐するためには特殊な書き方が必要になる。
$ rails g migration ChangeProductPrice
class ChangeProductsPrice < ActiveRecord::Migration[7.0]
def up
change_table :products do |t|
t.change :price, :string
end
end
def down
change_table :products do |t|
t.change :price, :integer
end
end
end
$ rails g migration AddNotNullOnBooks
class AddNotNullOnBooks < ActiveRecord::Migration[6.0]
def up
change_column_null :books, :user_id, false
end
def down
change_column_null :books, :user_id, true
end
end
クソコードから学ぶ。
Rails のドキュメント。Factory Botの作り方。
- 5730
PR送信完了。一字だけ。
Rails + Reactの本。ホットリロードができない。
Rails console。ENVで分岐すれば本番コンソールでログレベルを上げる、ということができるはず。 GraphQLをrailsでやるチュートリアル。CLOSE Amazon.co.jp: Deploying Rails with Docker, Kubernetes and ECS (English Edition) eBook : Acuña, Pablo: Foreign Language Books
- 24, 51
KubernetesでRails deployまでやる本。 AWSデプロイコマンドの挙動が違い、バージョンを合わせても動かずよくわからなかったので断念。こういうのは新しい本を買うべきだな。ローカル環境minikubeでの動作は確認できた。
2つの違いは何で、実際どのように、どの境界で処理しているのか。- rails: アプリケーション(ソースコード)
- puma: アプリケーションサーバ。アプリケーションを動かしているもの
まず、webリクエストはwebサーバーが受け取ります。そのリクエストがRailsで処理できるものであれば、webサーバーはリクエストに簡単な処理を加えてアプリケーションサーバーに渡します。アプリケーションサーバーはRackを使ってRailsアプリケーションに話しかけます。Railsアプリケーションがリクエストの処理を終えると、Railsはレスポンスをアプリケーションサーバーに返します。そして、webサーバーはあなたのアプリケーションを使っているユーザーにレスポンスを返します。
ruby, railsのより良い書き方のガイド。
docker - How to run Capybara tests using Selenium & Chrome in a Dockerised Rails environment on a Mac - Stack Overflow
dockerのseleniumで動かす方法。
モデルの説明。
アップデートの流れ。
ルーティングをどうするかの指針。
READMEに安全なマイグレーションの説明がある。
ビジュアルリグレッションテストの運用方法。
Rails Wayの定義について。
Rails開発のディスカッション。
わかりやすいスライド。
ちゃんとRailsガイドを読まないときついな。