Rails validation on one to many associations - ruby-on-rails

I am new to Rails. I have 2 models - User and Order. A user can have multiple orders with the same order number but another user cannot have that order number. How can I implement this validation to make sure that order number is unique across users? Thanks in advance.

You can use a base error like this:
(if it is on your order model)
errors.add :base, :number_already_used, if: Order.where(number: self.number).where.not(user_id: user_id).exists?

Related

More efficient, rails way to check for any of three fields being unique?

So, I need check three fields for uniqueness of an object before creating it (from a form), but I will create the object so long as any of the three fields are unique.
My first thought was to just pass the params from the controller to the model, and then run a query to check if a query with those three fields returns > 0 documents. However, I've since learned that this is a dangerous approach, and should not be used.
So I checked the docs, and based off of this snippet
Or even multiple scope parameters. For example, making sure that a teacher can only be on the schedule once per semester for a particular class.
class TeacherSchedule < ActiveRecord::Base
validates_uniqueness_of :teacher_id, scope: [:semester_id, :class_id]
end
I thought I had found my answer, and implemented:
validates_uniqueness_of :link_to_event, :scope => [:name_of_event, :date_of_event]
which works! But, this dataset is going to get very large (not from this form alone, lol), and I'm under the impression that with this implementation, Rails is going to query for all fields with a link_to_event, and then all fields with a name_of_event, and then all fields with a date_of_event. So, my question(s) is:
A) Am I wrong about how rails will implement this? Is it going to be more efficient out of the box?
B) If this will not be efficient for a table with a couple million entries, is there a better (and still railsy) way to do this?
You can define a method that queries the records with all the fields that you want to be unique as a group:
validate :uniqueness_of_teacher_semester_and_class
def uniqueness_of_teacher_semester_and_class
users = self.class.where(teacher_id: teacher_id, semester_id: semester_id, class_id: class_id)
errors.add :base, 'Record not unique.' if users.exists?
end
To answer your questions:
A) Am I wrong about how rails will implement this? Is it going to be more efficient out of the box?
I think Rails will query for a match on all 3 fields, and you should check the Mongo (or Rails) log to see for sure.
B) If this will not be efficient for a table with a couple million entries, is there a better (and still railsy) way to do this?
This is the Rails way. There are 2 things you can do to make it efficient:
You would need indexes on all 3 fields, or a compound index of the 3 fields. The compound index *might* be faster, but you can benchmark to find out.
You can add a new field with the 3 fields concatenated, and an index on it. But this will take up extra space and may not be faster than the compound index.
These days a couple million documents is not that much, but depends on document size and hardware.

How to design database tables for different types of Users?

I'm on a project with Rails, Postgresql and Active Record, where I have Users, that can be either Influencers, or Creators.
The Users have common columns such as email, password, first_name and last_name, but :
influencers will have followers, eg_rate, account columns
creators will have SIRET_number and specialty columns
How can I design my database so Influencers and Creators are kind of "child" of the Users table ? I mean, is it possible to have a db where I can access a User' followers or a User's specialty with one query, or I'll always have to do multiple queries to achieve this ? I've tried to create three tables for each, with a foreign key user_id in Influencers table and Creators table. I also tried to add a user_type column to my User table where I pass the values of either "influencer" or "creator", but I'm kind of lost on how to link every tables...
Thank you everyone.
Your approach is right.
You can create a table users with the common columns and add a foreign key to influencers and creators tables.
Then when you need to retrieve the data, you can use ActiveRecord relations to easily fetch data and use ActiveRecord's includes method for relations.
For example:
class Creator < ActiveRecord::Base
# The relation must be set
has_one :user
end
# To fetch data anywhere else:
Creator.find_by(SIRET_number: 1234).includes(:user)
If you need to retrieve a creator or influencer by an attribute from related users table, you can use joins:
Creator.joins(:users).where(users: {email: "foo#bar.com"})
Make sure you have the relations set in both User and Creator models.
Check out this topic for more info.
By using includes or joins instead of using creator.user you'll avoiding the unnecessary additional query. The downside is the syntax is now longer, you can maybe create your own getters to easily retrieve data instead of writing "includes" everytime.
Also assuming you're not aware of this method, I suggest you to read about the common N+1 problem with Rails & ActiveRecord. The same methods can solve a lot of problems for you.

Include vs Join

