I have added a validation like this one to my model:
validates :name, uniqueness: {scope: user_id}
And added an add_index like this on my migration:
add_index(:posts, :name)
But I just read on the rails api page the part about data integrity.
And I was wondering if I will have any integrity errors on my models, so my question is: should I rewrite my indexes as?
add_index(:posts, [:name, :user_id]), unique: true
Thanks all,
The data integrity you're talking about can be enforced at 2 levels as you probably already know: at the application level and at the database level.
At the application level: the validation you added.
At the database level: the index you suggested
You already set up the first one. So, as long as everything goes through your Rails model to be saved in db, you won't have any db integrity issue.
However, if other third-party applications may write to your db, it is not a bad idea to enforce the uniqueness also at the db level.
And even if the first one is sufficient, it is not a bad idea neither to set up the second.
In addition, if you happen to often query the name associated with a user_id, it is actually better to use the add_index(:posts, [:name, :user_id]), making your queries a bit faster.
yes - that would be a good idea. Your model validation implies a composite primary key.
Related
I have an Rails 5 / Mongoid 7 app that parses files and adds the content to the DB. the parsing is taking more and more time after each file is processed and I think this is because I have a validates_uniqueness_of on one of the fields, as the collection grows larger, that validation needs to check a larger collection, makes sense.
So I figured creating an index on that field would help that, but I was wondering if I should still leave the validates_uniqueness_of there anyway or should I remove it?
Can't really seem to find information about that anywhere.
Model:
class SomeModel
include Mongoid::Document
include Mongoid::Timestamps
field :some_field, type: String
index({ some_field: 1 }, { unique: true, name: "some_field_index" })
validates_uniqueness_of :some_field, { case_sensitive: false }
end
Note: I've run rake db:mongoid:create_indexes but I haven't tried a new parse yet, wanted to know how to handle this first.
So I've run several tests and adding the index made a huge difference in processing time, i'll leave my answer here for posterity.
The validates_uniqueness_of can be removed, although now the uniqueness of the field is handled by the index so instead of getting a validation error when trying to save the document, you get an exception thrown, so i had to change how some of the code that was handling the document creation, keep that in mind if you have to deal with a similar situation.
I have a Company model and an Employer model. Employer belongs_to :company and Company has_many :employers. Within my Employer model I have the following validation:
validates :company_id, inclusion: {in: Company.pluck(:id).prepend(nil)}
I'm running into a problem where the above validation fails. Here is an example setup in a controller action that will cause the validation to fail:
company = Company.new(company_params)
# company_params contains nested attributes for employers
company.employers.each do |employer|
employer.password = SecureRandom.hex
end
company.employers.first.role = 'Admin' if client.employers.count == 1
company.save!
admin = company.employers.where(role: 'Admin').order(created_at: :asc).last
admin.update(some_attr: 'some_val')
On the last line in the example code snippet, admin.update will fail because the validation is checking to see if company_id is included in the list, which it is not, since the list was generated before company was saved.
Obviously there are ways around this such as grabbing the value of company.id and then using it to define admin later, but that seems like a roundabout solution. What I'd like to know is if there is a better way to solve this problem.
Update
Apparently the possible workaround I suggested doesn't even work.
new_company = Company.find(company.id)
admin = new_company.employers.where(role: 'Admin').order(created_at: :asc).last
admin.update
# Fails validation as before
I'm not sure I understand your question completely, but there is an issue in this part of the code:
validates :company_id, inclusion: {in: Company.pluck(:id).prepend(nil)}
The validation is configured on the class-level, so it won't work well with updates on that model (won't be re-evaluated on subsequent validations).
The docs state that you can use a block for inclusion in, so you could try to do that as well:
validates :company_id, inclusion: {in: ->() { Company.pluck(:id).prepend(nil) }}
Some people would recommend that you not even do this validation, but instead, have a database constraint on that column.
I believe you are misusing the inclusion validator here. If you want to validate that an associated model exists, instead of its id column having a value, you can do this in two ways. In ActivRecord, you can use a presence validator.
validates :company, presence: true
You should also use a foreign key constraint on the database level. This prevents a model from being saved if there is no corresponding record in the associated table.
add_foreign_key :employers, :companies
If it gets past ActiveRecord, the database will throw an error if there is no company record with the given company_id.
I have to prepare a rather large amount of seed data for a Rails application. Based on my limited experience, I know of two ways to do it but want to know which offers the most flexibility.
One, I could do it with Rails and just prepare the seed data the way it's used in the seeds.rb file
User.create!( name: "John" )
Or, I could create a json document of the data. For example, I know that Mongodb lets you import json documents directly into the database. I'm not sure about other databases...
It occurred to me that a json document might be the most flexible, because I suppose you could also use a regular expression script to turn the json into something like this User.create!( name: "John" )
However, I'm wondering if there's any other issues I should consider...
I'm a StackOverflow newbie, so can't reply to the comments above.
I've been trying to do something similar, the mass assignment protection issue came up for me. An idea that I came across in my research was to use:
without_protection: true
I found this worked using rails 3.2.3 For example:
json = ActiveSupport::JSON.decode(File.read('db/seeds/data.json'))
json.each do |a|
Country.create!(a['data'], without_protection: true)
end
See http://snippets.aktagon.com/snippets/401-How-to-seed-your-database-with-JSON-YAML-data-in-Rails
One issue to consider is that you can't always pass all the parameters through the constructor. Consider this:
class User
include Mongoid::Document
field :name
field :role
validates :name, presence: true
validates :role, presence: true
attr_accessible :name
end
If you have something like User.create({name: 'John', role: 'admin'}), then your seed will fail because role will not be able to be assigned.
How can I skip a specific model validation when importing data?
For example, suppose I have this model:
class Account
validates :street_address, presence: true
end
Normally, I don't want accounts to be saved without addresses, but I'm also going to convert a lot of data from an old system, and many accounts there don't have addresses.
My goal is that I can add the old accounts to the new database, but in the future, when these accounts are edited, a street address will have to be added.
Clarification
As I said, I want to skip a specific validation; others should still run. For example, an account without an account number shouldn't be loaded into the new system at all.
This should work:
class Account
attr_accessor :importing
validates :street_address, presence: true,
unless: Proc.new { |account| account.importing }
end
old_system_accounts.each do |account|
# In the conversion script...
new_account = Account.new
new_account.importing = true # So it knows to ignore that validation
# ... load data from old system
new_account.save!
end
If you're only going to do the conversion one time (i.e, after importing the old data you won't need to do this again), you could just skip validations when you save the imported records instead of modifying your app to support it.
new_account.save validate: false
note that
account.update_attribute(:street_address, new_address)
will skip validations as well. #update_attributes (notice the 's') run validations, where update_attribute (singular) does not.
Hi I wan't to validate the unique combination of 3 columns in my table.
Let's say I have a table called cars with the values :brand, :model_name and :fuel_type.
What I then want is to validate if a record is unique based on the combination of those 3. An example:
brand model_name fuel_type
Audi A4 Gas
Audi A4 Diesel
Audi A6 Gas
Should all be valid. But another record with 'Audi, A6, Gas' should NOT be valid.
I know of this validation, but I doubt that it actually does what I want.
validates_uniqueness_of :brand, :scope => {:model_name, :fuel_type}
There is a syntax error in your code snippet. The correct validation is :
validates_uniqueness_of :car_model_name, :scope => [:brand_id, :fuel_type_id]
or even shorter in ruby 1.9.x:
validates_uniqueness_of :car_model_name, scope: [:brand_id, :fuel_type_id]
with rails 4 you can use:
validates :car_model_name, uniqueness: { scope: [:brand_id, :fuel_type_id] }
with rails 5 you can use
validates_uniqueness_of :car_model_name, scope: %i[brand_id fuel_type_id]
Depends on your needs you could also to add a constraint (as a part of table creation migration or as a separate one) instead of model validation:
add_index :the_table_name, [:brand, :model_name, :fuel_type], :unique => true
Adding the unique constraint on the database level makes sense, in case multiple database connections are performing write operations at the same time.
To Rails 4 the correct code with new hash pattern
validates :column_name, uniqueness: {scope: [:brand_id, :fuel_type_id]}
I would make it this way:
validates_uniqueness_of :model_name, :scope => {:brand_id, :fuel_type_id}
because it makes more sense for me:
there should not be duplicated "model names" for combination of "brand" and "fuel type", vs
there should not be duplicated "brands" for combination of "model name" and "fuel type"
but it's subjective opinion.
Of course if brand and fuel_type are relationships to other models (if not, then just drop "_id" part). With uniqueness validation you can't check non-db columns, so you have to validate foreign keys in model.
You need to define which attribute is validated - you don't validate all at once, if you want, you need to create separate validation for every attribute, so when user make mistake and tries to create duplicated record, then you show him errors in form near invalid field.
Using this validation method in conjunction with ActiveRecord::Validations#save does not guarantee the absence of duplicate record insertions, because uniqueness checks on the application level are inherently prone to race conditions.
This could even happen if you use transactions with the 'serializable' isolation level. The best way to work around this problem is to add a unique index to the database table using ActiveRecord::ConnectionAdapters::SchemaStatements#add_index. In the rare case that a race condition occurs, the database will guarantee the field's uniqueness.
Piecing together the other answers and trying it myself, this is the syntax you're looking for:
validates :brand, uniqueness: { scope: [:model_name, :fuel_type] }
I'm not sure why the other answers are adding _id to the fields in the scope. That would only be needed if these fields are representing other models, but I didn't see an indication of that in the question. Additionally, these fields can be in any order. This will accomplish the same thing, only the error will be on the :model_name attribute instead of :brand:
validates :model_name, uniqueness: { scope: [:fuel_type, :brand] }