Establishing a relationship between two models through a Concern - ruby-on-rails

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.

Related

Audited: Associated audits with HABTM relation

I've got model user and role
class User < ApplicationRecord
rolify strict: true
has_many :roles, through: :users_roles
has_associated_audits
class Role < ApplicationRecord
has_and_belongs_to_many :users, join_table: :users_roles
audited associated_with: :users, join_table: :users_roles
When I create a new role, I've got the error:
2.4.4 :373 > User.first.add_role Role.pi, ProjectRequest.find(319)
User Load (0.7ms) SELECT `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1
ProjectRequest Load (0.6ms) SELECT `project_requests`.* FROM `project_requests` WHERE `project_requests`.`id` = 319 LIMIT 1
Role Load (0.6ms) SELECT `roles`.* FROM `roles` WHERE `roles`.`name` = 'pi' AND `roles`.`resource_type` = 'ProjectRequest' AND `roles`.`resource_id` = 319 ORDER BY `roles`.`id` ASC LIMIT 1
(0.2ms) BEGIN
SQL (0.6ms) INSERT INTO `roles` (`name`, `resource_type`, `resource_id`, `created_at`, `updated_at`) VALUES ('pi', 'ProjectRequest', 319, '2018-06-19 11:40:13', '2018-06-19 11:40:13')
(54.3ms) ROLLBACK
NoMethodError: undefined method `primary_key' for User::ActiveRecord_Associations_CollectknowProxy:Class
I don't really now whats the problem, did I wrongly specified something?
Look like there is issue with association if it is has_and_belongs_to_many :users and join_table is users_roles on that case in roles table also it should be has_and_belongs_to_many :roles, join_table: :users_roles, and this will make HABM relationship.
I've resolved the problem by this tutorial:
http://blog.flatironschool.com/why-you-dont-need-has-and-belongs-to-many/
What I did was that I removed the HABTM relation and created model for the joining table.
class User < ApplicationRecord
rolify strict: true
has_many :users_roles
has_many :roles, through: :users_roles, dependent: :destroy
has_associated_audits
class Role < ApplicationRecord
has_many :users_roles
has_many :users, through: :users_roles, dependent: :destroy
audited associated_with: :users
class UsersRole < ApplicationRecord
# audited associated_with: :role
audited associated_with: :user
belongs_to :user
belongs_to :role
end
Now when the audits are created with change at the UsersRole instance.
The problem is that, the change's contains only the ids of destroyed columns, so you cannot figure, what it was.
This problem is partially solved there: https://github.com/collectiveidea/audited/issues/72#issuecomment-398756380

has_many through using wrong column

I've a simply rails application. It contains two models. Employee and EmployeeManager. I Basically want an Employee to have one or more Managers (other Employees) and I wish to be able to query these as you would expect.
Here's my Employee:
class Employee < ActiveRecord::Base
attr_accessible :name
has_many :employee_managers
has_many :managers, through: :employee_managers
has_many :employees, through: :employee_managers
end
And the manager model:
class EmployeeManager < ActiveRecord::Base
attr_accessible :employee_id, :manager_id
belongs_to :employee, foreign_key: "employee_id"
belongs_to :manager, class_name: "Employee", foreign_key: "manager_id"
end
This looks OK to me, however when I go to query employees and managers off an Employee Rails is using the same column (employee_id=X):
irb(main):008:0> Employee.first
Employee Load (0.3ms) SELECT "employees".* FROM "employees" LIMIT 1
=> #
irb(main):009:0> Employee.first.managers
Employee Load (0.1ms) SELECT "employees".* FROM "employees" LIMIT 1 Employee Load (0.1ms) SELECT "employees".* FROM "employees" INNER JOIN "employee_managers" ON "employees"."id" = "employee_managers"."manager_id" WHERE "employee_managers"."employee_id" = 1
=> []
irb(main):010:0> Employee.first.employees
Employee Load (0.4ms) SELECT "employees".* FROM "employees" LIMIT 1 Employee Load (0.3ms) SELECT "employees".* FROM "employees" INNER JOIN "employee_managers" ON "employees"."id" = "employee_managers"."employee_id" WHERE "employee_managers"."employee_id" = 1
=> []
How do I fix this last query? It should be using manager_id not employee_id!
Thanks in advance :)
Set the foreign_key in the Employee model, and add and inverse relation (its the same Model)
class Employee < ActiveRecord::Base
has_many :employee_managers
has_many :managers, through: :employee_managers
has_many :manager_employees, :class_name => "EmployeeManager", :foreign_key => "manager_id"
has_many :employees, through: :manager_employees, :source => "Employee"
end
Other changes:
You don't need attr_accessible :name or attr_accessible :employee_id, :manager_id. Just add attr_accessible for fields that are not in the database.

Why is foreign_key ignored?

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

howto get friends of friends using active record?

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)

How get all the objects in a polymorphic nested relationship in Rails 4

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

Resources