How do I combine the query from two different models - ruby-on-rails

I have three models that look like this
Twit
class Twit < ApplicationRecord
belongs_to :user
has_many :retwits, foreign_key: :twit_id, class_name: "Retwit", dependent: :destroy
has_many :mentions, dependent: :destroy
validates :body, presence: true, length: { maximum: 280 }
end
Retwit
class Retwit < ApplicationRecord
belongs_to :twit, foreign_key: :twit_id, class_name: 'Twit'
belongs_to :retwiter, foreign_key: :retwiter_id, class_name: 'User'
validates :twit, uniqueness: { scope: :retwiter }
end
User
class User < ApplicationRecord
has_many :twits, dependent: :destroy
has_many :retwits, foreign_key: :retwiter_id, class_name: 'Retwit', dependent: :destroy
end
I want to query for Twits and Retwits where (twit.user = some_user or retwit.retwiter = some_user). I am using this code to do the query, however it becomes an array so I cant user ActiveRecords collection methods on it:
(Twit.where(user: people) + Retwit.where(retwiter: people))
I wanna be able to use ActiveRecords collection methods on it like so:
(Twit.where(user: people) + Retwit.where(retwiter: people)).offset(0).limit(5)
Which doesn't work since adding the two queries together return an array.
Is it possible to return a collection of Twits and Retwits in one single query? If so, how?

Related

Trying to match Products and Requests through Categories

I'm making a rails marketplace app for uni where Users can be matched with specific products based on their request.
Users can list products that have specific categories.
Users can also list Requests where they can specify what products they're looking and their categories.
The aim is to match the request to a particular product based on the matching categories
Here are my models
class Product < ApplicationRecord
belongs_to :user
has_many_attached :images, dependent: :destroy
has_many :product_categories
has_many :categories, through: :product_categories
validates :user_id, presence: true
end
class Category < ApplicationRecord
has_many :product_categories
has_many :products, through: :product_categories
validates :name, presence: true, length: { minimum: 3, maximum: 25}
validates_uniqueness_of :name
end
class ProductCategory < ApplicationRecord
belongs_to :product
belongs_to :category
end
class Request < ApplicationRecord
belongs_to :user
has_many_attached :images, dependent: :destroy
has_many :request_categories
has_many :categories, through: :request_categories
validates :user_id, presence: true
end
class RequestCategory < ApplicationRecord
belongs_to :request
belongs_to :category
end
I was thinking of creating a new model called Match to bring together the product and categories or is it easier to match it in the request?
In my mind, your new Match class would essentially be a join table for a has_many :through association. Assuming that you're implementing an asynchronous worker (e.g. Sidekiq / ActiveJob) to go through and make "matches", you'll want to connect matches to a particular Request, and likely store some meta-data (has the user seen the Match yet? Have they rejected it?)
So, I'd probably generate a Match class like this:
rails g model Match seen_at:datetime deleted_at:datetime request:references product:references
And set up the associations as follows:
class Match < ApplicationRecord
belongs_to :request
belongs_to :product
end
class Request < ApplicationRecord
belongs_to :user
has_many_attached :images, dependent: :destroy
has_many :request_categories
has_many :categories, through: :request_categories
has_many :matches
has_many :products, through: :matches
validates :user_id, presence: true
end
class Product < ApplicationRecord
belongs_to :user
has_many_attached :images, dependent: :destroy
has_many :product_categories
has_many :categories, through: :product_categories
has_many :matches
has_many :requests, through: :matches
validates :user_id, presence: true
end
Also, you'll likely want to add the Request has_many :through to your Category model (I think you forgot that one):
class Category < ApplicationRecord
has_many :product_categories
has_many :products, through: :product_categories
has_many :request_categories
has_many :requests, through: :request_categories
validates :name, presence: true, length: { minimum: 3, maximum: 25}
validates_uniqueness_of :name
end
The big part of the job is working out how to have your app periodically look for matches - you may want to start with the Active Job Basics documentation.

Rails - accepts_nested_attributes_for and presence validation

In my application I have a simple relationship where a user that has many events:
class User < ApplicationRecord
has_many :events, foreign_key: 'created_by', dependent: :destroy
accepts_nested_attributes_for :events
end
class Event < ApplicationRecord
belongs_to :user, foreign_key: 'created_by', inverse_of: :events
validates :created_by, presence: true
end
When I am trying to create a user alongside with some events I am getting a validation error "Companies.created by can't be blank".
My params hash looks like that:
{"user"=>{"events_attributes"=>[{"name"=>"disney show"}]}}
When I remove validates :created_by, presence: true everything works as expected. Any help would be appreciated!
Try to specify explicitly bi-directional associations in your User model using inverse_of: :
class User < ApplicationRecord
has_many :events, foreign_key: 'created_by', inverse_of: :user, dependent: :destroy
accepts_nested_attributes_for :events
end
And your Event record will be created with present foreign key pointing to User.

How to create a scope for current_user.following?

