総論・概念
とっても重要な概念。railsの流れは常にこう。
ルーティング → コントローラー → ビュー
アクセスしたアドレス(パス)を元に、次のアクションを起こすためにどのコントローラーを使うかルーティングを行う。
指定されたコントローラーに所属するアクションを用いてデータの取得等を行う。
取得したデータをビューとしてhtmlに返す。
get 'パス' => 'コントローラー名#アクション名'
get 'tweets' => 'tweets#index'
railsの命名規則
controller → 複数形
- ファイル名:users_controller.rb
- class名:UsersController
model → 単数形
- ファイル名:user.rb
- class名:User
データベース → 複数形
- table名:users
view → 複数形
- ディレクトリ名:users/
convention over configuration
規約が最初。規約に従わない場合は自分で設定してね、という概念。
初めてのプログラミングの勉強をrailsでやる場合に、学習を阻害する根源。
規約の中身が多すぎるため把握しきれず、意図しない動作をしてしまう。(規約通りなのだが)
初学者にとって規約を理解することそのものが困難であり、膨大すぎる規約のためそもそも把握できるようなものではない。なにより規約があることを知らない。
私が10日余りrailsを勉強して引っかかった部分はほぼ全てrails内部での処理である。
読んでないけどきっと規約に書いてある。
rubyに関しては書いた通りに動かせるのでロジックが組める。でもrailsは書いた内容が出力される時に、”いいように”して出力してくれるので私の意図と違ったりする。
なのでrailsは覚えゲーだと思う。「AをしたらBになる」という入出力を覚えることはできても、理解したいのならrailsではない方が好ましいかもしれない。
Routes
Rails.application.routes.draw do
devise_for :users
root 'tweets#index'
resources :tweets do
resources :comments, only:[:create]
end
resources :users, only:[:show]
# get 'tweets' => 'tweets#index'
# get 'tweets/new' => 'tweets#new'
# post 'tweets' => 'tweets#create'
# delete 'tweets/:id' => 'tweets#destroy'
# get 'tweets/:id' => 'tweets#show'
# get 'tweets/:id/edit' => 'tweets#edit'
# patch 'tweets/:id' => 'tweets#update'
# get 'users/:id' => 'users#show'
end
resoucesメソッド
自動でルーティングしてくれる。
7種類。only等で使うものだけ指定する。
new, create, show, destroy, index, update/put, update/patch
resources:reviews, only:[:new, :create]
上は下と同義
get 'products/:product_id/reviews/new' => 'reviews#new'
post 'products/:product_id/reviews' => 'reviews#create'
resourcesメソッドのネスト
resources :books do
resources :reviews
end
Prefix Verb URI Pattern Controller#Action
book_reviews GET /books/:book_id/reviews(.:format) reviews#index
POST /books/:book_id/reviews(.:format) reviews#create
new_book_review GET /books/:book_id/reviews/new(.:format) reviews#new
edit_book_review GET /books/:book_id/reviews/:id/edit(.:format) reviews#edit
book_review GET /books/:book_id/reviews/:id(.:format) reviews#show
PATCH /books/:book_id/reviews/:id(.:format) reviews#update
PUT /books/:book_id/reviews/:id(.:format) reviews#update
DELETE /books/:book_id/reviews/:id(.:format) reviews#destroy
books GET /books(.:format) books#index
POST /books(.:format) books#create
new_book GET /books/new(.:format) books#new
edit_book GET /books/:id/edit(.:format) books#edit
book GET /books/:id(.:format) books#show
PATCH /books/:id(.:format) books#update
PUT /books/:id(.:format) books#update
DELETE /books/:id(.:format)
prefix
prefixに_pathを付けるとURI Patternのパスを示す。
book_review_pathで”/books/:book_id/reviews”を示す。
:book_idに関しては、_pathの引数にインスタンスを渡すことで補われる。
なおprefixが空欄のものは無いのではなく、上と同じため省略されているだけである。
@review = Review.find(1)
book_review_path(@review)
=> /books/:book_id/reviews/1
prefixを用いる場合には''で囲う必要はない。囲うと/new_post_pathはno roote matchesと怒られる。
<li><%= link_to 'New', new_post_path %></li>
<li><%= link_to 'New', 'posts/new' %></li>
resourcesとresource
単数形と複数形で挙動が異なる。
複数形だとindexとcreate以外のアクションに:idが付与される。
そして単数形を用いる場合、リソースが1つと判断でき、indexする必要がないため自動的にindexアクションは省かれる。
単純なページ等でresourceと単数形で用いたい場合、resource :bookのようにどちらも単数形で指定しても返されるコントローラー名(controllers#show)などは複数形が指定される。railsのルールとしてコントローラー名は複数形で使われるから。
ただし、resources bookとした場合にはbook#indexという風にそのまま作成される。
処理としてはresourceに与えられる値は複数形化され、resourcesはもともと複数形として信頼されているのでそのままでコントローラー名として扱われる?
resource 単数形 複数形化#アクション => 明らかな介入あり
resource 複数形 そのまま#アクション => 介入あるか不明
resources 単数形 そのまま#アクション => 介入なし
resources 複数形 そのまま#アクション => 介入あるか不明
resourece :tweets
new_tweets GET /tweets/new(.:format) tweets#new
edit_tweetsGET /tweets/edit(.:format) tweets#edit
tweets GET /tweets(.:format) tweets#show
PATCH /tweets(.:format) tweets#update
PUT /tweets(.:format) tweets#update
DELETE /tweets(.:format) tweets#destroy
POST /tweets(.:format) tweets#create
resources :tweets
tweets GET /tweets(.:format) tweets#index
POST /tweets(.:format) tweets#create
new_tweet GET /tweets/new(.:format) tweets#new
edit_tweet GET /tweets/:id/edit(.:format)tweets#edit
tweet GET /tweets/:id(.:format) tweets#show
PATCH /tweets/:id(.:format) tweets#update
PUT /tweets/:id(.:format) tweets#update
DELETE /tweets/:id(.:format) tweets#destroy
namespace do
namespaceに付けた名前がurlの先頭に付与される。
namespace :admin do
namespece :users do
resources :hoges, only[:index]
end
end
admin/users/hoges
Controller
コントローラーの作成・削除
rails g controller コントローラ名
# コントローラーを作成 gはgenerateの略
rails d controller コントローラ名
# 作成したコントローラを削除
作成した場合、app/controllerにコントローラー名フォルダとコントローラー名.rbファイルが作成される。
対応するviewに関しては、app/viewにコントローラー名フォルダが作成されるが、中身となるアクション名.html.erbは自動的には作成されないので必要に応じて自分で新規作成する。(コントローラー作成時にアクションまでは指定しないので当然ではあるが)
rails gで不要なファイル群を作成しないように設定変更
config/application.rbに以下を記述
module ChatSpace
class Application < Rails::Application
config.generators do |g|
g.stylesheets false
g.javascripts false
g.helper false
g.test_framework false
end
end
end
redirect_toメソッド
redirect_to controller: :コントローラー名, action: :アクション名
アクション → ビューが通常の流れだが、アクション内でredirect_toメソッドを行うことで別のアクションを実行したり、別のビューへ誘導できる。
def move_to_index
redirect_to action: :index
# indexアクションを強制的に実行する
end
引数にはaction: :indexという形で、キーがaction:バリューが:indexであるハッシュを指定する。
つまりredirect_to{action: :index}が精確だが、railsではハッシュの{}は省略しても良い。
before_actionメソッド
コントローラーでbefore_action :メソッド名 と指定することによってアクションが実行される前に、特定のメソッドを実行できる。
なおonlyやexceptを用いて制限することもできる。
before_action :hoge, except: :index
# indexアクション以外が実行される前にhogeが実行される。
例
class MessagesController < ApplicationController
before_action :set_group
private
def set_group
@group = Group.find(params[:group_id])
end
end
layout ‘レイアウトファイル名’
コントローラ内でlayout ‘レイアウトファイル名’と書くと、そのコントローラでのアクションが呼ばれたあとに表示するビューのレイアウトファイルを指定できる。
仮にlayout ‘movie’に指定すると、MovieControllerのindexアクションが呼ばれたときに表示されるレイアウトはmovie.html.erbとなる。なにも指定しないとレイアウトファイルはapplication.html.erbとなる。
リレーションのCreate
current_user.reviews.create(create_params)とするとcurrent_userの入ったテーブルが作成される。その他の情報はparamsから。
def create current_user.reviews.create(create_params) redirect_to controller: :products, action: :index end private def create_params params.require(:review).permit(:rate, :review).merge(product_id: params[:product_id]) end
View
コントローラーのアクションが実行された後、アクションと同名のviewファイルが展開される。
railsではviewファイルに.html.erbファイルが用いられ、embedded Rubyの略でHTMLへrubyスクリプトを埋め込んだ形式の物である。
コントローラーのアクションで定義したメソッドを使うには
<%= メソッド名 %>とする。
- <% ~ %> ・・・ 処理を行うのみで、htmlへ値を代入しない。
- <%= ~ %> ・・・ 処理を行い、その場に値を代入する。(htmlに埋め込む)
- <%= ~ -%> ・・・ 最後の改行を除いてその場に値を代入する。
実はRailsでは、ルーティングとルーティングに対応するビューさえあれば、明示的にコントローラのアクションを定義しなくても、ビューを表示することができる。
静的なページを構築する場合にはアクションを定義しなくてよい。
.erbで使うメソッド ヘルパーメソッド
link_toメソッド
rubyタグ内で使用できる。htmlのaタグへ変換される。
例として上のメソッドはhtml.erbからhtmlへ変換される際に自動的に変換される。
<%= link_to '記事一覧へ', '/hogehoge', class: 'sample' %>
↓へ変換される
<a class="sample" href="/hogehoge">記事一覧へ</a>
simple_format
rubyタグ内では改行が無視されるため、改行も出力したい場合に使う。
<%= simple_format(text) %>
img_tag
<%= img_tag 'cat1.png'%>
<img src="images/cat1.png">
validation(検証)
入力フォームを通じてビューからサーバー側へパラメーターが送られてきた際、正常な値か検証する。未入力を撥ねるために使う。
validates :カラム名, presence: true
フォームの中身の有無を検出してくれる。ない場合には保存処理をしない。
例えば、userのemailを入力必須にしたい場合、以下のように書く。
user.rb
class User < ApplicationRecord
validates :email, presence: true
ユーザーの新規登録時にemailを入力しなかった場合、userを登録することができなくなる。
validationとnew/save
validationで未入力があった場合にどうやって再処理を促すか?
仮にformから返る値をcreateで作った場合、validationで撥ねた場合に処理が分岐できない。
実はsaveメソッドは成功するとインスタンスが返り値となるが、失敗すると(validationで撥ねる)とfalseが返ります。これを利用して、if インスタンス.saveと書くことで再入力を促すことができる。
if tweet.save
redirect_to root_path
else
render :new
end
errorsメソッド
validationで失敗した場合、保存しようとしたインスタンスはnilではなく、エラー情報を含んだインスタンスになる。(情報が追加される)
これをrenderで再度同じページに飛ばすことで、エラー情報を持つ場合の分岐が作れる。
.errors.full_messages
エラーメッセージを配列で取得できる。
- if @group.errors.any?
.chat-group-form__errors
%h2= "#{@group.errors.full_messages.count}件のエラーが発生しました。"
%ul
- @group.errors.full_messages.each do |message|
%li= message
renderメソッド
文字通りレンダリングするときに使う。
部分テンプレートを呼び出したり、アクションをせずに別のビューページに飛ばす際に用いる。
類似の機能にredirect_toがある。
redirect_toの場合はアクションを実行後にビューに移るのに対し、
renderの場合はアクションを実行せずに直接ビューに移る。
具体的な使い方としては、静的なページにアクセスさせる際にはアクションを実行する必要がないためrenderを使う。
viewファイル内だけではなく、controller側でも使う。
その際にはhtmlを指定するだけでなく、actionを指定もできる。(当然だが、actionに対応するviewが出るだけで、actionは実行されない)
render :show
render action: :show
render "show"
render "show.html.erb"
render action: "show"
render action: "show.html.erb"
render partialオプション
renderメソッドに :partial
というオプションをつけることで、明示的に部分テンプレート名を指定し、部分テンプレートを表示することができます。
なお同名かつ_を付与してファイルを作成している場合には記述を省略できる。
sample.html.erb
_sample.html.erb 部分テンプレート名に_を付ける
render :partial => "sample", :locals => { :val1 => xxx, :val2 => xxx }
render localsオプション
部分テンプレート内に変数を渡す。
render partial: "sample", locals: { hogehoge: "hello!" }
<% @product.reviews.each do |review|%>
<%= render partial: "review", locals: { review: review } %>
<% end %>
render collection
render partial:tweet, collection @tweets
@tweets.each do |tweet|
render partial:tweet
end
上記の上と下はやっていることは同じであるが、処理速度が違う。
理由はeachで回す場合に部分テンプレートをeachの回数だけ繰り返し呼んでしまうからだ。
collectionで渡す場合には1度しか呼び出されないので通常は上の書き方をする。
renderメソッドの省略記法
<% render partial: "hogehoge", collection: @hogehoges %>
↑↓は同義。collection記法であれば大半を省略できる。
<% render @hogehoges %>
collection記法は、モデル名sと_モデル名.html.erbの規則であればpartial部分を省略可能。部分テンプレート内ではインスタンス名(単数形)が用いられる。加えてcollectionという表記も省略できるので上記のように簡潔に記載できる。
この規則は、@hogehogesの文字列由来ではなく、@hogehogesのモデルに由来する。
このため、下記のような記載では、参照される部分テンプレートや部分テンプレート内のインスタンス変数名はモデル由来のものが利用される。(ここの記載はちょっと怪しいです。 ローカル変数を省略すると、参照先テンプレート名が変数名に格納される?)
これは規約なのでどうしようもない。
def index
@hogehoges = Post.all.page(params[:page]).per(5)
end
posts/index.html.erb
<%= render @hogehoges %>
posts/_post.html.erb
<%= post.text %>
なお変数名を指定する場合には次のようにasで指定する。
= render partial: "irs", collection: @companies, as: :company
one more step
複数のコントローラに同じ処理が記述されている場合(concerns)
規模が大きくなるにつれ複数のコントローラに同じような処理が繰り返し記述されることがある。
- app/controllers/concernsにファイルを追加し、必要箇所で読み込ませる
- 親コントローラにメソッドを定義する
などの方法でコントローラの記述を共通化する。
concerns ディレクトリに共通するコードをモジュールとして定義し、ソースコードの可読性をあげる。
app/controllers/concerns/current_cart.rb
module CurrentCart extend ActiveSupport::Concern private def set_cart @cart = Cart.find_by(id: session[:cart_id]) if @cart.nil? @cart = Cart.create session[:cart_id] = @cart.id end end end
class ProductsController < ApplicationController include CurrentCart before_action :set_cart end
同じ親コントローラを継承していれば、親コントローラに記述するだけでOK。
def ApplicationController < ActionController::Base private def authorize_owner redirect_to root_path unless current_user.owner? end end
複数のアクションに同じ処理が記述されている場合
同じコントローラ内で同じような処理が繰り返し記述されている場合はcallbackを用いて共通化する。before_actionなどのcallbackを使うと良い。
注)諸説あり。before_action等が増えるとメソッド内の処理が見通せなくなる。
callbackの順番によって意図しないエラーが起こる可能性がある。
コントローラに複雑な処理を記述している場合
コントローラのコード量が多くなっている場合、本来モデルで行うべき処理がコントローラに書かれている可能性が高い。
モデル
ビューやコントローラから呼び出される様々な処理はモデルに集約されていくために、特にモデルは肥大化し易い。幾つかの観点で切り分ける。
Decorator
Decorator(デコレーター)とはビューとモデルの中間に位置し、モデルやビューなどに実装されやすい表示ロジックやフォーマットなどを担当する。
モデルにビューでしか使用しないメソッドが増えていくことがある。
full_nameやfull_name_kanaと言ったメソッドがその例。
こうしたメソッドをデコレーターへ移動する。
app/models/user.rb
class User < ActiveRecord::Base def full_name "#{family_name} #{first_name}" end def full_name_kana "#{family_name_kana} #{first_name_kana}" end end
Railsでデコレーターを使用する場合にはdraperやactive_decoratorと言ったgemを使う方法が一般的。
以下はdraperを使った場合。
app/decorators/user_decorator.rb
class UserDecorator < Draper::Decorator delegate_all def full_name "#{family_name} #{first_name}" end def full_name_kana "#{family_name_kana} #{first_name_kana}" end end
app/controllers/users_controller.rb
class UsersController < ApplicationController def show @user = User.find(params[:id]).decorate end end
app/views/users/show.html.erb
<%= @user.full_name %> <%= @user.full_name_kana %>
Callback
コントローラ同様にモデルにもvalidationの直前に実行されるbefore_validationであったり、saveの直後に実行されるafter_saveなど様々なタイミングで実行されるコールバックが存在する。
開発が大規模になるとコールバックのメソッドが増え、メソッド同士の関係性がわかりづらくなる。callbackの引数に、そのコールバックと同名のメソッドを持つインスタンスを渡すことで、コールバックの処理を別のクラスに移動できる。
class BankAccount < ActiveRecord::Base before_save EncryptionWrapper.new after_save EncryptionWrapper.new after_initialize EncryptionWrapper.new end class EncryptionWrapper def before_save(record) record.credit_card_number = encrypt(record.credit_card_number) end def after_save(record) record.credit_card_number = decypt(record.credit_card_number) end def after_initialize(record) record.credit_card_number = decypt(record.credit_card_number) end private def encrypt(value) # 暗号化の処理 end def decrypt(value) # 解読の処理 end end
Modelメソッド
モデル内に定義したメソッドはモデルに対して使う。
selfを用いてクラスメソッド風に書くが、インスタンスに使う。
モデル
def add_tax
self.price = (price * 1.08).round
end
コントローラ
def create
@product = Product.new(product_params)
@product.add_tax
end
頻出メソッド
ActiveRecordメソッド
最終的にSQL分を発行してDBから情報を抜いてくれる。
findメソッド
whereメソッド
モデル.where(条件)のように引数部分に条件を指定することで、テーブル内の条件に一致したレコードのインスタンスを配列型で取得できる。
例
user.where('id < ?', 20)
Product.where('title LIKE(?)', "test%")
Review.where(name: "test")
あいまい検索
文字列 | 意味 |
---|---|
% | 任意の文字列(空白文字列含む) |
_ | 任意の1文字 |
実行例 | 詳細 |
---|---|
where(‘title LIKE(?)’, “a%”) | aから始まるタイトル |
where(‘title LIKE(?)’, “%b”) | bで終わるタイトル |
where(‘title LIKE(?)’, “%c%”) | cが含まれるタイトル |
where(‘title LIKE(?)’, “d_”) | dで始まる2文字のタイトル |
where(‘title LIKE(?)’, “_e”) | eで終わる2文字のタイトル |
複数条件検索
User.where(‘id = ? or name = ?‘, 5, ‘satou’)
User.where(‘id = ? and name = ?‘, 5, ‘satou’)
where.not
@users = User.where('name LIKE(?)', "%#{keyword}%").where.not(id: current_user.id)
destroyメソッド
インスタンスに対してそのレコードを削除する際に使うことができる。
updateメソッド
updateメソッドはモデルのインスタンスに対して使用でき、引数内の情報にレコードを更新できる。
update(カラム名: 更新する情報, カラム名2: 更新する情報2、、、)
orderメソッド
コンテンツの並び替えをする。すべてのインスタンスを取得する場合は.allを省略できる。
@contents = Content.all.order("id DESC")
昇順.order(“id ASC”) もしくは降順 .order(“id DESC”)で並べ替えます。
id部分には任意のカラム名を指定します。
limitメソッド
取得するインスタンスの数を制限する。
すべてのインスタンスを取得する場合は.allを省略できる。
@ranking = Product.limit(5)
mergeメソッド
フォームから値を受けとって、統合したりする時などに使う。
params.permit(:image, :text).merge(user_id: current_user.id)
new_record?
モデル.new_record?と言う感じで使う。
返り値はtrue/false
.last()
Items.last
id順で最後のidのインスタンスを取得
Items.last(4)
最後の4つのインスタンスを取得
first_or_initializeとその類似
個人的にはfind_or_create_byの方が検索と作成保存まで1メソッドで済むので良さそう。
first_or_initializeメソッド
user = User.where(name: "hogehoge").first_or_initialize(name: "fugafufa")
whereメソッドと使うことで、whereで検索した条件のレコードがあればそのレコードのインスタンスを返す。なければ新しくインスタンスを作る。
user = User.where(nickname: "Tanaka").first_or_initialize
=> #<User id: nil, nickname: "Tanaka">
p user.nickname
=> "Tanaka" # "Tanaka"がnicknameカラムに代入されている
上記だけだと保存されてないので、最後に保存する。
user.save
.find_or_initialize_by()
引数で検索してなければnew()で作ってくれる。
user = User.find_or_initialize_by(name: "fugafufa")
if user.new_record?
user.save
end
.first_or_create()
.first_or_initializeとほぼ同じ。whereと併用する。
違いはcreate()かnew()かの違い。
生成したインスタンスに追加情報を加えるときはinitializeで、加えないときはcreateの方がsaveしないでいいので楽。
user = User.where(name: "hogehoge").first_or_create(name: "hogehoge")
.find_or_create_by()
.find_or_initialize_by()とほぼ同じ。
違いはcreate()かnew()かの違い。
createなのでsaveしなくていい。
enum
カラム名に対して設定する。モデルファイルに記述する。
カラムはinteger型かboolean型のものが対象。
nationテーブルにprefectureカラムがある場合
enum prefecture: [:tokyo, :saitama, kanagawa]
enum prefecture: { tokyo: 5, saitama: 10, kanagawa: 3 }
[]で指定すると0から順にintが割り当てられる。
{}で渡すと番号も指定できる。ただし、自動追加はできない。
ActiveRecord Relationクラス
whereメソッドやアソシエーションを利用してDBから複数のレコードをインスタンスとして取得した場合、取得した配列はActiveRecord Relationクラスに属す。
ActiveRecord Relationクラスには、whereを始めとして複数のメソッドが準備されている。
products = Product.all
products.class #=> ActiveRecord::Relation::ActiveRecord_Relation_Product
whereメソッドやアソシエーションで、DBに当てはまるレコードが存在しない場合、返り値は空の配列になる。
modelからインスタンスを取得すると配列になる。配列内の個々のモデルにはハッシュ?
ActiveRecord 集計関数
averageメソッド
取得したモデルインスタンスの平均を出力できる。
以下の書き方をするが、カラム名をシンボルで指定する。
例)reviews.average(:rate)
モデル.average(カラム名 [, オプション])
配列に対して1つずつ中身(ハッシュ)を取り出して、シンボル指定で値を抜いている印象。
コードの実態が何をしているのかはわからないが、eachを当てているように見える。
groupメソッド
テーブルのレコードを指定したカラムでまとめる。
.group(:key)のように使う。
Review.group(:product_id)
Review Load (10.5ms) SELECT `reviews`.* FROM `reviews` GROUP BY product_id
=> [#<Review id: 1, nickname: "まいき", rate: 1, review: "おもしろ", product_id: 21, user_id: 4>,
#<Review id: 3, nickname: "ちゃん", rate: 10, review: "感動", product_id: 22, user_id: 4>,
#<Review id: 13, nickname: "ごとう", rate: 10, review: "良かった", product_id: 23, user_id: 4> ・・・
返る値はテーブル情報のハッシュ(?)が入った配列?
product_idで情報がまとめられるが、それぞれのreviewの詳細を失うわけではない。
1つ1つのproduct_idのテーブル内にネストされて格納されるイメージ。(正しいかは知らない)
countメソッド
array.count(hogehoge)
count (Array)
配列の中で引数hogehogeと同じ要素の数を返す。
引数を省略すると、要素の数を返す。
order(‘count_カラム名’).count(カラム名)
countメソッドの引数にカラム名を指定できる。
こうするとorderメソッドでcount_カラム名でのソートが可能になり、そのカラムを持つレコードの数でソートする。
product_ids = Review.group(:product_id).order('count_product_id DESC').limit(5).count(:product_id).keys
例として、reviewをproduct_idでまとめたレコードをレコード数でソートしてカウントする。
返り値として、カラム名とレコード数のハッシュで返す。
ハッシュからkeyだけ取り出すという処理がされる。
.group.countと併用した場合
ActiveRecordにおけるcountは返り値がハッシュになる。
Review.group(:product_id).count
=> {21=>2, 22=>1, 23=>4}
limit()を併用したい場合にはcount()の前につける。
count(:product_id)の後に行うと返り値がハッシュになるため使えない。
dateクラス
date = Date.new(2011, 4, 1)
Date.today
今日を取得
date = Date.today
その他
include?()
文字列に対して使う。
()内の文字を含む場合にはtrue、含まなければfalseを返す。
str.include?("hogehoge")
.slice() or []
.sliceに引数を1つだけ与えた場合はN番目の文字を取り出す。(0始まり)
負の数を与えた場合には後ろからN番目。(こっちは-1始まり)
str = "abcdef54321"
str.slice(1) = b
str[1] = b
str.slice(-1) = 1
str[-1] = 1
引数を2つ与えた場合にはN番目からM個の文字を取り出す。
負の値の場合でも後ろからN番目から後ろ方向へM個なので注意。
str = "abcdef54321"
str[1,3] = bcd
str[-5,3] = 543
dig
ハッシュに対して使う。
ネストされたハッシュに対してキーを順に参照して値を返す。
hash={key1: {key2: {key3: "hoge"}}}
hash.dig(:key1, :key2, key3)
=> hoge
.delete()
stringに対して用い、特定の文字を削除する。
"hoge".delete("h")
=> oge
破壊的な変更はdelete!とする。
注意点としては、delete!(“hogehoge”)でhogehogeが含まれない場合、nilが返る。
ワンライナー
array = [1, 2, 3, 4, 5].map do |el|
if el.odd?
el
end
end.compact!
array = [1, 2, 3, 4, 5].map { |el| el if el.odd? }.compact!
array = (1..5).to_a.delete_if { |el| el.even? }
array = (1..5).to_a.delete_if(&:even?)
array = [1, 2, 3, 4, 5].select{ |el| el.odd?}
delete_if
hash.delete_if(|key, value| 条件 )
array.delete_if(|element| 条件 )
ブロックの戻り値がtrueの時、要素を削除する。
ハッシュに使う場合はキーと値を削除する。
配列から複数の要素を削除
「-」メソッドで差集合が取れる。(左辺の重複要素は削られない)
a = [hoge, hoge, fuga, fuga]
b = [hoge]
c = a - b
=> [fuga, fuga]
他にも|で和集合、&で積集合(どちらも重複要素は削られます)
a = [hoge, hoge, fuga, fuga]
b = [hoge]
c = a | b
=> [hoge, fuga]
d = a & b
=> [hoge]
数値の桁区切り、通過表示
to_s(:delimited), number_with_delimiter
to_s(:delimited)だけで桁区切りは, 小数点区切りは.が与えられる。
桁区切りはdlimiterを指定することで記号を変更できる。
小数点区切りはseparator。
1000.to_s(:delimited, delimiter: ' ', separator: ',')
to_s(:currency)
数字を通貨表示に変更してくれる。
12345.to_s(:currency) #=> "12,345円"
task
定期的に実行したい処理は、lib/tasks以下のディレクトリに、rakeタスクを作成して処理を記述する。
taskの作成
rails g task task名
以下のファイルが作成される。
lib/tasks/task名.rake
namespace :task_sample do
end
namespace :user do
desc "ユーザー情報をリセットする"
task reset_flag: :environment do
User.update_all(flag: false)
end
end
実行する。
rake task user:reset_flag
helperメソッド
app/helpers/product_helper.rb
module ProductsHelper
def converting_to_jpy(price)
"#{price.to_s(:delimited, delimiter: ',')}円"
end
end
よく使うGem
gemをインストールする際にはgemfileに gem ‘hogehoge’と書いて、bundle installを実行。
pry-rails
デバッグツール。
binding.pryをソースコードの中に記述することで処理を止めることができる。
止めた処理はexitで再開。exit!でサーバーを止める。
止めた時点での変数を出力したりすることで処理が正しく行われているか確認できる。
ページネーション
ページ分割のこと。
gemのkaminariを利用して実装する。
gemfileに以下を追記してbundle install.
インストール後はrails sでサーバーを再起動します。
gem 'kaminari'
kaminariをインストールすると、pageメソッドとperメソッドを利用できる。
さらにビューのリクエストの際paramsの中にpageというキーが追加されて、その値がビューで指定したページ番号となります。
.pageでページ番号を、.perで一度に表示するデータ数を指定する。
@hogehoge = Modelclass.all.page(params[:page]).per(5)
paginateメソッドはページリンクを作る。
html.erbの必要な箇所に以下を追記する。
<%= paginate(@tweets) %>
その他
インストール済みのGemを探す
list または search は、ローカルもしくはリモートにあるGemパッケージを検索する。
gem list
ローカルサーバーの立ち上げ
rails s
サーバーを終了する場合にはcontrol+C。
ターミナルをタブで分けた方が使いやすいので、command+Tでタブを分ける。
Rails console
rails c
コンソールを起動するとターミナルからrailsのメソッドを使うことができる。
終了するときはexit
フォントファイルの置き場は
app/assets/fonts/
fontsフォルダは存在しないので作成する。
ファイルのかき分けの問題
カリキュラムでScraping用のscraping.rbはデータテーブルを扱うモデルとは別に作った。
テーブルに関するメソッドはModelファイルに書くべきでは?
=> 1度しか使わないようなメソッド等は別ファイルに書き分けるのはよくある。
モデルファイルには別ファイルのメソッドを呼び出す記述だけにとどめて可読性をあげた方がよい。
=> カリキュラムではモデルフォルダにscraping.rbを作ったことが腑に落ちない。というかスッキリしない。ファイルが増えると管理しにくくなるはず。
=> モジュールで管理するのが普通なのかも?
Rubyのモジュール
モジュールとクラスはよく似ている。クラスはデータと処理を、モジュールは処理を担当する。
モジュールを用いることで、モジュール内に定義したメソッドをインスタンスメソッドとして取り込んだり(include)、クラスメソッドとして取り込んだり(extend)できる。
名前空間の提供というもっと重要そうな点については理解が追いつかないので割愛。
README
マークダウンで記述され、ソフトウェアの仕様、規格、インストール方法などを文書化したもの。rails newをした際に自動的に作成される。
## membersテーブル
|Column|Type|Options|
|------|----|-------|
|user_id|integer|null: false, foreign_key: true|
|group_id|integer|null: false, foreign_key: true|
### Association
- belongs_to :group
- belongs_to :user
rake db:create と rails db:create
rails5からはrakeではなくrailsコマンドを使うように変更された。
rakeコマンドも使えるが、あえて使う意味はない。
rails4以前ではバージョン情報をgemファイル内に記載していたため、bundle exec rake db:**としていたが、rails5からはbundle execは不要となり、単にrails db:**でよくなった。
コラム 2.1. Rakeについてを参照。
Ynaqdh
rails gでconflictした際にでる。
Y - yes, overwrite
n - no, do not overwrite
a - all, overwrite this and all others
q - quit, abort
d - diff, show the differences between the old and the new
h - help, show this help
m - merge, run merge tool
複数形
sをつける以外の複雑な複数形にも対応されている。
company -> companies
asset pipeline
アセットファイルを管理する仕組み。
アセットファイル = javascriptやcssなどの付帯情報
sprockets
Sprocketsは、そのAsset Pipelineの基盤となるgem。
アセットファイルのパス管理やコンパイルをしてくれる。
コンパイルする際にはマニュフェストファイルと呼ばれるapplication.cssやapplication.jsなどを元にファイルを参照する。
プリコンパイル
rake assets:precompile
public/assets/以下にコンパイルされたファイルが配置される。
production環境ではassetフォルダではなく、こちらを読みに行く。
scaffold
rails g scaffold product name:string price:integer
これを実行すると以下が作成される。
要点はmigrationファイル、コントローラ、ビュー(jsonも)
さらにルーティングやアセットファイルの作成まで。
Running via Spring preloader in process 56483
invoke active_record
create db/migrate/20190820033625_create_products.rb
create app/models/product.rb
invoke test_unit
create test/models/product_test.rb
create test/fixtures/products.yml
invoke resource_route
route resources :products
invoke scaffold_controller
create app/controllers/products_controller.rb
invoke erb
create app/views/products
create app/views/products/index.html.erb
create app/views/products/edit.html.erb
create app/views/products/show.html.erb
create app/views/products/new.html.erb
create app/views/products/_form.html.erb
invoke test_unit
create test/controllers/products_controller_test.rb
create test/system/products_test.rb
invoke helper
create app/helpers/products_helper.rb
invoke test_unit
invoke jbuilder
create app/views/products/index.json.jbuilder
create app/views/products/show.json.jbuilder
create app/views/products/_product.json.jbuilder
invoke assets
invoke coffee
create app/assets/javascripts/products.coffee
invoke scss
create app/assets/stylesheets/products.scss
invoke scss
create app/assets/stylesheets/scaffolds.scss
ベーシック認証 Basic認証
HTTP通信の規格に備え付けられているユーザー認証
authenticate_or_request_with_http_basic
下記の例ではusernameとpasswordを設定している。
ソースにベタ書きではなく、普通は環境変数を使う。
authenticate_or_request_with_http_basic do |username, password|
username == 'admin' && password == 'password'
end
実際に動かすにはbefore actionで呼び出す。
before_actionに付けるif文は、rails5.2以前はif: “Rails.env.production?”という記法が通用したが、5.2〜はproc{}で囲まないと弾かれるようになった。
class ApplicationController < ActionController::Base
before_action :basic_auth, if: proc{Rails.env.production?}
private
def basic_auth
authenticate_or_request_with_http_basic do |username, password|
username == 'admin' && password == '2223'
end
end
end
もしくはメソッドを定義してif文に書き込む。
before_action :basic_auth, if: :production?
private
def production?
Rails.env.production?
end
def basic_auth
authenticate_or_request_with_http_basic do |username, password|
username == ENV["BASIC_AUTH_USER"] && password == ENV["BASIC_AUTH_PASSWORD"]
end
end
ancestry
カテゴリ等の多階層にわたるテーブルの設計を行うgem。
インスタンスに対して.childrenを繰り返すことで多階層なテーブルを作成できる。
category = Category.create( name: "レディース")
child = category.children.create(name: "トップス")
child.children.create([{name: "Tシャツ/カットソー"},{name: "パーカー"}}])
逆に親を参照する際には.parentを使えば参照できる。
child.parent.name
session
ハッシュで情報を保持している。pramsと違いページ遷移でデータは消えない。
deviseの機能としては主にuser_idを持たせているが、他にも任意の値を入れることができる。
sessionに情報を保存する場合は以下のようにする。
session[:name] = "田中"
session自体はハッシュと似た構造なのだが、以下で代入すると単なるハッシュと見なされ、sessionとしては保存されない。(実は手抜き代入しようとしてココで嵌った)
session = {name: "satou", email: "ggg@ggg"}
paramsの内容を一旦、sessionに保存する場合
paramsは以下のクラス(ハッシュクラスからの派生クラス)
<ActionController::Parameters>
sessionは以下のクラス
#<ActionDispatch::Request::Session:0x00007fe98b371ff0 ...>
session = params.permit()
とすると、sessionという変数は<ActionController::Parameters>になってしまう。
ActionDispatchクラスのsessionとは別物な感じ。
これではページ遷移後に情報が保持されない。
paramsを.to_hでハッシュに変えて代入してやればSessionクラスとして代入できる?
session = user_information_params.to_h
なおparams.to_hでハッシュ化されるのはpermitされた項目のみ。
params.permit.to_hとしないと空のハッシュになる。
これをやって動くかと思ったが、結局sessionは維持されず。
これじゃダメみたい。
session[:key] = valueに立ち返って、eachで回して解決した。
def sign_up
user_params.each do |key, value|
session[:"#{key}"] = value
end
end
コメント