I have 3 models
User - has many debits and has many credits
Debit - belongs to User
Credit - belongs to User
Debit and credit are very similar. The fields are basically the same.
I'm trying to run a query on my models to return all fields from debit and credit where user is current_user
User.left_outer_joins(:debits, :credits).where("users.id = ?", #user.id)
As expected returned all fields from User as many times as there were records in credits and debits.
User.includes(:credits, :debits).order(created_at: :asc).where("users.id = ?", #user.id)
It ran 3 queries and I thought it should be done in one.
The second part of this question is. How I could I add the record type into the query?
as in records from credits would have an extra field to show credits and same for debits
I have looked into ActiveRecordUnion gem but I did not see how it would solve the problem here
includes can't magically retrieve everything you want it to in one query; it will run one query per model (typically) that you need to hit. Instead, it eliminates future unnecessary queries. Take the following examples:
Bad
users = User.first(5)
users.each do |user|
p user.debits.first
end
There will be 6 queries in total here, one to User retrieving all the users, then one for each .debits call in the loop.
Good!
users = User.includes(:debits).first(5)
users.each do |user|
p user.debits.first
end
You'll only make two queries here: one for the users and one for their associated debits. This is how includes speeds up your application, by eagerly loading things you know you'll need.
As for your comment, yes it seems to make sense to combine them into one table. Depending on your situation, I'd recommend looking into Single Table Inheritance (STI). If you don't go this route, be careful with adding a column called type, Rails won't like that!
First of all, in the first query, by calling the query on User class you are asking for records of type User and if you do not want user objects you are performing an extra join which could be costly. (COULD BE not will be)
If you want credit and debit records simply call queries on Credit and Debit models. If you load user object somewhere prior to this point, use includes preload eager_load to do load linked credit and debit record all at once.
There is two way of pre-loading records in Rails. In the first, Rails performs single query of each type of record and the second one Rails perform only a one query and load objects of different types using the data returned.
includes is a smart pre-loader that performs either one of the ways depending on which one it thinks would be faster.
If you want to force Rails to use one query no matter what, eager_load is what you are looking for.
Please read all about includes, eager_load and preload in the article here.

Two simple questions about STI implementation

I am using STI for my user models. I have an User class, and the subclasses Seller and Customer. A user cannot be both, so I think STI is alright for this case.
I have two questions:
How do I restrict the creation of User instances, so only sellers or customers can be created? I guess I could validate the presence of Type, but that doesnt feel very right to me.
Can I have extra, different fields for sellers and customers with STI? How?
You can only allow saving of users and customers by doing:
validates_inclusion_of :type, :in => [:customer, :user]
As far as your second question, the answer is: add columns. If you add columns for the customer the user model will ignore them, so no big deal. It depends on your use case though, in some cases it's best to avoid STI.

Best practice for limiting the number of associations within a has_many relationship?

Say that I have two models- Users and Accounts. Each account can have at most n users associated with it, and a user can only be associated with one account.
It would seem natural to say that User
belongs_to :account
and Account
has_many :users
However, I'm not clear on the best practice when it comes to limiting the number of associations through that has_many declaration. I know that there is a :limit argument, but that that only limits the number of associations returned, not the number that are able to exist.
I suspect that the answer is to use something like :before_add. However, that approach seems only to apply to associations created via << . So it would get called when you used
#account.users << someuser
but not if you used
#account.users.create
I had also considered that it might be more practical to implement the limit using before_save within the User model, but it seems like it would be a bit off to implement Account business rules within the User model.
What is the best practice for limiting the number of associations?
Edit: the n users per account would be some business data that is stored within the individual accounts, rather than being a straight up magic number that would be floating around willy nilly in the code.
At first, if your users table has foreign key account_id then you need to use
class User
belongs_to :account
end
In this way you will ensure that User can be associated just to one Account.
If you want to limit that Account can have e.g. at most 3 users then you can define the following validation:
class User
validates_each :account do |user, attr, value|
user.errors.add attr, "too much users for account" if user.account.users.size >= 3
end
end
and as a result you will not be able to create new user for account if account has already 3 users.
I think your rule is subject to a different interpretation. Think of the rule as being "You cannot add a user to an account that already has 3 users." Now that it is a user rule, implementing it on the user object seems perfectly natural and #Raimond's solution will suffice.
You could also think about implementing this as a database constraint, but I probably wouldn't go that way...3 seems to be an arbitrary number that may change later and I, and I suspect you, would prefer that it be captured in the code rather than hidden away in a DB constraint.

Resources