Weird relationship behavior on Rails 3 and update_attribute - ruby-on-rails

I'm having a hard time trying to find out why a test is failing:
describe User, "Instance Methods" do
describe "leave_group!" do
it "should set group_id to nil" do
#user = Factory(:user)
#group_2 = Factory(:group, :owner => #user)
#user.leave_group!
#user.reload.group_id.should be_nil # THIS LINE IS FAILING!!!
end
it "should assign another owner to group, if owner was user" do
#user = Factory(:user)
#group = Factory(:group, :owner => #user)
1.upto(4) { #group.add_user Factory(:user) }
#user.leave_group!
#group.reload.owner.should_not eql(#user)
end
end
end
These are the models I'm using:
class User < ActiveRecord::Base
# Associations
has_one :own_group, :class_name => "Group", :foreign_key => "owner_id"
belongs_to :group
def leave_group!
current_group_id, current_group_owner = self.group.id, self.group.owner
self.group_id = nil
save!
Group.find(current_group_id).randomize_owner! if current_group_owner == self
end
end
class Group < ActiveRecord::Base
# Associations
has_many :users
belongs_to :owner, class_name: "User"
def randomize_owner!
current_users = self.users
return false unless current_users.length > 1
begin
new_user = current_users.sort_by { rand }.first
end while self.owner == new_user
self.owner_id = new_user.id
save!
end
end
Am I doing something wrong here? Could I improve it? And more importantly, why is that single test failing?
Here's the log output for runing that single test:
SQL (0.2ms) SELECT name
FROM sqlite_master
WHERE type = 'table' AND NOT name = 'sqlite_sequence'
AREL (0.3ms) INSERT INTO "users" ("name", "uid", "provider", "email", "image_url", "group_id", "created_at", "updated_at", "timezone", "public_readings", "is_visible_on_leaderboards", "phone_number") VALUES ('John Doe', '314159265', 'facebook', 'john#does.com', NULL, NULL, '2011-07-18 02:02:08.455229', '2011-07-18 02:02:08.455229', NULL, 't', 't', NULL)
Group Load (0.1ms) SELECT "groups".* FROM "groups" WHERE "groups"."key" = 'SNYEMJ' LIMIT 1
AREL (0.1ms) INSERT INTO "groups" ("name", "key", "score", "owner_id", "created_at", "updated_at") VALUES ('John''s Group', 'SNYEMJ', 0, 1, '2011-07-18 02:02:08.522442', '2011-07-18 02:02:08.522442')
AREL (0.0ms) UPDATE "users" SET "group_id" = 1, "updated_at" = '2011-07-18 02:02:08.524342' WHERE "users"."id" = 1
Group Load (0.1ms) SELECT "groups".* FROM "groups" WHERE "groups"."id" = 1 LIMIT 1
User Load (0.2ms) SELECT "users".* FROM "users" WHERE ("users".group_id = 1)
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT 1
The last 3 lines are all selects, notice rails doesn't even try to remove the group_id from the user. (There are 2 inserts, 1 for the test user and 1 for the test group and 1 update which assigns group_id to the test user).

Try adding a #user.reload call before #user.leave_group in the test.
Even though the user record is updated with it's group in the DB when you create #group_2 from the factory, I suspect the #user object is not. Then you call leave_group! with a #user with a group ID of nil, so the save won't do anything because the object is unchanged. Then in the next line of your test you reload the #user, which now has the group_id from the DB assigned earlier.

try to chance the model class as following:
class User < ActiveRecord::Base
# Associations
has_one :own_group, :class_name => "Group", :foreign_key => "owner_id"
belongs_to :group
def leave_group!
group_randomize_owner
self.group.clear
end
private
def group_randomize_owner
if group.owner == self
group.randomize_owner!
end
end
end

Related

How do I create a scope so that I can search acts-as-taggable-on name column with ILIKE and return photo_id?

acts-as-taggable-on gem
In Heroku Console,
=> Photo.find(106).taggings
Photo Load (1.7ms) SELECT "photos".* FROM "photos" WHERE "photos"."id" = $1 ORDER BY "photos"."created_at" DESC LIMIT 1 [["id", 106]]
Photo Load (1.7ms) SELECT "photos".* FROM "photos" WHERE "photos"."id" = $1 ORDER BY "photos"."created_at" DESC LIMIT 1 [["id", 106]]
ActsAsTaggableOn::Tagging Load (1.1ms) SELECT "taggings".* FROM "taggings" WHERE "taggings"."taggable_id" = $1 AND "taggings"."taggable_type" = $2 [["taggable_id", 106], ["taggable_type", "Photo"]]
ActsAsTaggableOn::Tagging Load (1.1ms) SELECT "taggings".* FROM "taggings" WHERE "taggings"."taggable_id" = $1 AND "taggings"."taggable_type" = $2 [["taggable_id", 106], ["taggable_type", "Photo"]]
=> #<ActiveRecord::Associations::CollectionProxy [#<ActsAsTaggableOn::Tagging id: 78, tag_id: 32, taggable_id: 106, taggable_type: "Photo", tagger_id: nil, tagger_type: nil, context: "tags", created_at: "2016-08-11 04:09:55">
You can see that there is no :name column in ActsAsTaggableOn::Tagging but pay attention to tag_id: 32
=> ActsAsTaggableOn::Tag.find(32)
ActsAsTaggableOn::Tag Load (1.0ms) SELECT "tags".* FROM "tags" WHERE "tags"."id" = $1 LIMIT 1 [["id", 32]]
ActsAsTaggableOn::Tag Load (1.0ms) SELECT "tags".* FROM "tags" WHERE "tags"."id" = $1 LIMIT 1 [["id", 32]]
=> #<ActsAsTaggableOn::Tag id: 32, name: "pickled", taggings_count: 2>
The following code works in the controller:
#photos = Photo.approved.tagged_with(params[:tag]).paginate(page: params[:page], per_page: 20) if params[:tag].present?
but it does not allow for tagged_with to be searched with ILIKE. The tag banana pudding will be returned only if banana pudding is queried.
Photo.find(106).tag_list
Photo Load (1.0ms) SELECT "photos".* FROM "photos" WHERE "photos"."id" = $1 ORDER BY "photos"."created_at" DESC LIMIT 1 [["id", 106]]
ActsAsTaggableOn::Tag Load (1.0ms) SELECT "tags".* FROM "tags" INNER JOIN "taggings" ON "tags"."id" = "taggings"."tag_id" WHERE "taggings"."taggable_id" = $1 AND "taggings"."taggable_type" = $2 AND (taggings.context = 'tags' AND taggings.tagger_id IS NULL) [["taggable_id", 106], ["taggable_type", "Photo"]]
=> ["pickled"]
Edit
Alternatively, how can I setup my new photo form so that a hidden field :tags copies the tag_list column upon creation?
Edit for models
=> ActsAsTaggableOn::Tagging(id: integer, tag_id: integer, taggable_id: integer, taggable_type: string, tagger_id: integer, tagger_type: string, context: string, created_at: datetime)
=> ActsAsTaggableOn::Tag(id: integer, name: string, taggings_count: integer)
=> Photo(id: integer, user_id: integer, picture: string, title: string, description: string, photo_type: integer, location_type: integer, remote_picture_url: string, created_at: datetime, updated_at: datetime, approved: boolean, flags_count: integer)
tagging.rb
module ActsAsTaggableOn
class Tagging < ::ActiveRecord::Base #:nodoc:
belongs_to :tag, class_name: '::ActsAsTaggableOn::Tag', counter_cache: ActsAsTaggableOn.tags_counter
belongs_to :taggable, polymorphic: true
belongs_to :tagger, {polymorphic: true}.tap {|o| o.merge!(optional: true) if ActsAsTaggableOn::Utils.active_record5? }
scope :owned_by, ->(owner) { where(tagger: owner) }
scope :not_owned, -> { where(tagger_id: nil, tagger_type: nil) }
scope :by_contexts, ->(contexts) { where(context: (contexts || 'tags')) }
scope :by_context, ->(context = 'tags') { by_contexts(context.to_s) }
validates_presence_of :context
validates_presence_of :tag_id
validates_uniqueness_of :tag_id, scope: [:taggable_type, :taggable_id, :context, :tagger_id, :tagger_type]
after_destroy :remove_unused_tags
private
def remove_unused_tags
if ActsAsTaggableOn.remove_unused_tags
if ActsAsTaggableOn.tags_counter
tag.destroy if tag.reload.taggings_count.zero?
else
tag.destroy if tag.reload.taggings.count.zero?
end
end
end
end
end
tag.rb
#encoding: utf-8
module ActsAsTaggableOn
class Tag < ::ActiveRecord::Base
### ASSOCIATIONS:
has_many :taggings, dependent: :destroy, class_name: '::ActsAsTaggableOn::Tagging'
### VALIDATIONS:
validates_presence_of :name
validates_uniqueness_of :name, if: :validates_name_uniqueness?
validates_length_of :name, maximum: 255
# monkey patch this method if don't need name uniqueness validation
def validates_name_uniqueness?
true
end
### SCOPES:
scope :most_used, ->(limit = 20) { order('taggings_count desc').limit(limit) }
scope :least_used, ->(limit = 20) { order('taggings_count asc').limit(limit) }
def self.named(name)
if ActsAsTaggableOn.strict_case_match
where(["name = #{binary}?", as_8bit_ascii(name)])
else
where(['LOWER(name) = LOWER(?)', as_8bit_ascii(unicode_downcase(name))])
end
end
def self.named_any(list)
clause = list.map { |tag|
sanitize_sql_for_named_any(tag).force_encoding('BINARY')
}.join(' OR ')
where(clause)
end
def self.named_like(name)
clause = ["name #{ActsAsTaggableOn::Utils.like_operator} ? ESCAPE '!'", "%#{ActsAsTaggableOn::Utils.escape_like(name)}%"]
where(clause)
end
def self.named_like_any(list)
clause = list.map { |tag|
sanitize_sql(["name #{ActsAsTaggableOn::Utils.like_operator} ? ESCAPE '!'", "%#{ActsAsTaggableOn::Utils.escape_like(tag.to_s)}%"])
}.join(' OR ')
where(clause)
end
def self.for_context(context)
joins(:taggings).
where(["taggings.context = ?", context]).
select("DISTINCT tags.*")
end
### CLASS METHODS:
def self.find_or_create_with_like_by_name(name)
if ActsAsTaggableOn.strict_case_match
self.find_or_create_all_with_like_by_name([name]).first
else
named_like(name).first || create(name: name)
end
end
def self.find_or_create_all_with_like_by_name(*list)
list = Array(list).flatten
return [] if list.empty?
existing_tags = named_any(list)
list.map do |tag_name|
comparable_tag_name = comparable_name(tag_name)
existing_tag = existing_tags.find { |tag| comparable_name(tag.name) == comparable_tag_name }
begin
existing_tag || create(name: tag_name)
rescue ActiveRecord::RecordNotUnique
# Postgres aborts the current transaction with
# PG::InFailedSqlTransaction: ERROR: current transaction is aborted, commands ignored until end of transaction block
# so we have to rollback this transaction
raise DuplicateTagError.new("'#{tag_name}' has already been taken")
end
end
end
### INSTANCE METHODS:
def ==(object)
super || (object.is_a?(Tag) && name == object.name)
end
def to_s
name
end
def count
read_attribute(:count).to_i
end
class << self
private
def comparable_name(str)
if ActsAsTaggableOn.strict_case_match
str
else
unicode_downcase(str.to_s)
end
end
def binary
ActsAsTaggableOn::Utils.using_mysql? ? 'BINARY ' : nil
end
def unicode_downcase(string)
if ActiveSupport::Multibyte::Unicode.respond_to?(:downcase)
ActiveSupport::Multibyte::Unicode.downcase(string)
else
ActiveSupport::Multibyte::Chars.new(string).downcase.to_s
end
end
def as_8bit_ascii(string)
if defined?(Encoding)
string.to_s.dup.force_encoding('BINARY')
else
string.to_s.mb_chars
end
end
def sanitize_sql_for_named_any(tag)
if ActsAsTaggableOn.strict_case_match
sanitize_sql(["name = #{binary}?", as_8bit_ascii(tag)])
else
sanitize_sql(['LOWER(name) = LOWER(?)', as_8bit_ascii(unicode_downcase(tag))])
end
end
end
end
end

Rails 4.1 enum wrong where conditions

Today i want change my Postgres enum to Rails 4.1 enum.
I make some steps.
migrations:
create_table :users do |t|
# other column here
t.column :status, :integer
end
in model:
class User < ActiveRecord::Base
enum status: [ :local, :tourist ]
end
now create user:
=> User.create(email: 'ffoo#mai.com', password: '12345678', status: 'local')
=> #<User id: "53621ec84d6163124", email: "ffoo#mai.com", status: 0, ......>
=> u = User.last
=> User Load (0.9ms) SELECT "users".* FROM "users" ........
=> #<User id: "53621ec84d6163124", email: "ffoo#mai.com", status: 0, ......>
=> u.local?
=> true
Try find local users(and now i have strange behavior of my enum):
=> User.where("status <> ?", User.statuses[:local])
=> User Load (0.9ms) SELECT "users".* FROM "users" WHERE (status <> 0)
=> []
WTF?
=> User.where("status <> ?", User.statuses[:tourist])
=> User Load (0.9ms) SELECT "users".* FROM "users" WHERE (status <> 1)
=> #<User id: "53621ec84d6163124", email: "ffoo#mai.com", status: 0, ......>
So problem, this query User.where("status <> ?", User.statuses[:local]) should return my local user. Or i doing something wrong?
You are using a wrong operator here.
"status <> ?" equals "status != ?"
Instead you want to use
"status = ?"
Postgres comparison operators

Rails - Model Associations - User and Product - Custom methods

I'm creating an app with two main models : User and Product. User can have many products as an owner, and many products as a borrower. Product have only one owner, but can have many seekers, including a borrower. I associated them directly for the owning property, but for the borrowing property, I created a Transaction model. The three look like this :
app/models/transaction.rb
class Transaction
# has a seeker_id:integer, a product_id:integer and a current:boolean
before_save :check_current
# Associations
belongs_to :seeker, class_name: "User", foreign_key: "seeker_id"
belongs_to :product
# Methods
def check_current
if !self.borrowing_date.nil? && self.return_date.nil?
self.current = true
end
end
end
A product has many transactions, but it can be borrowed by only one seeker at the time. When the product is borrowed, the transaction has a borrowing_date that is not nil, and a return_date that is nil. Then the check_current method toggles the current boolean of this transaction from false to true. The seeker of that current transaction is specified as a borrower.
app/models/user.rb
class User
.
.
.
has_many :owned_products, class_name: "Product", foreign_key: "owner_id", dependent: :destroy
has_many :transactions, foreign_key: "seeker_id", dependend: :destroy
has_many :requested_products, through: :transactions, source: :product
has_many :active_transactions, -> { where current: true },
class_name: 'Transaction',
foreign_key: "seeker_id",
dependent: :destroy
has_many :borrowed_products, through: :active_transactions,
source: :product
def requesting?(product)
self.transactions.find_by(product_id: product.id)
end
def request!(product)
self.transactions.create!(product_id: product.id)
end
def borrowing?(product)
self.transactions.find_by(product_id: product.id, current: true)
end
def borrowed_products
self.transactions.where(current: :true).product
end
end
app/models/products.rb
class Product
.
.
.
belongs_to :owner, class_name: "User", foreign_key: "owner_id"
has_many :transactions, dependent: :destroy
has_many :seekers, through: :transactions,
source: :seeker
def borrowed?
self.transactions.find_by(current: true)
end
def borrower
self.transactions.find_by(current: true).seeker
end
end
When I testing some of my code, five of the tests fail, the same type, and I don't understand why :
describe User do
before { #user = User.new(name: "Utilisateur de test",
email: "test#utilisateur.com",
password: "motdepasse",
password_confirmation: "motdepasse") }
subject { #user }
describe "requested product associations" do
let(:lender) { FactoryGirl.create(:user) }
let(:product) { FactoryGirl.create(:product, owner: lender) }
before do
#user.save
#user.request!(product)
end
it { should be_requesting(product) }
its(:requested_products) { should include(product) } # FAIL
describe "when product is borrowed" do
before do
transaction = Transaction.find_by(product: product)
transaction.update_attributes(borrowing_date: 1.day.ago)
transaction.save
end
it { should be_borrowing(product) }
its(:requested_products) { should_not include(product) } # FAIL
its(:borrowed_products) { should include(product) } # FAIL
describe "then returned" do
before do
transaction = Transaction.find_by(product: product)
transaction.update_attributes(return_date: 1.hour.ago)
end
it { should_not be_borrowing(product) }
its(:requested_products) { should_not include(product) } # FAIL
its(:borrowed_products) { should_not include(product) } # FAIL
end
end
end
end
Here are the error messages :
1) User requested product associations requested_products
Failure/Error: its(:requested_products) { should include(product) }
ActiveRecord::StatementInvalid:
SQLite3::SQLException: ambiguous column name: created_at: SELECT 1 AS one FROM "products" INNER JOIN "transactions" ON "products"."id" = "transactions"."product_id" WHERE "transactions"."seeker_id" = ? AND "products"."id" = 1 ORDER BY created_at DESC LIMIT 1
# ./spec/models/user_spec.rb:174:in `block (3 levels) in <top (required)>'
2) User requested product associations when product has been borrowed borrowed_products
Failure/Error: its(:borrowed_products) { should include(product) }
ActiveRecord::StatementInvalid:
SQLite3::SQLException: ambiguous column name: created_at: SELECT 1 AS one FROM "products" INNER JOIN "transactions" ON "products"."id" = "transactions"."product_id" WHERE "transactions"."seeker_id" = ? AND "transactions"."current" = 't' AND "products"."id" = 1 ORDER BY created_at DESC LIMIT 1
# ./spec/models/user_spec.rb:185:in `block (4 levels) in <top (required)>'
3) User requested product associations when product has been borrowed requested_products
Failure/Error: its(:requested_products) { should_not include(product) }
ActiveRecord::StatementInvalid:
SQLite3::SQLException: ambiguous column name: created_at: SELECT 1 AS one FROM "products" INNER JOIN "transactions" ON "products"."id" = "transactions"."product_id" WHERE "transactions"."seeker_id" = ? AND "products"."id" = 1 ORDER BY created_at DESC LIMIT 1
# ./spec/models/user_spec.rb:184:in `block (4 levels) in <top (required)>'
4) User requested product associations when product has been borrowed then returned requested_products
Failure/Error: its(:requested_products) { should_not include(product) }
ActiveRecord::StatementInvalid:
SQLite3::SQLException: ambiguous column name: created_at: SELECT 1 AS one FROM "products" INNER JOIN "transactions" ON "products"."id" = "transactions"."product_id" WHERE "transactions"."seeker_id" = ? AND "products"."id" = 1 ORDER BY created_at DESC LIMIT 1
# ./spec/models/user_spec.rb:195:in `block (5 levels) in <top (required)>'
5) User requested product associations when product has been borrowed then returned borrowed_products
Failure/Error: its(:borrowed_products) { should_not include(product) }
ActiveRecord::StatementInvalid:
SQLite3::SQLException: ambiguous column name: created_at: SELECT 1 AS one FROM "products" INNER JOIN "transactions" ON "products"."id" = "transactions"."product_id" WHERE "transactions"."seeker_id" = ? AND "transactions"."current" = 't' AND "products"."id" = 1 ORDER BY created_at DESC LIMIT 1
# ./spec/models/user_spec.rb:196:in `block (5 levels) in <top (required)>'
But when I run some tests manually in the rails console, the user.borrowed_products and user.requested_products work just fine. Weird ???
For the first failing test
def borrowed_products
self.transactions.where(current: :true).product
end
The above method checks for current: true. I don't see you setting the attribute in your transaction setup.
before do
transaction = Transaction.find_by(product: product)
transaction.update_attributes(borrowing_date: 1.day.ago) #Why are you setting borrowing date. How is borrowing date and current related?
transaction.save
end
For the Second test.
requested_products association is established through transactions. You are not setting up a transaction. Is it done in your factory?
OK I found ! Yippee-ki-yay !
The error messages were telling me the created_at column was ambiguous. But why ? Because there are as much created_at column as there are associated models ! So it had something to do with it. But where the created_at appeared in my code ?
I checked my app/models/transaction.rb, my app/models/user.rb and my app/models/product.rb, and in this last model, I found the line :
default_scope -> { order('created_at DESC') }
That I changed to that, just to try :
default_scope -> { order('name DESC') }
And everything went just fine !
But now, if I want to scope it by created_at, I don't know how to do it :-p

