Rails: Validating uniqueness across multiple models - ruby-on-rails

Is there a way to validate the uniqueness of an attribute among columns in two different models. For example:
I have a bike model and a car model. When I create a new bike, I want to validate that the name of the bike is unique in that there is no other bike or car with that name. I don't want to put these into one model because they have vastly different properties. I'm on rails 2.3.8
Thanks.

Rails doesn't validate across models (I don't think, anyways) automatically. You should probably just write your own method to check, a la…
class YourModel < ActiveRecord::Base
validates :uniqueness_of_a_property_across_models
def uniqueness_of_a_property_across_models
// check the other model
end
end

Maybe your Car and Bike Models can have somes common properties like this name, and they can both inherit a common model, and have your uniqueness validation on this model ?

Related

Validate presence of associations in a many-to-many relation

In my Rails app I have a many-to-many relationship between 2 models Teacher and Course through a join table. I'd like to create some sort of validation where a course can't be created without being associated to at least one teacher (it is assumed that all teachers are in the database by the time we are adding a new course). This would be easy to do if this was a one-to-many relationship, but with a many-to-many relationship, we need to save the course before we can associate it with teachers.
My initial plan was to override Rails create method in the Course model to allow passing teacher_ids and validate presence of at least one teacher_id before saving the course, but I'm not sure this is a nice approach.
You should write custom validation, which is quite easy (please adapt to your code):
class Course < ActiveRecord::Base
has_and_belongs_to_many :teachers
validate :has_one_teacher_at_least
def has_one_teacher_at_least
if teachers.empty?
errors.add(:teachers, "need one teacher at least")
end
end
end
That way, you'll only be able to create courses if associated to one teacher like so:
teacher = Teacher.create()
course = Course.new()
course.teachers << teacher
course.save!

Advanced ruby on rails models and validation

In general, I have a website which needs to have complex registration process. And in that registration process I need to include 4 tables from database.
Now... I cannot validate one by one model and to enter 4 of them in database. Is there a way to make common points of all those tables in one model?
Let's say:
User model has columns: username, name, etc.
Package model has: type, account_number
etc
And in registration process I need to include username, name, account_number and to validate them. How to do that?
Without seeing your model structure, this is just speculation, but here goes:
--
Virtual Attributes
In your User model, you can use attr_accessor to create a set of virtual attributes - which basically mean you can create a series of setter / getter methods in your User model.
Although I don't think this will help you directly, it should give you an idea as to how you can create single-model validation:
#app/models/user.rb
Class User < ActiveRecord::Base
attr_accessor :new, :values, :to, :validate
validates, :new, :values, :to, :validate, presence: true
end
This will allow you to create the attributes in the User model - and although they won't be saved, you can then use them to validate against
attr_accessor
To give you a quick overview of this method, you first need to remember that Rails is just a collection of modules and classes.
This means that every model you load is just a class which has been populated with a series of getter / setter methods. These methods are defined by ActiveRecord from your data table columns, and are why you can call #user.attribute
The magic is that if you use attr_accessor, you'll basically create your own attributes in your User model - which won't be saved in the database, but will be treated like the other attributes your objects have, allowing you to validate them
Because your registration process seems to be complex, I would go even futher as virtual attributes and use Form Objects
7 Patterns to Refactor Fat ActiveRecord Models
LA Ruby Conference 2013 Refactoring Fat Models
ActiveModel Form Objects
I understand that you multistep registration. You shouldn't create 4 models only because your view pages needs it. You should:
remove validation from User model and add validation on each form
create 4 different forms (for example extends by ActiveModel or user gem reform)
add validation to each form
after form.valid? save part of user info to #user object
Thats all.

How to extend model class?

I have two types of users (regular user, super user). What is the proper way to extend one base user class with additional tables?
I was thinking something like this but I am not sure am I going to right direction:
class User < ActiveRecord::Base
end
class SuperUser < User
end
class RegularUser < User
end
Is this the proper way to do it in Rails? Thanks :)
It is 100% correct approach, however you need to remember, that all your models will be stored in one table in database. This approach is called STI (Single table inheritance) and requires only one additional field type in you model.
If you want to have different types of users I would go with user roles versus different user tables etc.
A very good gem for that is CanCan and the documentation is excellent:
https://github.com/ryanb/cancan/wiki/Role-Based-Authorization
You will also have nice helpers as .can? or .cannot? and more.
Yes, and you should also use single table inheritance. What this means is you should add a column called 'type' to your user model. Rails recognizes the column 'type' and treats it special. Essentially, all entries in your type model will reference another model. In that model, you can define rules for each type. It would also be a good idea to validate your user model so that only the two types you want can be entered. This should work:
class User < ActiveRecord::Base
validates :type, :inclusion => {:in => ['SuperUser', 'RegularUser']}
end

Has Many Limitation

I would like to set a limit to a has_many association. For example, a car has 4 tires. Therefore the Car table should have 4 foreign keys to records in the Tire table.. (note that in this case, each tire can have different priorities and it is for this reason that I need 4 keys)
Anyway I can specify a the number of tire keys in a car record when declaring an association please?
I don't think has_many association has such an option but you can have a before_create validation in your tires model. Assuming that you create tires independently and not via cars using nested forms, below is an example code for your RAILS_APP/app/models/tire.rb.
Class Tire < ActiveRecord::Base
belongs_to :car
before_create :four_tires_per_car
private
def four_tires_per_car
# can't create more tires if the car in question already has four tires
errors[:base] << "A car can have a maximum of four tires" if car.tires.count == 4
end
end
If you are creating tires via car using nested forms, you can modify this example code accordingly.
No afaik, but you can use the cancan gem to achieve that. For example inside the ability.rb
can :create, Car do |car|
car.tires.count <= 4
end

In Rails, how can I protect an attribute from mass assignment while still allowing the attribute to be set when the object is first created?

I've only been working with Rails for a few months now so I may be missing something obvious, but if an attribute is protected from mass assignment by attr_accessible, or attr_protected, how do you set that attribute when you first create a record? (And do it in a way that is still secure.)
For example:
Say I'm working on an application that keeps track of different procedures and processes that employees in a company have been trained on.
As part of this application, I have three models: User, Course, and TrainingClass. User represents employees, Course represents different things employees can be trained on, and TrainingClass represents a specific class in which employees will be trained on a course.
The TrainingClass has a belongs_to relationship with both the Course model (because a class is basically just a specific instance of a course) and the User model (because the class has a single instructor, who is the user who creates the TrainingClass). Similarly, the Course and User models both have a has_many relationship with the TrainingClass model.
Based on that information, how would I go about creating a new record for TrainingClass?
I could say:
course.training_classes.build(user_id: user.id)
or
user.training_classes.build(course_id: course.id)
But wouldn't that be mass assignment on the user_id or course_id attributes?
What would be the best way to do this while still keeping the application secure?
Read about Mass Assignment Security, specifically roles.
You could have
class TraningClass < ActiveRecord::Base
attr_accessible .....
attr_accessible ....., :user_id, as: :create
end
Where ...... is your list of other attributes you want accessible for mass assignment. Then when you call build, you'll pass that role
course.training_classes.build({user_id: user.id}, as: :create)
Repeat similarly for your other models. The default role will be used (the first one) unless you specify the :create role when calling build, create!, etc...

Resources