I have implemented friends relationsships by using the same procedure as in http://railscasts.com/episodes/163-self-referential-association. I want to fetch friends of friends from the same city, but I cannot figure out howto get friends of friends and from the same city.
I tried calling:
User.find_by(first_name: user_name).friendships.friendships(:f_of_f).cities.where(name: "london").pluck(:f)
and it didnt work.
my code is the following:
class User < ActiveRecord::Base
has_many :friendships
has_many :friends, through: :friendships
has_many :inverse_friendships, class_name: 'Friendship', foreign_key: 'friend_id'
has_many :inverse_friends, through: :inverse_friendships, source: :user
has_many :cities, through: :user_cities
has_many :user_cities
belongs_to :company
has_many :comments, dependent: :delete_all
has_many :reviews, dependent: :delete_all
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
def self.friends_reviews_on_company(user_id, city_id)
User.find_by(first_name: user_name).friendships.friendships(:f_of_f).cities.where(name: "london").pluck(:f)
end
end
and city:
class City < ActiveRecord::Base
has_many :user_cities
has_many :users, through: :user_cities
end
and user_city:
class UserCity < ActiveRecord::Base
belongs_to :city
belongs_to :user
end
and friendship:
class Friendship < ActiveRecord::Base
belongs_to :user
belongs_to :friend, class_name: 'User'
end
Error I tried solution:
PG::UndefinedTable: ERROR: missing FROM-clause entry for table "friends_friends_of_friends_join"
LINE 1: ...ndships" "friendships_friends_of_friends_join" ON "friends_f...
^
: SELECT "users".* FROM "users" INNER JOIN "user_cities" ON "user_cities"."user_id" = "users"."id" INNER JOIN "cities" ON "cities"."id" = "user_cities"."city_id" INNER JOIN "friendships" ON "users"."id" = "friendships"."friend_id" INNER JOIN "friendships" "friendships_friends_of_friends_join" ON "friends_friends_of_friends_join"."id" = "friendships_friends_of_friends_join"."friend_id" WHERE "friendships"."user_id" = $1 AND "friendships_friends_of_friends_join"."user_id" = $2 AND "cities"."name" = 'London' LIMIT 1 OFFSET 0
Rendered search/index.html.erb within layouts/application (8.4ms)
Completed 500 Internal Server Error in 27ms
ActionView::Template::Error (PG::UndefinedTable: ERROR: missing FROM-clause entry for table "friends_friends_of_friends_join"
LINE 1: ...ndships" "friendships_friends_of_friends_join" ON "friends_f...
^
: SELECT "users".* FROM "users" INNER JOIN "user_cities" ON "user_cities"."user_id" = "users"."id" INNER JOIN "cities" ON "cities"."id" = "user_cities"."city_id" INNER JOIN "friendships" ON "users"."id" = "friendships"."friend_id" INNER JOIN "friendships" "friendships_friends_of_friends_join" ON "friends_friends_of_friends_join"."id" = "friendships_friends_of_friends_join"."friend_id" WHERE "friendships"."user_id" = $1 AND "friendships_friends_of_friends_join"."user_id" = $2 AND "cities"."name" = 'London' LIMIT 1 OFFSET 0):
ActiveRecord isn't the neo4j gem, and you're running into one of the big reasons where ActiveRecord is harder.
I think this is about the most efficient you'll get without writing SQL:
# Getting a query for multiple friends, even if we're only expecting one so that we can use `includes` method
friend_user_ids = User.where(first_name: user_name).includes(:friendships).map(&:friendships).flatten.map(&:friend_id)
fof_user_ids = Friendship.find(user_id: friend_user_ids).pluck(:friend_id)
city = City.find(name: 'london')
result = User.find(UserCity.where(user_id: fof_user_ids).where(city_id: city.id).pluck(:user_id))
Alternatively you could do something like this:
city = City.find(name: 'london')
result_ids = User.where(first_name: user_name)
.joins('LEFT JOIN friendships ON users.id=friendships.user_id LEFT JOIN friendships AS friendships2 ON friendships.friend_id=friendships2.user_id LEFT JOIN users_cities ON friendships2.friend_id=users_cities.user_id')
.where('users_cities.city_id = ?', city.id)
.pluck('friendships2.friend_id')
result = User.find(result_ids)
Related
I have the models Province and User and I need to stop a Province from being deleted if a User has that Province in it's address.
I can do User.province thanks to the following concern included in the model User:
module MyAddressable
extend ActiveSupport::Concern
included do
has_one :address, as: :addressable, dependent: :destroy
has_one :city, through: :address
has_one :province, through: :city
has_one :zone, through: :city
accepts_nested_attributes_for :address, reject_if: :reject_address, allow_destroy: true
end
end
I'm trying to establish the relationship between Province and User to be able to do something like Province.users, in the following way:
has_many :users, through: :myaddresable
With the following result:
ActiveRecord::HasManyThroughAssociationNotFoundError: Could not find the association :myaddresable in model Province
Same thing if I try to define the relationship as
has_many :users, through: :addressable
Can this be done? If so, what would be the right way to do it?
has_many :users, through: :addressable doesn't work because Province model has no knowledge about the Address model.
We can build the relationship between the Province model and User model through Address model.
The following setup works in rails 6
User Model
class User < ApplicationRecord
has_one :address, as: :addressable, dependent: :destroy
has_one :city, through: :address
has_one :province, through: :city
end
Province Model
class Province < ApplicationRecord
has_many :cities
has_many :users, through: :cities
end
City Model
class City < ApplicationRecord
has_many :addresses
has_many :users,
through: :addresses,
source: :addressable,
source_type: 'User'
belongs_to :province
end
Address Model
class Address < ApplicationRecord
belongs_to :addressable, polymorphic: true
belongs_to :city
end
Let's assume the migrations are defined correctly as per the model associations. Now the following queries work...
irb(main): > User.first.province
DEBUG -- : User Load (0.6ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1 [["LIMIT", 1]]
DEBUG -- : Province Load (0.3ms) SELECT "provinces".* FROM "provinces" INNER JOIN "cities" ON "provinces"."id" = "cities"."province_id" INNER JOIN "addresses" ON "cities"."id" = "addresses"."city_id" WHERE "addresses"."addressable_id" = $1 AND "addresses"."addressable_type" = $2 LIMIT $3 [["addressable_id", 1], ["addressable_type", "User"], ["LIMIT", 1]]
irb(main): > Province.first.users
DEBUG -- : Province Load (0.5ms) SELECT "provinces".* FROM "provinces" ORDER BY "provinces"."id" ASC LIMIT $1 [["LIMIT", 1]]
DEBUG -- : User Load (0.5ms) SELECT "users".* FROM "users" INNER JOIN "addresses" ON "users"."id" = "addresses"."addressable_id" INNER JOIN "cities" ON "addresses"."city_id" = "cities"."id" WHERE "cities"."province_id" = $1 AND "addresses"."addressable_type" = $2 [["province_id", 1], ["addressable_type", "User"]]
In your case, as the MyAddressable concern is already included in the User model, only the other associations and migrations need to be defined.
Hope this helps. Thank you.
Lets say I have a User / Post model like the example here:
https://github.com/mbleigh/acts-as-taggable-on#tag-cloud-calculations
User has many Posts and Posts belong to User. Posts have tags. Can I easily search for Users that have a particular tagged post?
So you basically have:
class User
has_many :posts
end
class Post
acts_as_taggable
# under the hood, this declaration creates
# following relations
has_many :tags
has_many :taggings
end
You can now add the following relation to User:
class User
has_many :posts
has_many :tags, through: :posts
# has_many :taggings, through: :posts
end
Now querying for a given #tag should be easy:
users_with_give_tag = User.joins(:tags).where("tags.id=?", #tag.id)
which produces the following SQL:
SELECT "users".* FROM "users"
INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
INNER JOIN "taggings" ON "taggings"."taggable_id" = "posts"."id" AND "taggings"."context" = ? AND "taggings"."taggable_type" = ?
INNER JOIN "tags" ON "tags"."id" = "taggings"."tag_id"
WHERE (tags.id=1) [["context", "tags"], ["taggable_type", "Post"]]
I have 2 models with has_one and has_many associations.
realm.rb
class Realm < ActiveRecord::Base
has_one :realm_type, foreign_key: "id"
end
realm_type.rb
class RealmType < ActiveRecord::Base
has_many :realms, foreign_key: "realm_type_id"
end
But when i preforming sql request Realm.find(1).realm_type in rails console, i get
Realm Load (0.3ms) SELECT "realms".* FROM "realms" WHERE "realms"."id" = $1 [["id", 1]]
RealmType Load (0.3ms) SELECT "realm_types".* FROM "realm_types" WHERE "realm_types"."id" = $1 LIMIT 1 [["id", 1]]
As you see, it ignores foreign_key: "realm_type_id" for has_many association in realm_type.rb
UPD 1:
Replaced has_many with belongs_to, still get the same result
Shouldn't you be using belongs_to?
class RealmType < ActiveRecord::Base
has_many :realms, foreign_key: "realm_type_id"
end
class Realm < ActiveRecord::Base
belongs_to :realm_type
end
I have the following classes
class State < ActiveRecord::Base
has_many :cities
has_many :products, as: :geography
end
class City < ActiveRecord::Base
has_many :neighborhoods
has_many :products, as: :geography
end
class Neighborhood < ActiveRecord::Base
has_many :products, as: :geography
end
class Product < ActiveRecord::Base
belongs_to :geography, polymorphic: true
end
class User < ActiveRecord::Base
belongs_to :state
end
And obviously I have another class User. The user just has permissions for see the products in his/her state (except if the user is admin, in which case he/she can see all the products) How I can get all the products of a User?
I tried to add some has_many, :through to State as follows
has_many :cities_activities, through: :cities, source: :products
has_many :neighborhoods_activities, through: :neighborhoods, source: :products
def owned_activities
self.products + self.cities_activities + self.neighborhoods_activities
end
but owned_activities returns an Array not a ActiveRecord::Relation (I need some way of return a ActiveRecord::Relation, so I can apply on it chained scopes).
I am patching the code with if-blocks and the code is getting messing and ugly, How can I do this in a clean, rails way?
#JTG suggested using merge, but apparently it don't work in a nested way (or for more of three tables/models):
Started GET "/api/products.json?order=total_cost&page=1&per_page=18" for 127.0.0.1 at 2014-07-20 09:27:29 -0500
Processing by ProductsController#index as JSON
Parameters: {"order"=>"total_cost", "page"=>"1", "per_page"=>"18"}
Geokit is using the domain: localhost
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1 ORDER BY "users"."id" ASC LIMIT 1
Role Load (0.3ms) SELECT "roles".* FROM "roles" WHERE "roles"."id" = $1 ORDER BY "roles"."id" ASC LIMIT 1 [["id", 5]]
State Load (0.2ms) SELECT "states".* FROM "states" WHERE "states"."id" = $1 ORDER BY "states"."id" ASC LIMIT 1 [["id", 17]]
(9.2ms) SELECT COUNT(*) FROM "products" INNER JOIN "cities" ON "products"."geography_id" = "cities"."id" AND "products"."geography_type" = 'City' INNER JOIN "polygons" ON "products"."geography_id" = "neighborhoods"."id
" AND "products"."geography_type" = 'Neighborhood' INNER JOIN "cities" ON "neighborhoods"."cities_id" = "cities"."id" WHERE "products"."geography_id" = $1 AND "products"."geography_type" = $2 AND "cities"."states_id" = $1 [["geography_id", 17], ["geography_type", "State"], ["states_id", 17]]
PG::DuplicateAlias: ERROR: table name "cities" specified more than once
: SELECT COUNT(*) FROM "products" INNER JOIN "cities" ON "products"."geography_id" = "cities"."id" AND "products"."geography_type" = 'City' INNER JOIN "neighborhoods" ON "products"."geography_id" = "neighborhoods"."id" AND "products"."geography_type" = 'Neighborhood' INNER JOIN "cities" ON "neighborhoods"."cities_id" = "cities"."id" WHERE "products"."geography_id" = $1 AND "products"."geography_type" = $2 AND "cities"."states_id" = $1
ERROR: table name "cities" specified more than once
-- Clase:
You can't use merge will combine the queries with AND and what you need is a SQL OR query. That is unfortunately not supported by any ActiveRecord method. You can use arel to create this query, but as it will be very complex and contain a lot of subqueries do I find it easier to just find the id's you need with normal queries and then use those.
To do that can you implement something like this in State.
class State < ActiveRecord::Base
has_many :cities
has_many :neighborhoods, through: :cities
has_many :products, as: :geography
has_many :city_products, through: :cities, source: :products
has_many :neighborhood_products, through: :neighborhoods, source: :products
def all_products
state_product_ids = product_ids
city_product_ids = city_products.pluck(:id)
neighborhood_product_ids = neighborhood_products.pluck(:id)
all_product_ids = [state_product_ids, city_product_ids, neighborhood_product_ids].flatten.uniq
Product.where(id: all_product_ids)
end
end
You can now query a user for all products like this user.state.all_products.
A polymorphic association is not suited optimally for these relationships...
You can use a nested has_many.
class State < ActiveRecord::Base
has_many :cities
has_many :products, through: :cities
end
class City < ActiveRecord::Base
has_many :neighborhoods
has_many :products, through: :neighborhoods
end
class Neighborhood < ActiveRecord::Base
has_many :products
end
class Product < ActiveRecord::Base
belongs_to :neighborhood
end
class User < ActiveRecord::Base
belongs_to :state
end
Then you should be able to run user.state.products
Even better would be to add has_many :products, through: :state to user.rb so you could run user.products
Ok, check this weirdness out. I have two types of users, leaders and followers (who each have their own subclasses for reasons that please for the love of god I don't want to go into and moreover are not germaine to this discussion).
class Admin < Account
has_many :leader_follower_relationships, foreign_key: :leader_id, dependent: :destroy
has_many :followers, through: :leader_follower_relationships
end
class Follower < Account
has_one :follower_leader_relationship, class_name: "LeaderfollwerRelationship",
dependent: :destroy
has_one :leader, through: :follower_leader_relationship
end
class LeaderFollowerRelationship < ActiveRecord::Base
belongs_to :follower, class_name: "Follower", foreign_key: :artist_id
belongs_to :leader, class_name: "Admin", foreign_key: :leader_id
end
Anyway, I can establish the relationship using a has_one, but I can't update it:
follower1.leader = leader1
(0.3ms) BEGIN
Account Exists (1.1ms) SELECT 1 AS one FROM "accounts" WHERE "accounts"."auth_token" IS NULL LIMIT 1
Account Exists (0.4ms) SELECT 1 AS one FROM "accounts" WHERE "accounts"."email_address" = 'leader1#example.com' LIMIT 1
SQL (0.6ms) ...
(0.5ms) COMMIT
follower1.leader = leader2
(0.3ms) BEGIN
(1.8ms) UPDATE "leader_follower_relationships" SET "leader_id" = 3 WHERE "leader_follower_relationships"."" IS NULL
(0.2ms) ROLLBACK
ActiveRecord::StatementInvalid: PGError: ERROR: zero-length delimited identifier at or near """"
LINE 1: ...ader_id" = 3 WHERE "leader_follower_relationships"."" IS NULL
If my follower can have many leaders (using has_many), I can both create and update:
class Follower < Account
has_many :follower_leader_relationships, class_name: "LeaderfollwerRelationship",
dependent: :destroy
has_many :leaders, through: :follower_leader_relationships
end
These commands work in succession:
follower1.leaders = [leader1]
follower1.leaders = [leader2]
The ActiveRecord error makes me think that the table for your LeaderFollowerRelationship model does not have an id column. If that's not it, can you post the related bits of schema.rb?