Rails 3.1: Why do all the dealt cards end up attached to a single player?

I'm new to Rails, so I apologize for the title of the question, I didn't know how phrase it. Feel free to change it. I'm building a poker game to learn rails and I have the following associations...
class Game < ActiveRecord::Base
has_many :players, :dependent => :destroy
has_many :community_cards, :class_name => "Card", :dependent => :destroy, :conditions => { :is_community_card => true }
has_many :used_cards, :class_name => "Card", :dependent => :destroy, :conditions => { :is_community_card => false }
attr_accessible :pot, :name, :status
class Player < ActiveRecord::Base
belongs_to :game
has_many :cards, :dependent => :destroy
attr_accessible :chip_count, :position, :fb_id
end
class Card < ActiveRecord::Base
belongs_to :player
belongs_to :game
attr_accessible :face, :suit, :is_community_card
end
When I attempt to deal out random cards to all the players, all the cards end up with a single player...
def deal_players_hole_cards
players.all.each do |p|
if(p.cards.count < 2)
first_card = deal_card()
second_card = deal_card()
p.cards << first_card
p.cards << second_card
end
end
end
Here's the deal card method...
def deal_card
card_was_found = false
while(!card_was_found) do
card_was_found = true
random_suit = (0..3).to_a.sample
random_face = (1..13).to_a.sample
used_cards.all.each do |used_card|
if(random_suit == used_card.suit and random_face == used_card.face)
card_was_found = false
end
end
end
new_card = Card.create(:suit => random_suit, :face => random_face, :is_community_card => false)
used_cards << new_card
end
There are two players and each player should have two cards, but instead, one player has all four cards...
ruby-1.9.2-p290 :001 > Game.last.players.last.cards.count
Game Load (0.1ms) SELECT "games".* FROM "games" ORDER BY "games"."id" DESC LIMIT 1
Player Load (0.1ms) SELECT "players".* FROM "players" WHERE "players"."game_id" = 2 ORDER BY "players"."id" DESC LIMIT 1
(0.2ms) SELECT COUNT(*) FROM "cards" WHERE "cards"."player_id" = 6
=> 4
ruby-1.9.2-p290 :002 > Game.last.players.first.cards.count
Game Load (0.4ms) SELECT "games".* FROM "games" ORDER BY "games"."id" DESC LIMIT 1
Player Load (0.3ms) SELECT "players".* FROM "players" WHERE "players"."game_id" = 2 LIMIT 1
(0.2ms) SELECT COUNT(*) FROM "cards" WHERE "cards"."player_id" = 5
=> 0
Thanks so much in advance for all your wisdom!
I ended up fixing this by passing in the player object to the deal card method and attaching the newly created cards to the player by using the build method...
####################################################################
# Deals each player their two hole cards
####################################################################
def deal_players_hole_cards
players.all.each do |p|
if(p.cards.count < 2)
deal_card(p)
deal_card(p)
end
end
end
####################################################################
# returns a random, unused card
####################################################################
def deal_card(p)
card_was_found = false
while(!card_was_found) do
card_was_found = true
random_suit = (0..3).to_a.sample
random_face = (1..13).to_a.sample
used_cards.all.each do |used_card|
if(random_suit == used_card.suit and random_face == used_card.face)
card_was_found = false
end
end
end
new_card = p.cards.build(:suit => random_suit, :face => random_face, :is_community_card => false)
used_cards << new_card
end
Results...
ruby-1.9.2-p290 :005 > Game.last.players.first.cards.count
Game Load (0.3ms) SELECT "games".* FROM "games" ORDER BY "games"."id" DESC LIMIT 1
Player Load (0.4ms) SELECT "players".* FROM "players" WHERE "players"."game_id" = 2 LIMIT 1
(0.3ms) SELECT COUNT(*) FROM "cards" WHERE "cards"."player_id" = 5
=> 2
ruby-1.9.2-p290 :006 > Game.last.players.last.cards.count
Game Load (0.4ms) SELECT "games".* FROM "games" ORDER BY "games"."id" DESC LIMIT 1
Player Load (0.4ms) SELECT "players".* FROM "players" WHERE "players"."game_id" = 2 ORDER BY "players"."id" DESC LIMIT 1
(0.2ms) SELECT COUNT(*) FROM "cards" WHERE "cards"."player_id" = 6
=> 2
I'm still curious why my old code doesn't work.