Here are my User and Relationships models
class User < ActiveRecord::Base
has_many :active_relationships, class_name: "Relationship",
foreign_key: "follower_id",
dependent: :destroy
has_many :passive_relationships, class_name: "Relationship",
foreign_key: "followed_id",
dependent: :destroy
has_many :followers, through: passive_relationships, source: :follower
has_many :following, through: :active_relationships, source: :followed
class Relationship < ActiveRecord::Base
belongs_to :follower, class_name: "User", counter_cache: :followeds_count
belongs_to :followed, class_name: "User", counter_cache: :followers_count
validates :follower_id, presence: true
validates :followed_id, presence: true
validates :followed, uniqueness: { scope: [:follower, :followed] }
end
In Users Controller I can do:
#users = current_user.following
However I would like to turn this into a scope in my User model.
There are 2 things you may approach:
Find all users who are following someone
class User < ActiveRecord::Base
scope :following_to, -> (user_id) {
where(
"id IN ( SELECT followed_id
FROM relationships
WHERE follower_id = ?
)",
user_id
)
}
end
Find all users who are following anyone, that means they are a follower
class User < ActiveRecord::Base
scope :follower, -> {
where("id IN ( SELECT followed_id FROM relationships)")
}
end
Finally, you can use these scope as your expectation:
# Find all users who are following to User (id = 1)
User.following_to(1)
# Find all users who are following someone,
# aka they are a follower
User.follower
By using the Instance Method you can make a method For User Model
like this :
class User < ActiveRecord::Base
def following?
self.following.present?
end
end
By Using Scope you can call only the activerecord based query into the scope of model.
You should get also this way
scope :following?, lambda { |user|
{ user.following.present? }
And this should be call like in your controller
User.following?(current_user)

Rails has_many :through with the where clause

I've built easy Twitter application in Rails.
Now I would like to choose three random users that are not followed by the current user.
Here is my model:
class User < ActiveRecord::Base
has_many :tweets, dependent: :destroy
has_many :followerships, class_name: 'Followership', foreign_key: 'followed_id'
has_many :followedships, class_name: 'Followership', foreign_key: 'follower_id'
has_many :followers, through: :followerships, source: :follower
has_many :followed, through: :followedships, source: :followed
end
class Followership < ActiveRecord::Base
belongs_to :follower, class_name: "User"
belongs_to :followed, class_name: "User"
validates :follower_id, presence: true
validates :followed_id, presence: true
end
class Tweet < ActiveRecord::Base
belongs_to :user
end
I tried to use the following query:
User.where.not(followers: current_user).order("RANDOM()").limit(3)
But it obviously doesn't work as I get no such column: users.follower_id error.
Is it even possible to do without sql query?
Thank you!
Try this:
already_following = current_user.followed.map(&:id)
#users = User.where.not(id: already_following).order("RANDOM()").limit(3)
Basically what I did, was got the list of users already being followed. Then you check the User table for id's not matching users already being followed.

Rails: has_many through not returning correctly with namespaced models

I have 3 models. Rom::Favorite, Rom::Card, User. I am having an issue creating the User has_many rom_cards through rom_favorites
Here are my relevant parts of my models
Rom::Card
class Rom::Card < ActiveRecord::Base
has_many :rom_favorites, class_name: "Rom::Favorite", foreign_key: "rom_card_id", dependent: :destroy
self.table_name = "rom_cards"
end
User
class User < ActiveRecord::Base
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me, :role
has_many :rom_favorites, class_name: "Rom::Favorite", dependent: :destroy
has_many :rom_cards, class_name: "Rom::Card", through: :rom_favorites, class_name: "Rom::Favorite"
end
Rom::Favorite
class Rom::Favorite < ActiveRecord::Base
attr_accessible :rom_card_id, :user_id
belongs_to :user
belongs_to :rom_card, class_name: "Rom::Card"
validates :user, presence: true
validates :rom_card, presence: true
validates :rom_card_id, :uniqueness => {:scope => :user_id}
self.table_name = "rom_favorites"
end
All of the utility methods that come along with associations work except
a = User.find(1)
a.rom_cards
The call a.rom_cards returns an empty array and it seems to run this SQL query
SELECT "rom_favorites".* FROM "rom_favorites" INNER JOIN "rom_favorites" "rom_favorites_rom_cards_join" ON "rom_favorites"."id" = "rom_favorites_rom_cards_join"."rom_card_id" WHERE "rom_favorites_rom_cards_join"."user_id" = 1
I am not strong in SQL, but I think this seems correct.
I know a.rom_cards should return 2 cards because a.rom_favorites returns 2 favorites, and in those favorites are card_id's that exist.
The call that should allow rom_cards is the following
has_many :rom_cards, class_name: "Rom::Card", through: :rom_favorites, class_name: "Rom::Favorite"
I feel like the issue has something to do with the fact that it is trying to find the Users cards through favorites and it is looking for card_id (because I specified the class Rom::Card) instead of rom_card_id. But I could be wrong, not exactly sure.
You are duplicating the key class_name in the association hash. There is no need to write class_name: "Rom::Favorite" because by using through: :rom_favorites it will use the configuration options of has_many :rom_favorites.
Try with:
has_many :rom_cards, class_name: "Rom::Card", through: :rom_favorites

Resources