Only show submissions for the current festival - ruby-on-rails-3

I have models in my application for Festivals and Submissions. Each submission belongs to a single festival. And I have a helper to determine what is the "current_festival" based on dates for each festival.
module ApplicationHelper
def current_festival
#current_festival ||= Festival.current.first
end
end
.current is a scope in the festival model:
scope :current, where("published = true and closing_date >= ?", Time.now.to_date).order('closing_date asc').limit(1)
What I'd like is to restrict the submissions shown in the index view to ones that belong to the current festival only. How would you do that in the controller? Or would it be best to somehow do that in the model, perhaps through a scope?

I suppose you have a relation defined like this:
class Festival
has_many :submissions
end
Then you can do in any place:
Festival.current.submissions

Related

Add Arbitrary Attribute to SQL Query from Joins Record without WHERE clause (Active record)

I'm trying to create an attribute in my select statement that depends on whether or not an association exists. I'm not sure if it's possible with a single query, and the goal is to not have to iterate a list afterward.
Here is the structure.
class Project < ApplicationRecord
has_many :subscriptions
has_many :users, through: :subscriptions
end
class User < ApplicationRecord
has_many :subscriptions
has_many :projects, through: :subscriptions
end
class Subscription < ApplicationRecord
belongs_to :project
belongs_to :user
end
Knowing a project, the goal of the query is to return ALL users and include on them a new attribute call subscribed - denoting whether or not they are subscribed.
non-working code (pseudo code):
project = Project.find_by(name: 'has_subscribers')
query = 'users.*, (subscriptions.project_id = ?) AS subscribed'
users = User.includes(:subscriptions).select(query, project.id)
user.first.subscribed
# => true or false
I'm open to whether or not there is a better way of going about this. However, the information is:
You know the project record.
You query a list of ALL users
Each user record has a subscribed attribute, denoting whether its
subscribed to the given project
Solution:
I was able to figure out a straight forward solution using the bool_or aggregate method. Coalesce ensures that the value returned is false instead of nil, should no subscriptions exists.
query = "users.*, COALESCE(bool_or(subscriptions.project_id = '#{project_id}'::uuid), false) as subscribed"
User.left_outer_joins(:subscriptions)
.select(query)
.group('users.id')
Yep, you can do this:
User.joins(:projects).select(Arel.star, Subscription.arel_table[:project_id])
Which will result in a SQL query like this:
SELECT *, "subscriptions"."project_id" FROM "users" INNER JOIN "subscriptions" ON "subscriptions"."user_ud" = "users"."id";
If you want to specify a specific project (i.e. use an expression), you can do it with Arel like this:
User.joins(:projects).select(Arel.star, Subscription.arel_table[:project_id].eq(42))
Unfortunately, you won't have a column name alias, and you can't call as on an Arel::Nodes::Equality instance. I don't know enough about the internals of Arel to have a way out of that box. But you can do this if you want the composability of Arel (e.g. if this is going to be something that needs to work with multiple models or columns):
User.joins(:projects).select(Arel.star, Subscription.arel_table[:project_id].eq(42).to_sql + " as has_project")
This is a bit clunky, but it works and provides a user.has_project method that returns a boolean. You can pretty it up like so:
class User
scope :with_project_status, lambda do |project_id|
has_project =
Subscription.arel_table[:project_id].
eq(project_id).to_sql + " as has_project"
joins(:projects).select(Arel.star, has_project)
end
end
User.with_project_status(42).where(active: true)

RAILS: How to query if there's already a team formed by an array of users