How to use ActiveRecord's INCLUDES when looping over an Object

I have the following:
#rooms = current_user.rooms
Then I need to build a JSON object so I do:
render :json => room_json_index(#rooms)
Then to build the JSON object:
def room_json_index(rooms)
#roomList = Array.new
rooms.each do |room|
#roomList << {
:id => room.id,
:user_id => room.user_id,
:user_count => room_members.length,
:user_photos => room_members.collect { |room_member|
{
:id => room_member.user.id,
:photo => room_member.user.photo(:thumb),
:name => room_member.user.full_name
}
}
}
end
#roomList.to_json
end
Problem here is that in every loop of rooms.each, rails keeps hitting the data for the same user objects. Is that necessary. I see in the logs that it is caching but I'd rather rails not even have to thinking about it.
roomMember Load (1.1ms) SELECT "room_members".* FROM "room_members" INNER JOIN "users" ON "users"."id" = "room_members"."user_id" WHERE ("room_members".room_id = 143) AND (users.guest = false OR users.guest = true AND users.fname IS NOT NULL OR users.id = 3)
CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = 3 LIMIT 1
CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = 69 LIMIT 1
User Load (0.6ms) SELECT "users".* FROM "users" WHERE "users"."id" = 70 LIMIT 1
Room
Rails in the logs is repeating the request above over and over, looking for the same records over and over. Any ideas on how this can be optimized?
Thanks
seems your data model is like
class User << AR::Base
has_many :room_members
has_many :rooms,:through=>:room_members,:include=>:users
......
end
class Room << AR::Base
has_many :room_members
has_many :users,:through=>:room_members
......
end
class RoomMember << AR::Base
belongs_to :room
belongs_to :user
......
end
you can load user when load room
class User << AR::Base
has_many :room_members
has_many :rooms,:through=>:room_members,:include=>:users
......
end

Resources