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]
Related
I've a Rails 5.2.1 app where each step of a relationship works, but the has_many :through version doesn't. The setup is a little strange, but I feel like I've set everything up correctly, so I'm a little stumped.
Given this code:
class Contact < SalesforceModel
self.table_name = 'salesforce.contact'
self.primary_key = 'sfid'
has_many :content_accesses, foreign_key: 'contact__c', class_name: 'ContentAccess'
has_many :concepts, through: :content_accesses, source: :inventory
end
class ContentAccess < ApplicationRecord
self.table_name = 'salesforce.content_access__c'
self.primary_key = 'sfid'
belongs_to :inventory, foreign_key: 'inventory__c', inverse_of: :content_accesses, primary_key: 'sfid', class_name: 'Inventory'
belongs_to :contact, foreign_key: 'contact__c', inverse_of: : content_accesses, primary_key: 'sfid', class_name: 'Contact'
end
class Inventory < SalesforceModel
self.table_name = 'salesforce.inventory__c'
self.primary_key = 'sfid'
has_many :content_accesses, foreign_key: 'inventory__c'
has_many :contacts, through: :content_accesses
end
Each step of the has_many :through works:
# Setup
2.5.1 :001 > contact = Contact.first
Contact Load (30.6ms) SELECT "salesforce"."contact".* FROM "salesforce"."contact" ORDER BY "salesforce"."contact"."sfid" ASC LIMIT $1 [["LIMIT", 1]]
=> #<Contact lastname: "Doe", mailingpostalcode: "90210", name: "John Doe", mobilephone: nil, birthdate: nil, phone: nil, mailingstreet: "123 ABC Street", isdeleted: false, systemmodstamp: "2018-03-16 00:09:01", mailingstatecode: "CA", createddate: "2018-03-15 17:50:44", mailingcity: "LA", mailingcountrycode: "US", firstname: "John", email: "john.doe#realisp.com", sfid: "003m000000txXhwAAE", id: "003m000000txXhwAAE", _hc_lastop: "SYNCED", _hc_err: nil>
# Accessing related ContentAccess works
2.5.1 :002 > contact.content_accesses.count
(2.0ms) SELECT COUNT(*) FROM "salesforce"."content_access__c" WHERE "salesforce"."content_access__c"."contact__c" = $1 [["contact__c", "003m000000txXhwAAE"]]
=> 2
# Accessing related Inventory, through the related ContentAccess works
2.5.1 :003 > contact.content_accesses.first.inventory
ContentAccess Load (0.6ms) SELECT "salesforce"."content_access__c".* FROM "salesforce"."content_access__c" WHERE "salesforce"."content_access__c"."contact__c" = $1 ORDER BY "salesforce"."content_access__c"."sfid" ASC LIMIT $2 [["contact__c", "003m000000txXhwAAE"], ["LIMIT", 1]]
Inventory Load (30.4ms) SELECT "salesforce"."inventory__c".* FROM "salesforce"."inventory__c" WHERE "salesforce"."inventory__c"."sfid" = $1 LIMIT $2 [["sfid", "a1mm0000001S9qzAAC"], ["LIMIT", 1]]
=> #<Inventory createddate: "2018-05-23 15:09:41", isdeleted: false, name: "Some Concept Name", systemmodstamp: "2018-05-23 15:09:42", sfid: "a1mm0000001S9qzAAC", id: "a1mm0000001S9qzAAC", _hc_lastop: "SYNCED", _hc_err: nil>
# Accessing the related inventory through the has_many :through does not work
2.5.1 :004 > contact.concepts.count
(33.0ms) SELECT COUNT(*) FROM "salesforce"."inventory__c" INNER JOIN "salesforce"."content_access__c" ON "salesforce"."inventory__c"."sfid" = "salesforce"."content_access__c"."inventory__c" WHERE "salesforce"."content_access__c"."contact__c" = $1 [["contact__c", "003m000000txXhwAAE"]]
=> 0
Running the generated query in Postgres works, though:
app_development=# SELECT COUNT(*) FROM "salesforce"."inventory__c" INNER JOIN "salesforce"."content_access__c" ON "salesforce"."inventory__c"."sfid" = "salesforce"."content_access__c"."inventory__c" WHERE "salesforce"."content_access__c"."contact__c" = '003m000000txXhwAAE';
count
-------
2
(1 row)
Running Contact.first.concepts.to_sql produces:
SELECT "salesforce"."inventory__c".* FROM "salesforce"."inventory__c" INNER JOIN "salesforce"."content_access__c" ON "salesforce"."inventory__c"."sfid" = "salesforce"."content_access__c"."inventory__c" WHERE "salesforce"."content_access__c"."contact__c" = '003m000000txXhwAAE'
Running that query through psql works fine, returning the proper records from the inventory__c table.
The reverse also has the same problem:
2.5.1 :002 > inventory = Inventory.first
Inventory Load (30.2ms) SELECT "salesforce"."inventory__c".* FROM "salesforce"."inventory__c" ORDER BY "salesforce"."inventory__c"."sfid" ASC LIMIT $1 [["LIMIT", 1]]
=> #<Inventory createddate: "2018-05-23 15:09:41", isdeleted: false, name: "Positive Focus", systemmodstamp: "2018-05-23 15:09:42", inventory_unique_name__c: "Inventory 1", sfid: "a1mm0000001S9qzAAC", id: "a1mm0000001S9qzAAC", _hc_lastop: "SYNCED", _hc_err: nil>
2.5.1 :003 > inventory.content_accesses.first.contact
ContentAccess Load (0.9ms) SELECT "salesforce"."content_access__c".* FROM "salesforce"."content_access__c" WHERE "salesforce"."content_access__c"."inventory__c" = $1 ORDER BY "salesforce"."content_access__c"."sfid" ASC LIMIT $2 [["inventory__c", "a1mm0000001S9qzAAC"], ["LIMIT", 1]]
Contact Load (30.9ms) SELECT "salesforce"."contact".* FROM "salesforce"."contact" WHERE "salesforce"."contact"."sfid" = $1 LIMIT $2 [["sfid", "003m000000txXhwAAE"], ["LIMIT", 1]]
=> #<Contact sfid: "003m000000txXhwAAE", id: "003m000000txXhwAAE", [...etc...] >
2.5.1 :004 > inventory.contacts.count
(30.7ms) SELECT COUNT(*) FROM "salesforce"."contact" INNER JOIN "salesforce"."content_access__c" ON "salesforce"."contact"."sfid" = "salesforce"."content_access__c"."contact__c" WHERE "salesforce"."content_access__c"."inventory__c" = $1 [["inventory__c", "a1mm0000001S9qzAAC"]]
=> 0
So: everything seems to be hooked up correctly, so why isn't the through version working? Any help would be appreciated.
Thanks! ❤️
Thanks to #Zabba's comment asking about SalesforceModel, I was able to track down the problem. Fundamentally, it was this:
class ContentAccess < ApplicationRecord
It should have been:
class ContentAccess < SalesforceModel # <-- it was using the wrong table, effectively.
This has been a harrowing week, but it's better now. 😀
I have two models with nested attributes: Order and Item. Every order has multiple items.
class Order < ApplicationRecord
has_many :items, autosave: true
accepts_nested_attributes_for :items,
:allow_destroy => true
end
class Item < ApplicationRecord
belongs_to :order
end
I have a setter method on the Order model to handle operations like update Item attributes, Create new Items, Delete Item, via the Orders views and Controller.
Everything works except for deleting an Item.
I would like to delete an Item when the User enters the :quantity as 0.
This is part of the Instance Setter Method with the logic that handles what I am trying to accomplish (Delete item on the edit order view (update order on controller) when the user enters 0 as the quantity).
elsif item[:id].present? && item[:qty].to_i <= 0
order_item = self.items.find item[:id]
order_item.mark_for_destruction
if I print order_item.marked_for_destruction? at the end of the elsif, I get true!... The problem is that the order gets successfully updated, but the Item stays there with the previous Quantity.
** UPDATE **
This is the whole setter method on the Order model. This instance method will update item (from Edit Order view), Destroy Item (when Qty = 0), and Create Item (from New Order view) on that order.
def items=(items=[])
items.select {|item| item[:id].present? || item[:qty].to_i.positive? }.each do |item|
if item[:id].present? && item[:qty].to_i.positive?
order_item = self.items.find item[:id]
order_item.update quantity: item[:qty],
unit: item[:unit],
catch_weight_data: item[:catch_weight_data],
elsif item[:id].present? && item[:qty].to_i <= 0
order_item = self.items.find item[:id]
order_item.mark_for_destruction
puts "Was the item marked for destruction?"
puts order_item.marked_for_destruction?
else
next unless item[:qty].to_i.positive?
lp = LabeledProduct.find item[:labeled_product_id]
order_item = self.items.new unit_price_in_cents: lp.price_in_cents,
case_price_in_cents: lp.case_price_in_cents,
data: lp.data,
quantity: item[:qty],
unit: item[:unit],
labeled_product_id: item[:labeled_product_id],
customer_labeled_product_id: item[:customer_labeled_product_id],
taxable: item[:taxable],
item_weight: item[:item_weight]
end
end
end
This is the server log ... I printed Was the Item marked for destruction? and order_item.marked_for_destruction? right after the method that updates items with qty = 0... as you can see it prints as true:
Started PATCH "/admin/customers/customer/orders/113" for 127.0.0.1 at
2018-07-19 10:26:53 -0700
Processing by Admin::Customer::OrdersController#update as HTML
Parameters: {"utf8"=>"✓","authenticity_token"=>"", "order"=>
{"status"=>"Submitted", "picked_at"=>"", "fulfilled_at"=>"", "items_attributes"=>{"0"=>{"taxable"=>"1", "unit_price"=>"2.47", "id"=>"307"}}, "items"=>[{"id"=>"307", **"qty"=>"0"**, "unit"=>"UNIT", "catch_weight_data"=>["", ""]}...
"commit"=>"Update Order", "customer_id"=>"customer", "id"=>"113"}
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 1], ["LIMIT", 1]]
Customer Load (0.3ms) SELECT "customers".* FROM "customers" WHERE "customers"."slug" = $1 LIMIT $2 [["slug", "customer"], ["LIMIT", 1]]
Order Load (0.5ms) SELECT "orders".* FROM "orders" WHERE "orders"."customer_id" = $1 AND "orders"."id" = $2 ORDER BY "orders"."created_at" DESC LIMIT $3 [["customer_id", 1], ["id", 113], ["LIMIT", 1]]
(0.2ms) BEGIN
Item Load (0.4ms) SELECT "items".* FROM "items" WHERE "items"."order_id" = $1 AND "items"."id" = $2 LIMIT $3 [["order_id", 113], ["id", 307], ["LIMIT", 1]]
Warehouse Load (0.2ms) SELECT "warehouses".* FROM "warehouses"
INNER JOIN "labeled_products" ON "warehouses"."id" =
"labeled_products"."warehouse_id" WHERE "labeled_products"."id"
= $1 LIMIT $2 [["id", 22215], ["LIMIT", 1]]
Was the item marked for destruction?
true
Item Load (0.4ms) SELECT "items".* FROM "items" WHERE
"items"."order_id" = $1 AND "items"."id" = 307 [["order_id",113]]
(0.2ms) COMMIT
Redirected to
http://localhost:3000/admin/customers/customer/orders/113
Completed 302 Found in 30ms (ActiveRecord: 2.4ms)
Controller Action:
def update
respond_to do |format|
if #order.update(order_params)
format.html { redirect_to [:admin, #customer, #order], notice: "Order was successfully updated." }
format.json { render :show, status: :ok, location: [:admin, #customer, #order] }
else
format.html { render :edit }
format.json { render json: #order.errors, status: :unprocessable_entity }
end
end
end
Some guidance will be appreciated.
Thank you!
I am having challenges writing a scope to display:
all cards belonging to events that have payments that belong to a specific user
i am currently able to display, all events that have payments that belong to a specific user using the scope scope :booked_events, -> (user) { joins(payments: :user).where(users: { id: user.id }) } in the event.rb file
some events have a card and some don't
could one kindly advise me how i display all events with a card that
have payments that belong to a specific user
event.rb
has_many :payments
has_one :card
scope :booked_events_with_cards, -> (user) { joins(payments: :user).where(users: { id: user.id }) }
card.rb
belongs_to :event
payment.rb
belongs_to :event
belongs_to :user
user.rb
has_many :payments
i tried the below in the card.rb file but i am unsure
belongs_to :event
has_many :payments, through: :event
scope :cards_belonging_to_booked_events, -> (user) { joins(payments: :event).where(users: { id: user.id }) }
but got the below error:
2.3.0 :012 > cards.cards_belonging_to_booked_events(user)
Card Load (0.6ms) SELECT "cards".* FROM "cards" INNER JOIN "events" ON "events"."id" = "cards"."event_id" INNER JOIN "payments" ON "payments"."event_id" = "events"."id" INNER JOIN "events" "events_payments" ON "events_payments"."id" = "payments"."event_id" WHERE "users"."id" = 4
SQLite3::SQLException: no such column: users.id: SELECT "cards".* FROM "cards" INNER JOIN "events" ON "events"."id" = "cards"."event_id" INNER JOIN "payments" ON "payments"."event_id" = "events"."id" INNER JOIN "events" "events_payments" ON "events_payments"."id" = "payments"."event_id" WHERE "users"."id" = 4
ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: users.id: SELECT "cards".* FROM "cards" INNER JOIN "events" ON "events"."id" = "cards"."event_id" INNER JOIN "payments" ON "payments"."event_id" = "events"."id" INNER JOIN "events" "events_payments" ON "events_payments"."id" = "payments"."event_id" WHERE "users"."id" = 4
or, am i to write the scope in event.rb file if i want to display all cards with events that have payments that have been made by a user?
You just need to include card association in joins. It removes events without card associated from the query result:
scope :booked_events_with_cards, -> (user) { joins(:card, payments: :user).where(users: { id: user.id }) }
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 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