I'm working on an rails API, more specifically in a create operation.
The workflow that I have is this, I have two rails applications, one is an API and the other is an interface. The API manages the different backend operations in order to handle data, store data, and respond in json format to the interface. The interface serves as the frontend, just making http requests to the API in order to display the information.
In my API I have the 3 model listed below:
class Team < ApplicationRecord
has_many :team_users
has_many :users, through: :team_users
end
class User <ApplicationRecord
has_many :team_users
has_many :teams, through: :team_users
end
class TeamUser < ApplicationRecord
belongs_to :user
belongs_to :team
end
Basically I'm sending an array of user ids from the Interface to the API and I would like to make a query to find out if there's already a team formed by the users (user ids) that I've passed to the API.
I have already tried to do this:
Team.joins(:team_users).where('team_users.user_id' => [3,5])
The problem with this query is that it returns every team that contains the users with id that are equal to 3 or 5. The correct result would be to return a team that has the users 3 and 5 or 5 and 3 as their team members.
Thanks in advance.
Update
The original business rule is this, have an aplication that keeps track of trees, so I have a model named tree and when we create a tree whe must say what team created this tree. So I used a multi select user dropdown field with select2 js library that is how I'm passing the user_ids to the API. So the basic idea is to check is theres already a team composed only by the users passed to the API, if there is already a team I use it's id and say that the tree was registered by that team, if there insn't a team with coposed only buy the users I create a new team and reference it's Id to the tree.
You can approach the problem in different ways. Scrolling over each Team record to check if it contains the associated user_ids is pretty straightforward but inefficient:
team.user_ids.sort == user_ids.sort
But we can make it performant by reversing the process, i.e. iterating over the user_ids to find out corresponding teams, taking their intersection of Team ids and finally checking if any team_id holds those user_ids. This line will return true if there's already a team formed by the users (user ids):
user_ids.map { |id| User.find(id).team_ids }.reduce(:&).present?
You may include it in the User class scope like below:
class User < ApplicationRecord
...
def self.team_exists_for?(user_ids)
# return early
# what should it return when user_ids is empty
# return ??? if user_ids.blank?
# what should it return when any of the id is absent from :users table
# set_of_all_user_ids = Set.new(User.pluck(:id))
# set_of_user_ids = Set.new(user_ids)
# return ??? unless set_of_user_ids.subset? set_of_all_user_ids
# finally
user_ids.map { |id| User.find(id).team_ids }.reduce(:&).present?
end
...
end
Update
So you want to find the team which only has those users provided by user_ids or create a team with them and assign back to the Tree model instance. Combining both approaches described above and defining a scope in the Team model itself seems like a better solution.
class Team < ApplicationRecord
...
def self.find_or_create_for(user_ids)
# find all team_ids which contain user_ids (inclusive)
team_ids = user_ids.map { |id| User.find(id).team_ids }.reduce(:&).flatten
if team_ids.present? # search for the team which ONLY has 'user_ids'
team_id = team_ids.find { |id| Team.find(id).user_ids.sort == user_ids.sort }
end
return Team.find(team_id) if team_id
# or create a team with user_ids and return
team = Team.create! # create a team with required attributes
team.user_ids = user_ids
team
end
...
end
i have implemented this as
add a field key: string on Team and in Team model
class Team < ApplicationRecord
has_many :team_users
has_many :users, through: :team_users
#callback
before_validation :update_key
def update_key
self.key = Team.key_for_users(self.users)
end
def self.key_for_users(users)
users.sort.map(&:id).join("_")
end
end
so basically after this callback whenever you will create a team there will be a key
for example: -
users = [3,5]
then key in Team will be 3_5
or users = [5,3]
then key in Team will be 5_3
From this we can easily get the result what you wanted
example: -
user_ids = [3,5]
[14] pry(main)> user_ids_simple = user_ids.join('_')
=> "3_5"
[15] pry(main)> user_ids_reverse = user_ids.reverse.join('_')
=> "5_3"
and query will be like this: -
Team.where("key IN (?)",[user_ids_simple, user_ids_reverse])
it may be helpful for you. thanks

ActiveRecord: sort of complicated query with nested associations and sorting

