Validate many objects at the same time - ruby-on-rails-3

Let's say I have a model called SchoolYear. This model has many periods. When I want create an evaluation structure, it automatically creates three periods with default values.
class SchoolYear < ActiveRecord::Base
has_many :periods, :dependent => :destroy
...
end
Later on, I have a view in which the user can update all of these 3 periods, along with the school year (the form is based on the school year). A period has a status field, which may be active, inactive or finished. When the school year is created, the default value for its period's status is inactive
class Period < ActiveRecord::Base
ACTIVE = 1
INACTIVE = 2
FINISHED = 3
belongs_to :school_year
validates_inclusion_of :status, :in => [ACTIVE, INACTIVE, FINISHED]
end
At least one of the periods for the school year needs to have an active status when updating the school year. How can I validate this? If I try to do this in the SchoolYear model, all its periods will still have the default values. In the Period model, only the current period (self) has its new values. When examining the other periods, they all have the default values. They do get updated, but what I mean is that when I try to validate what I want, they still present their old values because they still haven't been updated in the database, and Rails isn't looking for their values in memory.
For example, if I want to do this validation in the SchoolYear model, I may have something like this:
class SchoolYear < ActiveRecord::Base
validate :at_least_one_active_period, :on => :update
def at_least_one_active_period
self.errors.add(:periods_error, "Period Error") unless self.periods.where(:status => Period::ACTIVE).count == 1
end
end
end
This doesn't work out because, as I said, Rails retrieves these values directly from the database, and doesn't take into account what has been passed by the form. Can you help me? Thanks!

Related

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

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

Rails 3: Chaining `has_many` relations with conditions on the last table, without excessive queries

I am new to both DBs and Rails (using 3.2), and I suspect this is a basic question that I'm just having trouble finding an answer for.
I'm making an app that tracks articles people submit to journals. So, each user has a number of articles. For each article, they can submit it to a number of journals. The submission object itself keeps track not only of the article being submitted and the journal it's submitted to, but also the date it was submitted, the way it was submitted (email, online system, etc.), the date a response was received (if any), the response (if any) (e.g. accepted, resubmit, decline).
I'm trying to allow a user to view all of their outstanding submissions in one place. So, for each user, I'm trying to query the db to get all submissions belonging to articles that belong to that user, but only returning the submissions for which response is NULL (meaning there is no response yet, and thus the submission is still outstanding).
So my models are related like this:
class User < ActiveRecord::Base
has_many :articles
end
class Article < ActiveRecord::Base
belongs_to :user
has_many :submissions
end
class Journal < ActiveRecord::Base
has_many :submissions
end
class Submission < ActiveRecord::Base
belongs_to :article
has_one :journal
end
Now, for a given #user, I think I could do something like
#articles_with_subs = #user.articles.joins(:submissions)
and then
#out_subs = Array.new
#articles_with_subs.each do |article|
outs = article.submissions.where("response NOT NULL")
#out_subs.push outs
end
#out_subs.flatten!
but that seems pretty inefficient. What probably big, obvious thing am I missing?
Thanks very much.
#out_subs = Submission.where("response NOT NULL")
.joins(:article).where(:article => {:user_id => #user.id})

has_many :through -- Adding metadata to the through relationship

I have a need to add metadata about a HABTM relationship. I wanted to use a has_many :through relationship to accomplish this, but it is not necessary. Here is the problem simplified:
class Customer < ActiveRecord::Base
has_many :customer_teddy_bears
has_many :teddy_bears, :through => :customer_teddy_bears
end
class CustomerTeddyBear < ActiveRecrod::Base
belongs_to :customer
belongs_to :teddy_bear
attr_accesible :most_favoritest # just to show it exists, boolean
end
class TeddyBear < ActiveRecord::Base
has_many :cusomter_teddy_bears
end
So what I need to do is start adding teddy bears to my customers, Teddy Bears are a fixed set of data, lets say a fireman_bear, doctor_bear, dominatrix_bear. Any customer can claim to own a kind of teddy bear, but they also specify which is their most favoritest bear. Since I cannot modify the bears model because that is globally shared among all customers I am adding the metadata (among other metadata) to CustomerTeddyBear.
The problem is that the following does not work.
customer = Customer.new # new record, not yet saved, this must be handled.
customer.teddy_bears << fireman_bear
customer.teddy_bears << doctor_bear
# now to set some metadata
favoritest_record = customer.customer_teddy_bears.select{|ctb| ctb.teddy_bear == doctor_bear}.first
favoritest_record.most_favoritest = true
The above code does not work since customer_teddy_bears entries are only populated during save when creating records in the database. Is there another mechanism for doing this?
If there is nothing "automated" built into rails I will just have to manually manage this relationship by including teddy_bears when I select customer_teddy_bears and using techniques like
def teddy_bears
self.customer_teddy_bears.map(&:teddy_bear)
end
along with manually creating the associations, and not using a :through relationship.
please note, all this must happen before the #save is executed on the Customer object, so I need to set all relevant metadata while still in-memory.
Recommendations I got from #RubyOnRails
ctb = customer.customer_teddy_bears.build({:customer => customer, :teddy_bear => fireman_bear})
ctb2 = customer.customer_teddy_bears.build({:customer => customer, :teddy_bear => doctor_bear})
...
ctb.most_favoritest = true
You can simply do this:
customer = Customer.new # new record, not yet saved, this must be handled.
customer.teddy_bears << fireman_bear
customer.teddy_bears << doctor_bear
customer.save
fav = CustomerTeddyBear.where(:customer_id => customer.id, :teddybear_id => doctor_bear.id)
fav.most_favoritest = true
fav.save
The solution I was forced to resort to is manually building the CustomerTeddyBear object and setting both the customer, teddy_bear, and most_favoritest. Basically most of the time, access is by customer.customer_teddy_bears.map(&:teddy_bear) at least in logic where the possibility is that the record is not yet saved, otherwise just short-cut to customer.teddy_bears.

Rails 3. Get most recent update

I am developing an API using Rails 3. A user can have several contact items, like phones, emails, websites and address. Each item got its own model.
I need to do caching in my iPhone app that is using this API therefore I need to get the date when the latest update to any of the items occured and match it against the timestamp of the cache file.
How can I get the most updated items (when comparing all the item tables)?
I am getting the most recent item for each item table like this. Is this really good?
#phones = #user.phones.order("updated_at desc").limit(1)
Thankful for all help!
You can make use of ActiveRecord's touch method. This is especially useful if you have one parent record with many child records. Every time the child records are saved or destroyed the parent record will have it's updated_at field set to the current time.
class User < ApplicationRecord
has_many :phones
has_many :addresses
end
class Phone < ApplicationRecord
belongs_to :user, touch: true
end
class Address < ApplicationRecord
belongs_to :user, touch: true
end
Now any time an address or a phone is updated, the User's updated_at will be set to the current time.
To check when the last updated for the current user took place, over all their tables, you now do:
#last_updated = #user.updated_at
For a small overhead in writes you gain a lot with simpler queries on checking your cache expiry. The documentation for this can be found under belongs_to options.