Where do I put the database constraints in rails? - ruby-on-rails

Simple problem. I'm learning RoR. I swear that I searched this theme here and in google.
I need a lot of tables in my app.
I'm reading about the benefits of database constraints. I'm using validations inside every model, example:
class Example < ActiveRecord::Base
belongs_to :other
has_one :another...
attr_accessible :username, :email, :password
validates :username, e:mail, :password, presence: true
validades .....
end
I would like to know about database constraints, how can i get the same validation inside the database? Should i put this constraints (like :null => false) inside the schema.rb file?

Yes, absolutely put that in your migration:
:null => false
To require a non-empty field. Although an empty string can still be supplied and it passes the non-NULL test. You can cover this by adding a length validation:
validates_length_of :username, :minimum => 1, :maximum => 255

Related

Model Post with different types in Rails

I have a question on how to build the models in Rails. Let's for example take the social network tumblr. You can post there in different ways. Links, just text, photos, conversations. Now I would like to do something similar, but I am kind of stock with the modeling of my app. Let's say I want to have a Post model. So my users can post either just a text or one with an image. So i guess I need an Image model too. Let's say I want that my users can post an event but as a form of a post. So i guess I need a Event model. And so on, maybe in future I will have another idea for another type of post (so it should be always releated to the post). How would you build the models and the relations so it is easily expandable? I am thinking of something like Enums from Java.
I will use one model Post for all post's types(image,event...)
For example posts table:
create_table :posts do |t|
t.belongs_to :author
t.column :post_type, :integer, default: 0
t.column :title, :string
t.column :body, :text
t.column :image_url, :string
t.column :star_at, :datetime
t.column :end_at, :datetime
t.timestamps
end
Model will have specific validation for each post type:
class Post < ActiveRecord::Base
enum post_type: [ :event, :image, :message]
validates :image_url, presence: true, if: ->{ image? }
validates :start_at, :end_at, presence: true, if: ->{ event? }
validates :title, :body, presence: true, if: ->{ message? }
end
Reasons to choose this way:
Easy fetch all data from database. No unions, no multiple selects.
Easy manage caching, owner's rules, ...
Use model's concerns for every post type to have a clean model.

Mongoid create new Users

I'm trying to write an example app using Ruby on Rails and the Mongoid Mapper.
For some kind of Testing I want to write 1000 Testusers into MongoDB...
With the code bolow Mongoid is not able to write unique uid's. In my ruby console i got the right number for the counter but not for the uid.
Does anybody know what I forgot?
class User
include Mongoid::Document
include Mongoid::Timestamps
def self.create_users
(1..1000).each do |f|
user = User.new(uid: f.to_s, first_name: "first_name", last_name: "last_name", e_mail: "e_mail")
user.save!
puts f
puts user.uid
end
end
field :uid, :type => String
field :first_name, :type => String
field :last_name, :type => String
field :e_mail, :type => String
field :messages, :type => String
attr_accessible :first_name, :last_name, :e_mail
validates_presence_of :uid, :first_name, :last_name, :e_mail
validates_uniqueness_of :uid
has_many :messages
end
You don't have to provide the field uid in your models. MongoId add the id field for you and manages the value during the create operation.
Simply remove field :uid, :type => String from model
If you want to use your own ids you can change the name of the uid field to _id and it should work just fine. However, the default generated mongo _id will make it easier to scale and using it removes one of the more difficult aspects of sharding if you ever need that feature.
If you want to use the ones that are generated by default, they are included automatically unless overridden explicitly (behavior which you have seen) so just remove your custom field and you should be all set.
You can read more about ObjectIds here.

ruby on rails specifying uniqueness in db across multiple columns

I have a model as follows:
class EntityTag < ActiveRecord::Base
attr_protected :user_id, :post_id, :entity_id
belongs_to :user
belongs_to :post
belongs_to :entity
validates :user_id, :presence => true
validates :entity_id, :presence => true
validates :post_id, :presence => true
end
I want to guard against multiple rows which have the same combination of user_id, entity_id, and post_id (e.g. a unique ID for a row is all three of those values).
What's the easiest way I can communicate that to ActiveRecord?
As #dhruvg mentioned:
validates_uniqueness_of :user_id, :scope => [:entity_id, :post_id]
Do note that uniqueness validation on model level does NOT guarantee uniqueness in the DB. To have that, you should put a unique index on your table.
Add the following to your migrations.
add_index :entity_tags, [:user_id, :post_id, :entity_id], :unique => true
I would check for this in the create action of your controller.
EntityTag.where(["user_id = ? and entity_id = ? and post_id = ?",
params[:user_id], params[:entity_id], params[:post_id]]).all
will return an Array of any existing record that have those same values. If Array.count == 0, then you can continue to save the newly created object as normal. Otherwise you can either return the existing record or throw an error; it's up to you.

