Rails model validation - validates_inclusion_of - ruby-on-rails

I have a string column in a table that can have a range of predefined values. It can also include a nil value. For ex: Dog, Cat, Bird, nil.
I want to write a validates_inclusion_of that checks to make sure all the values being entered fall within that predefined range. If, for ex, "Nasal Spray" is entered, it will throw an error.
What's the best way to do so?

Use the following validation within your model class:
validates_inclusion_of :animal, :in => %w(Dog Cat Bird), :allow_blank => true
—where :animal is the name of the column you want to validate.

Related

Rails: Possible to send a disabled option as a valid params value?

I have a User, and a Country models, and in User model, it has has_many :countries relationship. The country has 3 attributes -> :id, :name, :is_main
Let's say the countries table is currently populated as follow:
id name is_main
----------------------------------
1 USA true
2 Germany nil
3 France nil
4 England nil
Let's say a user is created that has countries USA, and Germany. So in this case the user.countries.pluck(:id) would return [1,2].
What I would like to achieve is that when editing the user's countries, a dropdown will appear and I am only allowed to add (or remove) countries where 'is_main' attribute is nil. In other words, in the dropdown, the USA country should either be disabled or be hidden completely to be selected. At the same time, the USA should remain in the user.countries after form submission plus any new countries that have been added from update action.
In short:
Original => user.countries.pluck(:id) => [1,2]
In edit form, if I add France to the user, the end result should be user.countries.pluck(:id) => [1,2,3]
I have tried the following:
f.collection_select(:countries, Country.where(is_main: nil), :id, :name, {}, {:multiple => true})
In doing this, the dropdown will display all the countries for me to add except for USA, which is what I desired. But the problem is when I click submit, the params[:country_ids] will be [2, 3]. As a result, after update action, the user.countries.pluck(:id) would become [2,3] instead of desired [1,2,3], effectively removing USA's id.
Is there a way to work around this? I have tried also adding :disabled option in collection_select that disables USA option, but the params[:country_ids] would still be [nil, 2, 3]. Appreciate if anyone could advise me on this.
I've come across this issue on my application. In rails 4+, set include_hidden attribute to false in multi-select selet_tags. Here's how i did it.
= f.input :countries, as: :select, collection: Country.where(is_main: nil).map{|country| [country.id, country.name]}, input_html: { multiple: true, data: { placeholder: "Countries"} }, include_hidden: false
Hope this helps you solve your issue
Actually using your example for multiple selection, your params after submit should have params[:country_ids] = [2,3]. If you are not giving the option to select USA, then it just won't be included.
You can disable options doing this:
// Your select id should be 'user_country_ids', if not change it
// This assumes that your first item will always be USA
$('#user_country_ids option:eq(0)').prop('disabled', true);
Anyway, this JS can be bypassed, so you will need to handle this on your backend too adding a validation for this in your User model:
class User
has_many :countries
validate :main_countries_always_associateds
private
def main_countries_always_associateds
errors.add(:countries, "must include main ones") if Country.where(is_main: true).any?{ |c| !self.countries.include? c }
end
end
EDIT:
If you want to always have main countries inside each user countries, then you can use before_validation callback to override this selection. I recommend to also include the disabled options on the multiple select, so the user is aware of this, the validation may become not necessary.
class User
has_many :countries
before_validation :associate_main_countries
# You should use something like this if you want your dropdown to always show marked main countries
def self.new_custom_user
User.new(countries: Country.where(is_main: true))
end
private
def associate_main_countries
Country.where(is_main: true).each do |c|
self.countries << c if !self.countries.include?(c)
end
end
end

Rails currency validation

