Railsの落とし穴

アソシエーション先のテーブル保存

autosave optionで設定する。
未指定ならnewは親オブジェクトの保存時に自動保存。updateは保存しない。

rails g modelやrails g controllerをする際に

ルーティングファイルも参照されるのでエラーに注意!
generateとは直接関係なくても参照されてしまうので、routes.rbを弄るのはgenerateが済んでからにしましょう。

order.limitの組み合わせ

下記のように処理を分けて書いても1回にまとまったSQL文が発行される。
このため意図しない挙動が起きる。

@datas = Product.limit(20)
@datas = @datas.order('id DESC')
↑↓で同じ結果が返る。
@datas = Product.order('id DESC').limit(20)

テーブルを切り出してからソートしたい場合にはwhereで切り出すのが吉。

order(‘count_カラム名’).count(カラム名)

自己矛盾?
.countを実行する前にcount_カラム名が使えるのはおかしい。
SQL上では先にcount関数が実行されてcount_カラム名が作られて一度中断、一連の処理が終わった後にcountの処理が再開されてハッシュを返している?
SQLのcountには集計する以上の機能がないのでハッシュにして返しているのはRails独自の機能かもしれない。なので先にcountが起動できて、別処理であるハッシュにする作業を後に行えるのかも?

.group.countと併用した場合なんかも処理が不思議な感じ。
配列の要素を数える.countメソッドなので、.groupすると配列に配列をネストしていると仮定すれば.countが使えそう。
だけどハッシュが返る道理がないので、特殊な処理が追加されいるっぽい?

mechanizeのsearchメソッド

hogehoge = agent.get(url).search("hogehoge")
fugafuga = hogehoge.search("fugafuga")
↑↓同じ結果が返る。
fugafuga = agent.get(url).search("fugafuga")

limit.orderとorder.limitで結果が同じになるケースと同じ。
おそらくRailsの内部処理で悪さしている。(2文に分けてるつもりでも合算したSQL文が発行されてる?)

値渡し と 参照渡し と 参照の値渡し

以下は自明ですよね。

def aaa(num)
  num += 1
end
num = 3

aaa(num)
puts num  => 3

num = aaa(num)
puts num  => 4

では配列だとどうでしょうか?

def aaa(arr)
  arr << 1
end
arr = []

puts arr  => nil

aaa(arr)
puts arr  => 1

仮引数に値を代入しただけなのに実引数が変更されるので不思議ですよね?
なんで数字は予定通りの挙動で配列だと挙動が変わるのかが謎でした。

ポイントは「参照の値渡し」
railsは基本的に値渡しです。実引数を渡しても、コピーされて仮引数が使われるためメソッド外へ影響を及ぼしません。
ですが、配列を持つ変数については配列内の実態をそのまま持つのではなく、参照先が保存されています。なので仮引数にも参照先がコピーされており、参照先を変更するような処理をすると、あたかも実引数を変更したかのように見えるのです。実引数は変更していませんが、実引数の参照先が書き換えられているのです。

多くの言語ではオブジェクトを渡すと参照の値渡しになるので、変更を明示的に返さなくても変更が反映される。

疑問

インスタンスはハッシュか?

whereやfind等で取得したインスタンスは配列で格納される。
では個々のインスタンスの中身はハッシュなのか?配列なのか?

product = Products.find(1)
product.reviews
=> [#<Review:0x00007fac8d3e4c70
  id: 1,
  created_at: Fri, 19 Jul 2019 05:02:46 UTC +00:00,
  updated_at: Fri, 19 Jul 2019 05:02:46 UTC +00:00>,
 #<Review:0x00007fac900197f0
  id: 2,
  created_at: Fri, 19 Jul 2019 05:03:15 UTC +00:00,
  updated_at: Fri, 19 Jul 2019 05:03:15 UTC +00:00>]

配列で値が返る。[]で括られているので明示的に配列だと思う。
product.reviews[1]
=> #<Review:0x00007fac900197f0
 id: 2,
 nickname: "あああ",
 rate: 9,
 review: "skakakいいいううう\r\nえええ\r\nおおお",
 product_id: 10,
 created_at: Fri, 19 Jul 2019 05:03:15 UTC +00:00,
 updated_at: Fri, 19 Jul 2019 05:03:15 UTC +00:00>

配列?ハッシュ?
<>の括りはハッシュの意味だろうか?
[33] pry(main)> product.reviews[1].is_a?(Hash)
=> false
[34] pry(main)> product.reviews[1].is_a?(Array)
=> false
[35] pry(main)> product.reviews[1].respond_to?(:rate)
=> true
[36] pry(main)> product.reviews[1][1]
=> nil
[37] pry(main)> product.reviews[1][:rate]
=> 9

ハッシュかどうか尋ねてもfalseが返る。
しかしシンボルでキーを指定すると値を返すのでハッシュでいいと思う。
respond_to(:key)を試すとTrue。
正確かは保証しないが、ハッシュという認識でいいと思う。