problem with passing booleans to update_attributes

I've got the following Model:
class GuestCatering < ActiveRecord::Base
# Validation
validates :name, :presence => true
validates :order_number, :presence => true
validates :orderable, :presence => true
end
But when I'll try to update an existing GuestCatering with the following code:
guest_catering.update_attributes(:orderable => false)
The guest catering variable is a valid GuestCatering object.
The guest_catering object has errors after the update, like that:
<{[:orderable, ["can't be blank"]]=>nil}>
But when i pass a orderable => true, everything is fine and no errors.
What's wrong here, why can't i set orderable to false?
Your model is actually behaving exactly as you told it to, through your use of validates :orderable, :presence => true
There's little point validating the presence of a boolean flag - it's going to be true, nil or false - and in Ruby world, nil and false have the same semantic value when it comes to boolean logic.
Internally, validates :presence relies on the value of the attribute being checked to return false when blank? is called. And, in Rails (with ActiveSupport), false.blank? evaluates as true - which means that your field is failing the validation.
Simply remove that validation and everything will work as expected.
Like Dan Cheail already said in his answer, a nil and false boolean is semantically the same thing.
But, if you really need to validate it (not allowing nil), you can always do :
validates_inclusion_of :orderable, :in => [true, false]
Instead of validates :presence => :true, you should write your migrations with the default value like this:
t.boolean :orderable, :default => 0
I assume your default value should be false. If true, use 1 as default. Then it will set the default value in database. So, you can omit the validation check.
The reason you cannot use validates :presence is answered by #dan. Presence means not blank and Rails use .blank? function for this and false.blank? is true

How do you validate uniqueness of a pair of ids in Ruby on Rails?

Suppose the following DB migration in Ruby:
create_table :question_votes do |t|
t.integer :user_id
t.integer :question_id
t.integer :vote
t.timestamps
end
Suppose further that I wish the rows in the DB contain unique (user_id, question_id) pairs. What is the right dust to put in the model to accomplish that?
validates_uniqueness_of :user_id, :question_id seems to simply make rows unique by user id, and unique by question id, instead of unique by the pair.
validates_uniqueness_of :user_id, :scope => [:question_id]
if you needed to include another column (or more), you can add that to the scope as well. Example:
validates_uniqueness_of :user_id, :scope => [:question_id, :some_third_column]
If using mysql, you can do it in the database using a unique index. It's something like:
add_index :question_votes, [:question_id, :user_id], :unique => true
This is going to raise an exception when you try to save a doubled-up combination of question_id/user_id, so you'll have to experiment and figure out which exception to catch and handle.
The best way is to use both, since rails isn't 100% reliable when uniqueness validation come thru.
You can use:
validates :user_id, uniqueness: { scope: :question_id }
and to be 100% on the safe side, add this validation on your db (MySQL ex)
add_index :question_votes, [:user_id, :question_id], unique: true
and then you can handle in your controller using:
rescue ActiveRecord::RecordNotUnique
So now you are 100% secure that you won't have a duplicated value :)
From RailsGuides. validates works too:
class QuestionVote < ActiveRecord::Base
validates :user_id, :uniqueness => { :scope => :question_id }
end
Except for writing your own validate method, the best you could do with validates_uniqueness_of is this:
validates_uniqueness_of :user_id, :scope => "question_id"
This will check that the user_id is unique within all rows with the same question_id as the record you are attempting to insert.
But that's not what you want.
I believe you're looking for the combination of :user_id and :question_id to be unique across the database.
In that case you need to do two things:
Write your own validate method.
Create a constraint in the database
because there's still a chance that
your app will process two records at
the same time.
When you are creating a new record, that doesn't work because the id of your parent model doesn't exist still at moment of validations.
This should to work for you.
class B < ActiveRecord::Base
has_many :ab
has_many :a, :through => :ab
end
class AB < ActiveRecord::Base
belongs_to :b
belongs_to :a
end
class A < ActiveRecord::Base
has_many :ab
has_many :b, :through => :ab
after_validation :validate_uniqueness_b
private
def validate_uniqueness_b
b_ids = ab.map(&:b_id)
unless b_ids.uniq.length.eql? b_ids.length
errors.add(:db, message: "no repeat b's")
end
end
end
In the above code I get all b_id of collection of parameters, then compare if the length between the unique values and obtained b_id are equals.
If are equals means that there are not repeat b_id.
Note: don't forget to add unique in your database's columns.

Resources