When I query directly against my database I get the expected result, but not in rails. I'm guessing it has to do with my associations and something bad I said about Ruby 3 years ago.
Postgres SQL Query:
SELECT users.email, members.software, members.files
FROM users INNER JOIN members
ON members.user_id = users.id
WHERE members.region_id=2
Result: "dan#gmail.com";t;t "dan#test.com";t;t
BUT from rails c:
> ←[1m←[36mUser Load (1.0ms)←[0m ←[1mSELECT users.email,
> members.software, members.files FROM "users" INNER JOIN "members" ON
> "members"."user_id" = "users"."id" WHERE "members"."region_id" =
> 2←[0m> => #<ActiveRecord::Relation [#<User id: nil, email: "dan#gmail.com">, #<User id: nil, email: "dan#test.com">]>
That snippet was the resulting query from pasting in what I have tried to create in my controller and hard coding the region id:
User.joins(:members).select("users.email, members.software, members.files").where(members: {region_id: params[:id]})
These are my models:
class User < ActiveRecord::Base
has_many :members
has_many :regions, :through => :members
end
class Region < ActiveRecord::Base
has_many :members
has_many :users, :through => :members
end
class Member < ActiveRecord::Base
belongs_to :user
belongs_to :region
end
Is it the way I have associated my models or something else that I am missing?
Thanks!
What you are getting is active_relations object.
you can access the attributes like this
users = User.joins(:members).select("users.email, members.software as software, members.files as files").where(members: {region_id: params[:id]})
users.each do |u|
p u.email
p u.software
p u.files
end
Related
I am trying to do two things:
query an attribute from an inner join table in Rails' Console.
query and displaying the attribute in a view.
These are my Models:
retreat.rb:
class Retreat < ApplicationRecord
belongs_to :user
belongs_to :account
validates :name, presence: true
has_many :retreats_teams
has_many :teams, through: :retreats_teams
accepts_nested_attributes_for :retreats_teams
end
retreats_team.rb:
class RetreatsTeam < ApplicationRecord
belongs_to :team
belongs_to :retreat
end
team.rb:
class Team < ApplicationRecord
belongs_to :account
has_many :team_members
has_many :users, through: :team_members
accepts_nested_attributes_for :team_members
has_many :retreats
has_many :retreats, through: :retreats_teams
end
In Rails' console, if I type:
Retreat.last.teams
I get the output:
irb(main):008:0> Retreat.last.teams
Retreat Load (0.9ms) SELECT "retreats".* FROM "retreats" ORDER BY "retreats"."id" DESC LIMIT $1 [["LIMIT", 1]]
Team Load (0.9ms) SELECT "teams".* FROM "teams" INNER JOIN "retreats_teams" ON "teams"."id" = "retreats_teams"."team_id" WHERE "retreats_teams"."retreat_id" = $1 LIMIT $2 [["retreat_id", 38], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Team id: 56, name: "My house", account_id: 2, created_at: "2020-02-10 15:57:25", updated_at: "2020-02-10 15:57:25">]>
irb(main):009:0>
How do I retrieve the team name: "My house"?
Also, there might be many teams that display here, too.
#teams returns a collection of team objects. The simplest solution is to call first on the teams to get the first team in the collection:
Retreat.last.teams.first.name
=> "My house"
But if you want all the names in teams you might use pluck. This will allow you to do this:
retreat = Retreat.last
foo = Team.create(name: 'Foo')
bar = Team.create(name: 'Bar')
retreat.teams << foo
retreat.teams << bar
retreat.teams.pluck(:name).to_sentence
=> "My house, Foo, and Bar"
A word on naming
The naming convention for join models is SingularSingular. The table should be named singular_plural. has_and_belongs_to_many is the only part of Rails that actually uses the oddball plural_plural naming scheme.
RetreatsTeam # bad
RetreatTeam # better
Even better though is to actually give your join tables meaningful names instead of just placeholder names.
1) querying an attribute from an inner join table in Rails Console.
Since the association between Retreat and RetreatsTeams in one to many you can actually only fetch aggregates. Otherwise which attribute should it fetch, from the first row, the last row or all the rows?
So for example you can do:
Retreat.joins(:retreats_teams)
.select('retreats.*', 'COUNT(retreats_teams.*) AS retreats_teams_count')
If you are storing more data on the join table that you want to display you want to iterate through the join table:
#retreat = Retreat.eager_load(retreats_teams: :teams).first
#retreat.retreats_teams.each do |rt|
puts rt.foo
puts rt.team.name
end
2) querying and displaying the attribute in a view.
In Rails you're usually just fetching records in the controller and then iterating through them in the view:
class ResortsController < ApplicationController
def show
#resort = Resort.includes(:teams).find(params[:id])
end
end
# app/views/resorts/show.html.erb
<h1><%= #resort.name %></h1>
<h2>Teams</h2>
<% if #resort.teams.any? %>
<ul>
<% #resort.teams.each do |team| %>
<li><%= team.name %></li>
<% end %>
</ul>
<% else %>
<p>This resort has no teams</p>
<% end %>
I have classic has_many through relationship where I need to be able to add multiple Companies to particular User. Models look like this:
class Company < ApplicationRecord
has_many :accounts, dependent: :destroy
has_many :users, through: :accounts
end
class Account < ApplicationRecord
belongs_to :company, inverse_of: :accounts
belongs_to :user, inverse_of: :accounts
accepts_nested_attributes_for :company, :user
end
class User < ApplicationRecord
has_many :accounts, dependent: :destroy
has_many :companies, through: :accounts
end
In console I can add single record with this:
[1] pry(main)> user=User.find(7)
[2] pry(main)> user.accounts.create(company_id: 1)
How do I add, edit, delete multiple accounts for user in one query? I need to attach multiple Companies to User, then Edit / Remove if necessary.
So far I tried to implement array part from this tutorial, but somehow it does not work as obviously I'm doing something wrong here:
[4] pry(main)> user.accounts.create(company_id: [1,2])
(0.4ms) BEGIN
User Exists (1.3ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER($1) AND ("users"."id" != $2) LIMIT $3 [["email", "tester#gmaill.com"], ["id", 7], ["LIMIT", 1]]
(0.6ms) COMMIT
=> #<Account:0x00000005b2c640 id: nil, company_id: nil, user_id: 7, created_at: nil, updated_at: nil>
As I understand I need to create array somehow and then operate with that. I would appreciate any help here. Thank you!
Solution
If anyone needs, I solved my problem a bit differently. I used checkboxes from this tutorial and it works just fine for me.
Here's an example with the bulk_insert gem:
company_ids = [1,2]
user_id = 1
Account.bulk_insert(
values: company_ids.map do |company_id|
{
user_id: user_id,
company_id: company_id,
created_at: Time.now,
updated_at: Time.now
}
end
)
I have User and Review models. A review can have an author and a subject, both pointing to a User:
class Review < ApplicationRecord
belongs_to :subject, class_name: 'User', optional: true
belongs_to :author, class_name: 'User', optional: true
end
class CreateReviews < ActiveRecord::Migration[5.0]
def change
create_table :reviews do |t|
t.references :subject
t.references :author
end
end
end
This works fine and now I can assign two separate User objects to the Review object to represent who wrote the review against whom.
The user though, doesn't "know" how many reviews he's associated with either as a subject or the author. I added has_and_belongs_to_many :users on reviews and vice-versa, and though doable, isn't exactly what I want.
How do I set up the associations to be able to do the following:
review.author = some_other_user
review.subject = user2
another_review.author = some_other_user
another_review.subject = user2
user2.a_subject_in.count
#=> 2
user2.a_subject_in
#=> [#<Review>, #<Review>]
some_other_user.an_author_in.count
#=> 2
In other words, how do I see how many times a User has been saved as an author or subject for a model with belongs_to?
IF you want to use has_many association on users side, you need to define two separate has_many relations like
class User < ApplicationRecord
has_many :reviews, foreign_key: :author_id
has_many :subject_reviews, class_name: 'Review', foreign_key: :subject_id
end
Now with this you can simply use
irb(main):033:0> s.reviews
Review Load (0.2ms) SELECT "reviews".* FROM "reviews" WHERE "reviews"."author_id" = ? [["author_id", 1]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Review id: 1, comment: "random", subject_id: 2, author_id: 1, created_at: "2016-07-12 01:16:23", updated_at: "2016-07-12 01:16:23">]>
irb(main):034:0> s.subject_reviews
Review Load (0.2ms) SELECT "reviews".* FROM "reviews" WHERE "reviews"."subject_id" = ? [["subject_id", 1]]
=> #<ActiveRecord::Associations::CollectionProxy []>
Comment: subject_reviews is not a good name :), change it to your requirements.
I think you're looking for this query:
class User
def referenced_in
# this fetches you all the reviews that a user was referenced
Review.where("reviews.author_id = :user_id OR reviews.subject_id = :user_id", user_id: id).distinct
end
end
User.first.referenced_in #should give you all records a user was referenced
I've got a User model/table and a Friend model/table. The users table has a uid column that stores a user's Facebook uid. The friends table stores their Facebook friends (associated by a user_id column).
How can I query all of a specific user's friends who have an account in the users table? In other words - I want the user models returned for user 13's friends who have signed up (exist in the users table).
My models for same problem
class User < ActiveRecord::Base
has_many :friendships
has_many :friends, through: :friendships
# ...
end
class Friendship < ActiveRecord::Base
belongs_to :user
belongs_to :friend, :class_name => 'User', :foreign_key => 'friend_id', :primary_key => 'facebook_id'
end
Assuming you the user of interest is referenced by #user:
User.joins("inner join friends on friends.uid = users.uid")
.where("friends.user_id = ?", #user.id)
There is probably a more "railsy" way to do this that makes better use of arel.
If I have the models:
class User < ActiveRecord::Base
attr_accessible :name, :uid
has_many :friends
end
class Friend < ActiveRecord::Base
attr_accessible :uid
belongs_to :user
belongs_to :account, foreign_key: 'uid', primary_key: 'uid', class_name: 'User'
end
then I can do:
u=User.create(name: 'Alice', uid: 'fb1')
u2=User.create(name: 'Bob', uid: 'fb2')
u.friends.create(uid: 'fb2') # add Bob as friend
u.friends.create(uid: 'fb9') # add someone as friend
Now
u.friends # gives all friends
Friend Load (0.3ms) SELECT "friends".* FROM "friends" WHERE "friends"."user_id" = 4
=> [#<Friend id: 3, user_id: 4, uid: "fb2">, #<Friend id: 4, user_id: 4, uid: "fb9">]
u.friends.joins(:account) # only friends having an account
Friend Load (0.2ms) SELECT "friends".* FROM "friends" INNER JOIN "users" ON "users"."uid" = "friends"."uid" WHERE "friends"."user_id" = 4
=> [#<Friend id: 3, user_id: 4, uid: "fb2">]
while includes doesn't do an inner join and gives all friends:
u.friends.includes(:account)
Friend Load (0.2ms) SELECT "friends".* FROM "friends" WHERE "friends"."user_id" = 4
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."uid" IN ('fb2', 'fb9')
=> [#<Friend id: 3, user_id: 4, uid: "fb2">, #<Friend id: 4, user_id: 4, uid: "fb9">]
Class Sale
belongs_to :product, :accessible => true
belongs_to :brand, :accessible => true
end
Class Product
has_many :sales
belongs_to :brand
end
Class Brands
has_many :products
has_many :sales
end
How do i get the brands that have the most product sales?
If you want to stay with activerecord you can use Calculations but it will take 2 queries to accomplish it:
>> brands = Sale.count(:id, :include => :brand, :group=> 'sales.brand_id', :limit => 5)
SQL (0.7ms) SELECT count(DISTINCT `sales`.id) AS count_id, sales.brand_id AS sales_brand_id FROM `sales` LEFT OUTER JOIN `brands` ON `brands`.id = `sales`.brand_id GROUP BY sales.brand_id LIMIT 5
=> #<OrderedHash {967032312=>3, 1392881137=>1}>
>> brands_with_most_sales = Brand.find(brands.keys)
Brand Load (0.5ms) SELECT * FROM `brands` WHERE (`brands`.`id` = 967032312)
=> [#<Brand id: 967032312, title: "brand-x", created_at: "2009-11-19 02:46:48", updated_at: "2009-11-19 02:46:48">]
Otherwise you might want to write you own query using find_by_SQL