before actionで行われる処理とインスタンス変数の参照?

before_action :set_tweet

def show
      @comments = @tweet.comments.includes(:user)
end

def set_tweet
      @tweet = Tweet.find(params[:id])
end

これでshowメソッド内で@tweetが動くのはなぜ?
before actionの内部的な処理はどうなってるの?

showが呼ばれた時の動きとして、
set_tweet
show
の順でメソッドを呼んでいるのならset_tweet内で定義した@tweetは次のメソッドで利用できない。

def show
      set_tweet
      @comments = @tweet.comments.includes(:user)
end

みたいに書き換えているとしても結局変数を呼べないよね?
少なくともrubyの記法ならこんな書き方しても機能しないはず。

理解

そもそも”@”tweetと書くということは、これはただの変数ではなくインスタンス変数。
では@tweetを属性に持つインスタンスとは何か?

おそらくコントローラークラスのインスタンスである。
つまりこの例で言うと、Tweetsクラスのインスタンス(hogehoge)が属性として@tweetを持っている。
なので他のメソッド(インスタンスメソッド)を利用しても共通してインスタンス(hogehoge)の属性を扱うことができる。

おそらくrailsでは、コントローラが呼び出される際に自動的にコントローラクラスのインスタンスが生成される。
このインスタンスに対して、コントローラ内のインスタンスメソッドで処理を行い、インスタンス変数を格納していく。
ビューで使う場合にもこのインスタンスが引き渡されているはずなので、html.erb内で@付きの変数(インスタンス変数)を利用できる。

Rails: 提案「コントローラから`@`ハックを消し去ろう」(翻訳)|TechRacho by BPS株式会社
概要原著者の許諾を得て翻訳・公開いたします。英語記事:Removingthe@HackinRailsControllers–EricAnderson–Medium原文公開日:2018/06/02著者:EricAnderson週刊Railsウォッチで絶賛された記事です。RailsWayから外れるのでRails中級以上向け...

何故 Rails では View から Controller のインスタンス変数にアクセスできるのか
この記事はOkinawa.rbAdventCalendar2016 6日目の記事です。はじまりむかしむかし、あるところでひとりのあっとんさんがOkinawa.rbAdventCalendarのネタをどうるか考えていました。そこへ @Cod...

いくつかのサイトをみてみたが、大筋の理解は間違ってなさそう。
ただし、@付きの変数に関してはインスタンス変数だから、ではなくこれを目印として別途viewへ引き渡すための監視を特別に行なっている模様。

部分テンプレート?

投稿時と編集時で同じフォームを使いまわそうとした際に、下記のようにしても動く。
変数を渡していないのにフォーム上にeditしたい値が埋まった状態で表示されるのはなぜ???

edit.html.erb
<%= render partial: "form"%>

コントローラ
def edit
  @post = Post.find(params[:id])
end

_form.html.erb

<%= form_for(@post, html:{class: "col s12"}) do |f| %>
    <div class="input-field col s12">
      <%= f.text_area :text, autocomplete: "text" %>
      <%= f.label :text , for: "textarea1"%>
    </div>
<% end %>
リソースの編集画面を作って、編集処理を実装する | Ruby on Rails 始めました
 RubyonRailsで電話帳アプリを作っています。 前回までで、電話帳リストの一覧画面と新規登録画面を作りました。 今回は、既に登録しているデータを編集する画面、及びその機能を作っていきます。

フォーム用のような単一のインスタンスの場合、変数に保存されている情報がそのまま使えるため、わざわざrenderに引き渡さなくてもOKな感じ?
通常はrenderする場合はcollection等でバラして使うので、わざわざlocalを書いているものと思われる。

deviseのuser controllerはどうすべき?

users controllerはrails g controller usersで作るのが正しいのか?
userモデルに対してuser controllerとdevise controllerの2つが管理することになって、気持ち悪いんだけど普通なんでしょうか?
user#showとかを使うためにrails g controller userしているケースが結構見られる。

params.require

わざわざrequireメソッドを使っているが、ハッシュなので普通に取り出しても動く。
params.require(:review)
params[:review]
なのでparamsに対して.require().permit()とする理由は可読性だけ?

.new(class)に外部キーっぽい配列を引き渡した場合の処理

has_many throughでuserとgroupモデルが繋がっているときに

def create
  @group = Group.new(create_params)
  @group.save
end

private
def create_params
  params.require(:group).permit(:name, user_ids: [])
end

“user_ids”=>[“”, “1”, “2”]を引き渡したところ、

@group
=> #<Group:0x00007ffb6f749268 id: nil, name: "sgggg", created_at: nil, updated_at: nil>

@group.users
=> [#<User id: 1, email: "aaa@aaa", name: "hogehoge">,
 #<User id: 2, email: "fdg@dfg", name: "fugafufa">]

が返った。.newに配列を渡すと複数生成されるのか?
そもそも配列を渡してよいのか?
initializeメソッドでの処理だと思うが、その実態はどのように確認したらよいのか?

コメント