How to write validation in rails so as to allow the below possible values for price
100 or $100 or 100.00 or $100.00
I have declared price field as Float in my model
My current code looks like :
validates :price,numericality: true
which is not allowing values like 100 to get saved.
You can add the format option to your validates method.
validates :price, numericality: true,
:format => { :with => /^\d{1,6}(\.\d{0,2})?$/
This will allow values of up to $999999.99 with an optional decimal place (if the decimal is present, no more than 2 digits must follow.)
But like others already mentioned - it's not the best option to save these values into the database.
I can recommend you the RubyMoney gem for working with currencies.

What do conditions target in uniqueness validators?

I have the following validator in my model:
validates_uniqueness_of :item_id, conditions: -> { where.not(status: "published") }
What does the condition target here? Does it prevent the validator itself to look on the table row if there is status: "published" or is it an extension for the uniqueness validator to exclude the rows with status: "published" in the uniqueness (yes, there is a difference)?
Is there also a difference between the above validator and the following, assuming that status_published? is a method checking for the status to be "published" or not?
validates_uniqueness_of :item_id, :unless => lambda { status_published? }
And finally, if there is no difference, how can I accomplish the second case, where uniqueness validator will check if the value is unique only in the rows which are true for the condition?
According to the documentation, conditions limit the constraint to the set of records that match them, so the validation doesn't run at all if those said conditions aren't matched.
On a side-note, if you're doing this in Rails 4, you may want to look at the new validates syntax:
validates :item_id, uniqueness: true, unless: :status_published?

grouped_collection_select returns an error

I have a "grouped_collection_select" for a "branch" field, in combination with a "sector" field. Sector has_many branches and branch belongs_to sector:
<%= f.grouped_collection_select(:branch, current_user.company.sectors.order(:sector), :branches, :sector, :id, :branch, include_blank: true) %>
This works, but the ":branches" shows all branches and should only show the branches of the current_user.company, just like the sectord. But when I change ":branches" into "current_user.company.branches.order(:branch)", I get an error:
(eval):1: syntax error, unexpected $end group.#<ActiveRecord
What am I doing wrong here? Thanks!
For clarity (and semantics) set that collection in your controller to use in your view:
#sectors = current_user.company.sectors.order(:sector)
...or if you want to limit the branches that are showing for each sector by user, you'd probably want to build the collection based on the user's branches. One example:
#sectors = current_user.company.branches.collect{|b| b.sector}.uniq
Then assuming you want to set the branch_id AND a sector has a name attribute AND the branch has a name attribute, this should work:
<%= f.grouped_collection_select(:branch_id, #sectors, :branches, :name, :id, :name, :include_blank=>true)
#:branch_id is the attribute you will be setting on the form_builder's object.
##sectors is the collection of objects for the groups.
#:branches is the method that will be called on each sector object.
#:name is the method that will be called on each sector object to label the group.
#:id is the method that will be called on each branch to set the option's value.
#:name is the method that will be called on each branch to set the option's label.

mongoid batch update

I'd like to update a massive set of document on an hourly basis.
Here's the
fairly simple Model:
class Article
include Mongoid::Document
field :article_nr, :type => Integer
field :vendor_nr, :type => Integer
field :description, :type => String
field :ean
field :stock
field :ordered
field :eta
so every hour i get a fresh stock list, where :stock,:ordered and :eta "might" have changed
and i need to update them all.
Edit:
the stocklist contains just
:article_nr, :stock, :ordered, :eta
wich i parse to a hash
In SQL i would have taken the route to foreign keying the article_nr to a "stock" table, dropping the whole stock table, and running a "collection.insert" or something alike
But that approach seems not to work with mongoid.
Any hints? i can't get my head around collection.update
and changing the foreign key on belongs_to and has_one seems not to work
(tried it, but then Article.first.stock was nil)
But there has to be a faster way than iterating over the stocklist array of hashes and doing
something like
Article.where( :article_nr => stocklist['article_nr']).update( stock: stocklist['stock'], eta: stocklist['eta'],orderd: stocklist['ordered'])
UPDATING
You can atomically update multiple documents in the database via a criteria using Criteria#update_all. This will perform an atomic $set on all the attributes passed to the method.
# Update all people with last name Oldman with new first name.
Person.where(last_name: "Oldman").update_all(
first_name: "Pappa Gary"
)
Now I can understood a bit more. You can try to do something like that, assuming that your article nr is uniq.
class Article
include Mongoid::Document
field :article_nr
field :name
key :article_nr
has_many :stocks
end
class Stock
include Mongoid::Document
field :article_id
field :eta
field :ordered
belongs_to :article
end
Then you when you create stock:
Stock.create(:article_id => "123", :eta => "200")
Then it will automaticly get assign to article with article_nr => "123"
So you can always call last stock.
my_article.stocks.last
If you want to more precise you add field :article_nr in Stock, and then :after_save make new_stock.article_id = new_stock.article_nr
This way you don't have to do any updates, just create new stocks and they always will be put to correct Article on insert and you be able to get latest one.
If you can extract just the stock information into a separate collection (perhaps with a has_one relationship in your Article), then you can use mongoimport with the --upsertFields option, using article_nr as your upsertField. See http://www.mongodb.org/display/DOCS/Import+Export+Tools.

Resources