First model:
class AdPlace < ActiveRecord::Base
belongs_to :user
has_many :user_ads, dependent: :destroy
accepts_nested_attributes_for :user_ads, allow_destroy: true, reject_if: :all_blank
def ad_places_json
self.as_json(
include: {
user_ads: {
}
})
end
end
Second Model:
class UserAd < ActiveRecord::Base
belongs_to :ad_place
end
From my controller I am trying to get all AdPlaces having all UserAds.
So my method of controller is following:
def get_ads
ad_places = AdPlace.includes(:user_ads).where(user_id: #current_user.id)
render json: {
ad_places: ad_places.each {|ad| ad.ad_places_json},
message: ['success']
},
status: :ok
end
The above function renders all AdPlaces but UserAds.
The SELECT query on server is the following:
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", 7]]
AdPlace Load (0.9ms) SELECT "ad_places".* FROM "ad_places"
UserAd Load (0.5ms) SELECT "user_ads".* FROM "user_ads" WHERE "user_ads"."ad_place_id" IN (1, 8, 9, 10, 11, 12, 13)
Everything seems perfect to me, I am confused where am I making mistake.
Thank you.
Change each to map:
ad_places.map {|ad| ad.ad_places_json}
Array#each vs. Array#map
Related
In my index method for every model I make sure to query starting with current user: current_user.model.all, in order to only show models that belong to the current user.
My show method for all models is quite simple and standard, without current_user.
def show
#logica = Logica.find params[:id]
authorize #logica
end
This does open the chance of a user entering a random id in the url and see the model from a different user. What is the best way to prevent this from happening?
The has_many association has a number of different methods available including find
current_user.model.find(params[:id])
which is similar to
Model.where(user_id: current_user.id).find(params[:id])
If you have to go through several models in order to reach your User model, for instance
class User < ApplicationRecord
has_one :test_one
end
class TestOne < ApplicationRecord
has_one :test_three
belongs_to :user
end
class TestThree < ApplicationRecord
has_many :test_fours
belongs_to :test_one
end
class TestFour < ApplicationRecord
belongs_to :test_three
end
you can set it up in a single query by doing something like
TestFour.joins(:test_three => { :test_one => :user }).where(test_threes: { test_ones: { users: { id: current_user.id}}}).find(1)
# TestFour Load (1.2ms) SELECT "test_fours".* FROM "test_fours" INNER JOIN "test_threes" ON "test_threes"."id" = "test_fours"."test_three_id" INNER JOIN "test_ones" ON "test_ones"."id" = "test_threes"."test_one_id" INNER JOIN "users" ON "users"."id" = "test_ones"."user_id" WHERE "users"."id" = $1 AND "test_fours"."id" = $2 LIMIT $3 [["id", 1], ["id", 1], ["LIMIT", 1]]
#=> #<TestFour id: 1, test_three_id: 1, created_at: "2017-07-12 21:06:51", updated_at: "2017-07-12 21:06:51">
and then if you did this with a user id/test four id that don't match:
TestFour.joins(:test_three => { :test_one => :user }).where(test_threes: { test_ones: { users: { id: current_user.id + 1}}}).find(1)
# TestFour Load (0.8ms) SELECT "test_fours".* FROM "test_fours" INNER JOIN "test_threes" ON "test_threes"."id" = "test_fours"."test_three_id" INNER JOIN "test_ones" ON "test_ones"."id" = "test_threes"."test_one_id" INNER JOIN "users" ON "users"."id" = "test_ones"."user_id" WHERE "users"."id" = $1 AND "test_fours"."id" = $2 LIMIT $3 [["id", 2], ["id", 1], ["LIMIT", 1]]
#=> ActiveRecord::RecordNotFound: Couldn't find TestFour with 'id'=1 [WHERE "users"."id" = $1]
In my controller I have the following:
def sort
params[:order].each do |key,value|
Question.find(value[:id]).update_attribute(:order,value[:order])
end
render :nothing => true
end
This works perfectly to update the order column for the 'Question' item.
However i've now moved the order column to a new table 'question_sections' which is associated to Questions.
class Question < ActiveRecord::Base
has_many :sections, :through => :question_sections
belongs_to :section
has_many :question_sections
default_scope { order(order: :asc) }
accepts_nested_attributes_for :section, :reject_if => :all_blank, allow_destroy: true
accepts_nested_attributes_for :question_sections, :reject_if => :all_blank, allow_destroy: true
end
I'm trying to adapt the sort function to update the 'order' column in 'question_sections' but am having trouble with it.
Any help on what the function should look like?
In case you are using nested attributes, you shoud call the includes method, and then iterate over each question_sections:
def sort
params[:order].each do |key,value|
questions = Question.includes(:question_sections).find(value[:id])
questions.question_sections.each { |q| q.update_attribute(:order,value[:order]) }
end
render :nothing => true
end
This breaks the problems into 2 parts, load all the question_sections needed:
1) Load all the question_sections of a question:
questions = Question.includes(:question_sections).find(value[:id])
Question Load
SELECT "questions".* FROM "questions" WHERE "questions"."id" = ? LIMIT 1 [["id", 1]]
QuestionSections Load
SELECT "question_sections".* FROM "question_sections" WHERE "question_sections"."question_id" IN (1)
2) update this question_sections
questions.question_sections.each { |q| q.update_attribute(:order,value[:order]) }
QuestionSections Update
UPDATE "question_sections" SET "order" = ?, "updated_at" = ? WHERE "question_sections"."id" = ? [["order", "different order now"], ["updated_at", "2017-03-09 13:24:42.452593"], ["id", 1]]
I think if you are using nested_attributes for Question model here then Rails should automatically update the nested params for QuestionSection
The controller should look something like this:
def sort
#question = Question.find_by(id: params[:id])
#question.update_attributes(question_params)
end
private
def question_params
params.require(:question).permit!
end
The parameters received to the controller should be like :
params = { question: {
abc: 'abc', question_sections_attributes: [
{ order_id: 1, ... },
{ order_id: 2, ... },
{ order_id: 3, ... }
]
}}
I hope this helps :)
I have these models in my multi-tenant app:
class Tenant < ActiveRecord::Base
has_many :userroles
has_many :users, through: :userroles
has_many :roles, through: :userroles
has_many :admins, -> { joins(:roles).where("roles.name = 'admin'").uniq }, through: :userroles, class_name: 'User', source: :user
end
class Role < ActiveRecord::Base
has_paper_trail
acts_as_paranoid
has_many :userroles, :dependent => :destroy
has_many :users, :through => :userroles
end
class Userrole < ActiveRecord::Base
acts_as_tenant(:tenant)
has_paper_trail
belongs_to :user
belongs_to :role
end
I use gem ActsAsTenant written by Erwin (source code at github). When current_tenant doesn't set my code work right, but if I set current_tenant, I got errors.
In console I got those errors:
2.1.0 :001 > ActsAsTenant.current_tenant
=> nil
2.1.0 :002 > t = Tenant.first
2.1.0 :004 > t.admins.count
(1.5ms) SELECT DISTINCT COUNT(DISTINCT "users"."id") FROM "users" INNER JOIN "userroles" "userroles_users_join" ON "userroles_users_join"."user_id" = "users"."id" INNER JOIN "roles" ON "roles"."id" = "userroles_users_join"."role_id" AND "roles"."deleted_at" IS NULL INNER JOIN "userroles" ON "users"."id" = "userroles"."user_id" WHERE "users"."deleted_at" IS NULL AND "userroles"."tenant_id" = $1 AND (roles.name = 'admin') [["tenant_id", 1]]
=> 1
2.1.0 :005 > ActsAsTenant.current_tenant = t
2.1.0 :006 > t.admins.count
(2.6ms) SELECT DISTINCT COUNT(DISTINCT "users"."id") FROM "users" INNER JOIN "userroles" "userroles_users_join" ON "userroles_users_join"."user_id" = "users"."id" AND (userroles.tenant_id = 1) INNER JOIN "roles" ON "roles"."id" = "userroles_users_join"."role_id" AND "roles"."deleted_at" IS NULL INNER JOIN "userroles" ON "users"."id" = "userroles"."user_id" WHERE "users"."deleted_at" IS NULL AND "userroles"."tenant_id" = $1 AND (userroles.tenant_id = 1) AND (roles.name = 'admin') [["tenant_id", 1]]
PG::UndefinedTable: ERROR: invalid reference to FROM-clause entry for table "userroles"
LINE 1: ...erroles_users_join"."user_id" = "users"."id" AND (userroles....
^
HINT: Perhaps you meant to reference the table alias "userroles_users_join".
: SELECT DISTINCT COUNT(DISTINCT "users"."id") FROM "users" INNER JOIN "userroles" "userroles_users_join" ON "userroles_users_join"."user_id" = "users"."id" AND (userroles.tenant_id = 1) INNER JOIN "roles" ON "roles"."id" = "userroles_users_join"."role_id" AND "roles"."deleted_at" IS NULL INNER JOIN "userroles" ON "users"."id" = "userroles"."user_id" WHERE "users"."deleted_at" IS NULL AND "userroles"."tenant_id" = $1 AND (userroles.tenant_id = 1) AND (roles.name = 'admin')
ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR: invalid reference to FROM-clause entry for table "userroles"
Problem is when I set current_tenant. All works right before setting it. What could be the problem? In gem's code I can't find anything strange.
I change has_many condition:
has_many :admins, -> { for_tenanted_roles.where("roles.name = 'admin'").uniq }, through: :userroles, class_name: 'User', source: :user
And in user.rb
scope :for_tenanted_roles, -> { joins('INNER JOIN "roles" ON "roles"."id" = "userroles"."role_id" AND "roles"."deleted_at" IS NULL') }
It's just handy overrided joins(:roles)
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.
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