Alrigth, so I'm not sure if this isn't too specific... But I really don't have a clue how to construct such a query, neither in AR, nor in SQL. So here's the situation:
I have a User model. User has_many Projects. A Project, in turn, have a following associations:
Project has_one BasicEvent
Project had_many AdditionalEvents
BasicEvent and AdditionalEvent classes are built on inheritance from a AR model class, Event
Now, the goal is this: on my view, I need:
access to all Projects count per User
access to all Events count, where happened_at attribute in nil
have all the Users sorted, by the count of Events, where happened_at attribute in nil
I made a couple of attempts at it so far, but didn't really get too far... I will appreciate any help with this complicated (at least from my perspective) query.
You can start from code like:
# -*- frozen-string-literal: true -*-
class UserStatService
SELECT_CLAUSE = <<~SQL
(SELECT count(1) FROM projects WHERE user_id=users.id) AS projects_count,
(SELECT count(1) FROM events WHERE (happened_at IS NULL)
AND (project_id IN (SELECT id FROM projects WHERE user_id=users.id)
) AS events_count,
users.*
SQL
def call
User
.select( SELECT_CLAUSE )
.order( 'events_count' )
end
end
Each User instance in resultset will have projects_count and events_count attributes.
Also any kind of cache (counter_cache or handmade) is recommended. Because the query is not expected to be fast.
Projects count per User: Project.group(:user_id).count
Events count, where happened_at attribute in nil: Event.where(happened_at: nil).count
Users sorted, by the count of Events, where happened_at attribute in nil: This is the trickiest but it's also not particularly difficult:
User.
select("users.*, (SELECT COUNT(*) FROM events WHERE user_id = users.id AND happened_at IS NULL) AS unhappened_events_count").
order("unhappened_events_count DESC")
This is not a particularly efficient query but it should do its job well for several thousand records (easily) – especially if you set an index on user_id and happened_at in the events table.
Assuming basic_events and additional_events have essentially the same columns, a great solution to this problem would be to change your database/app to use single table inheritance.
Assuming you're using Rails 5.x your models would look like this:
class User < ApplicationRecord
has_many :projects
has_many :basic_events, through: :projects
has_many :additional_events, through: :projects
end
class Project < ApplicationRecord
belongs_to :user
has_one :basic_event
has_many :additional_events
end
class Event < ApplicationRecord
belongs_to :project
end
class BasicEvent < Event
belongs_to :project
end
class AdditionalEvent < Event
belongs_to :project
end
This way, there are only three tables involved users, projects and events. Your events table will have a :type column where you can specify if it's basic or additional.
For your queries:
access to all Projects count per User
User
.select("users.*, count(projects.id) AS projects_count")
.joins(:projects)
.group('users.id')
.order('projects_count DESC')
Using select like this give you access to a :projects_count method on each User object returned in this active record relation, so if you assigned this query to a variable called users_with_projects_count you could do users_with_projects_count.first.projects_count and it would return the number of projects associated with that user.
access to all Events count, where happened_at attribute in nil
User
.select("users.*, count(events.id) AS events_count")
.joins(:events)
.where('events.happened_at IS NULL')
.group('users.id')
You can access :events_count the same way you did :projects_count in the last example.
have all the Users sorted, by the count of Events, where happened_at attribute in nil
User
.select("users.*, count(events.id) AS events_count")
.joins(:events)
.where('events.happened_at IS NULL')
.group('users.id')
.order('events_count DESC')
You use the same query as the last example just add an :order.

Get Users with an attribute for each Group?

My models: User, Group, and Membership. Users can have many groups through memberships, and vice-versa. I want to create a tool for website admins that produces a large table with the following specification:
Every row represents a user,
Every column represents a group,
In each cell of the table there is a boolean indicating whether the user belongs to the group.
What would be the best way to do this? Is it possible to write a single SQL query that achieves it (i.e. User.find_by_sql)? If not, how else?
p.s. I actually need a bit more than this (I need two columns per group, the first one indicating membership, and the second one counting how many meetings the user has attended in that group, but this involves the Meeting model, so I'll leave that for later.
Assuming that you're asking about the backend methodology not the data visualization aspect most of what JuanM. said is correct. One thing I would recommend is avoid writing his 'get_groups' method and just set up a 'has many through' relationship between users in groups. To do so put
class User < ActiveRecord::Base
has_many :memberships
has_many :groups, through: :memberships
end
In your Users model and vice versa in your Groups model (assuming memberships 'belongs_to' both). Then you'll have a '.groups' method on any User instance and a '.users' method on any Group instance
This would be my approach:
Write a function that returns if the user belongs to a group passed by parameter. Get all the groups from a user. In your user.rb model you can add this method get_groups to retrieve all groups from the user and then a method is_in(group). See code below:
class User < ActiveRecord::Base
#validations, some other stuff
has_many :memberships
#this method stores all the group-ids of the user in an array
def get_groups
myGroups = []
memberships.each do |membership|
membership.groups.each do |group|
myGroups << group.id
end
end
return myGroups
end
#this method receive a Group object and return true if the user belongs to that group
def is_in(group)
groups = get_groups
return groups.include?(group.id)
end
Then in your view or controller you can work as follow:
#the columns of the table
groups = Group.all
#iterating all users
User.all.each do |user|
#each user has to see if it belongs to each group in the groups table
groups.each do |group|
#the boolean value you display in a cell
value = user.is_in(group)
end
end

Wishlist relationships in Rails?

Im building an app where Users have some sort of wishlist
a User can have only one wishlist, and can add existing Items to that wishlist
however the Items belong to other Users on the site
I need to be able to access the wishlist items through current_user.wishlist.items (im using Devise so current_user is available)
i though of adding a wishlist_id column to the items table, but that wouldnt work since items can belong to multiple wishlists.
this seems simple but im having a hard time visualizing the relationship or the migration im supposed to generate
class User < ActiveRecord::Base
has_one :wishlist # or belongs_to :wishlist, it depends which you prefer
end
class Wishlist < ActiveRecord::Base
belongs_to :user
has_and_belongs_to_many :items
end
And of course:
./script/rails generate migration create_item_wishlists wishlist_id:integer item_id:integer
to create join table between items and wishlists.
UPDATE: To answer "frank blizzard" question in comment:
Let's say you have the same structure as in my answer (just change Item to Product or other model name), with HABTM relationship you just need to add new "item" to collection of "items", and then save wishlist:
#user.wishlist.items << item
#user.wishlist.save
You can make it method in user:
class User
def add_to_wishlist(item)
wishlist.items << item
end
end
If you want to remove or modify collection of "items", just use any Ruby method from Array and then save wishlist, which will check differences for you and save only changes.