If I have a model Department with columns user_id and group_id
When the action tries to save an entry into this model that already exists, i.e. 1 (user_id), 22 (group_id) already exists, at that time I want to raise a violation. What is the way to do this in rails?
Following is the code I am using to save right now:
if #department.save
flash[:notice] = "Successfully created department."
redirect_to #department
else
render :action => 'new'
end
Department model
class Department < ActiveRecord::Base
belongs_to :group
belongs_to :user
end
But I guess what you want is to validate that there's only one department with pair user_id = 1, group_id = 22. This could be achieved with:
validates_uniqueness_of :user_id, :scope => [:group_id]
edit
Now, I might have misunderstood you and maybe all you want is validates_uniqueness_of check. Let me know if I miss something.
Active Records has new_record? method to determine whether object is saved already (whether record in database exists for it).
I copy this demo from rails tutorial:
>> p = Person.new(:name => "John Doe")
=> #<Person id: nil, name: "John Doe", created_at: nil, :updated_at: nil>
>> p.new_record?
=> true
>> p.save
=> true
>> p.new_record?
=> false
You can also use built-in rails validations, something like
class Department < ActiveRecord::Base
validate :my_validation_method, :on => update
def my_validation_method
errors.add_to_base("You can't update existing objects")
end
end
You can find more information on rails validations in the tutorial I linked above.
Related
I read this post on
In Rails, how do I limit which attributes can be updated, without preventing them from being created?
They problem is that the u.save returns true, so it give the impression that all the values got updated. When they did not.
Is there a way to use attr-readonly, but on save return false if an attribute is read only?
class User < ActiveRecord::Base
attr_accessible :name
attr_readonly :name
end
> User.create(name: "lorem")
> u = User.first
=> #<User id: 1, name: "lorem">
> u.name = "ipsum"
=> "ipsum"
> u.save
=> true
> User.first.name
=> "lorem
You can use a validator method to write an error on changes detected to your read only field
Class User
validate :name_not_changed
private
def name_not_changed
return unless name_changed?
errors.add(:name, 'Cannot change name of User')
end
end
Is this really necessary though? If you're making your attribute read-only, why are you still leaving the possibility for it to be changed and additionally need validation errors for that operation? Ideally, you shouldn't permit the name to ever be used when updating models and the story ends there, no validation errors needed.
I have the following mongoid model, with a scoped validation to prevent multiple votes on one bill. Each vote belongs to a user and a group:
class Vote
include Mongoid::Document
field :value, :type => Symbol # can be :aye, :nay, :abstain
field :type, :type => Symbol # TODO can delete?
belongs_to :user
belongs_to :polco_group
embedded_in :bill
validates_uniqueness_of :value, :scope => [:polco_group_id, :user_id, :type]
end
The user has the following method to add a vote to a bill:
def vote_on(bill, value)
if my_groups = self.polco_groups # test to make sure the user is a member of a group
my_groups.each do |g|
# TODO see if already voted
bill.votes.create(:value => value, :user_id => self.id, :polco_group_id => g.id, :type => g.type)
end
else
raise "no polco_groups for this user" # #{self.full_name}"
end
end
and a Bill class which embeds many :votes. This is designed to allow a user to associate their vote with different groups ("Ruby Coders", "Women", etc.) and is working well, except the database currently allows a user to vote multiple times on one bill. How can I get the following to work?
u = User.last
b = Bill.last
u.vote_on(b,:nay)
u.vote_on(b,:nay) -> should return a validation error
Most probably validators on Vote are not getting fired. You can confirm that by adding a validates function and outputting something or raising an exception in it.
class Vote
validate :dummy_validator_to_confirmation
def dummy_validator_to_confirmation
raise "What the hell, it is being called, then why my validations are not working?"
end
end
If after creating above validations User#vote_on doesn't raises exception, it confirms that callbacks are not fired for Vote via vote_on method. You need to change your code to fire callbacks on Vote. Probably changing it to resemble following would help:
def vote_on(bill, value)
if my_groups = self.polco_groups # test to make sure the user is a member of a group
my_groups.each do |g|
# TODO see if already voted
vote = bill.votes.new(:value => value, :user_id => self.id, :polco_group_id => g.id, :type => g.type)
vote.save
end
else
raise "no polco_groups for this user" # #{self.full_name}"
end
end
There is an open issue on mongoid github issue tracker to allow cascade callbacks to embedded documents. Right now callbacks are only fired on document on which persistence actions are taking place on.
As I understand it, the build method can be used to build up a collection of associated records before saving. Then, when calling save, all the child records will be validated and saved, and if there are validation errors the parent record will have an error reflecting this. First question is, is this correct?
But my main question is, assuming the above is valid, is it possible to do the same thing with updates, not creates? In other words, is there a way to update several records in a collection associated with a parent record, then save the parent record and have all the updates take place at once (with an error in the parent if there are validation errors in the children)?
Edit: So to summarize, I'm wondering the right way to handle a case where a parent record and several associated child records need to be updated and saved all at once, with any errors aborting the whole save process.
Firstly +1 to #andrea for Transactions - cool stuff I didn't know
But easiest way here to go is to use accepts_nested_attributes_for method for model.
Lets make an example. We have got two models: Post title:string and Comment body:string post:references
lets look into models:
class Post < ActiveRecord::Base
has_many :comments
validates :title, :presence => true
accepts_nested_attributes_for :comments # this is our hero
end
class Comment < ActiveRecord::Base
belongs_to :post
validates :body, :presence => true
end
You see: we have got some validations here. So let's go to rails console to do some tests:
post = Post.new
post.save
#=> false
post.errors
#=> #<OrderedHash {:title=>["can't be blank"]}>
post.title = "My post title"
# now the interesting: adding association
# one comment is ok and second with __empty__ body
post.comments_attributes = [{:body => "My cooment"}, {:body => nil}]
post.save
#=> false
post.errors
#=> #<OrderedHash {:"comments.body"=>["can't be blank"]}>
# Cool! everything works fine
# let's now cleean our comments and add some new valid
post.comments.destroy_all
post.comments_attributes = [{:body => "first comment"}, {:body => "second comment"}]
post.save
#=> true
Great! All works fine.
Now lets do the same things with update:
post = Post.last
post.comments.count # We have got already two comments with ID:1 and ID:2
#=> 2
# Lets change first comment's body
post.comments_attributes = [{:id => 1, :body => "Changed body"}] # second comment isn't changed
post.save
#=> true
# Now let's check validation
post.comments_attributes => [{:id => 1, :body => nil}]
post.save
#=> false
post.errors
#=> #<OrderedHash {:"comments.body"=>["can't be blank"]}>
This works!
SO how can you use it. In your models the same way, and in views like common forms but with fields_for tag for association. Also you can use very deep nesting for your association with validations nd it will work perfect.
Try using validates_associated :some_child_records in your Patient class.
If you only want this to occur on updates, just use the :on option like validates_associated :some_child_records, :on => :update
More info here:
http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html#method-i-validates_associated
I'm just learning Ruby on Rails (no prior Ruby experience)
I have these models (not showing the migrations here for brevity - they're standard fields like firstname, city etc):
class User < ActiveRecord::Base
has_one :address
end
class Address < ActiveRecord::Base
has_one :user
end
How do I use the Address class to manage the underlying table data? Simply call methods on it? How would I pass params/attribute values to the class in that case? (since Address won't have a controller for it (since it's meant to be used internally)).
How does one go about doing something like this?
u = User.create :first_name => 'foo', :last_name => 'bar' #saves to the database, and returns the object
u.address.create :street => '122 street name' #saves to the database, with the user association set for you
#you can also just new stuff up, and save when you like
u = User.new
u.first_name = 'foo'
u.last_name ='bar'
u.save
#dynamic finders are hela-cool, you can chain stuff together however you like in the method name
u = User.find_by_first_name_and_last_name 'foo', 'bar'
#you also have some enumerable accessors
u = User.all.each {|u| puts u.first_name }
#and update works as you would expect
u = User.first
u.first_name = 'something new'
u.save
#deleting does as well
u = User.first
u.destroy
There is more to it then just this, let me know if you have any questions on stuff I didn't cover
I have a model named Tickets that being saved to the database even when
invalid. This is stopping me from using validations to help prevent
duplicate data being saved to the DB. In script/console
>> Ticket.last.valid?
=> False
>> Ticket.first.valid?
=> False
If I try to see what errors are associated with this invalid object
>> Ticket.last.errors.each{|attr,msg| puts "#{attr} - #{msg}\n" }
=> {}
So does anyone know how it's possible to save an invalid object to the
database, and how can I find what is making the object invalid?
Ticket.rb (model)
class Ticket < ActiveRecord::Base
belongs_to :whymail
belongs_to :forms
attr_accessible :to_email, :to_email, :from_email, :subject, :body
validates_uniqueness_of :to_email, :scope => [:body, :from_email]
validates_presence_of :to_email
validates_presence_of :from_email
validates_presence_of :subject
validates_presence_of :body
def after_create
if self.valid?
whymail = Whymail.find(:first, :include => :user, :conditions => ['(email = ?)', self.to_email.upcase ] )
if !whymail.nil?
self.whymail_id = whymail.id
self.save
MyMailer.deliver_forward(whymail.user.email, self.from_email, self.to_email, self.subject, self.body)
end
end
end
end
One part of this question was answered, second was not. Can anyone see problems with this model that may allow it to save even though it is invalid??
It is possible to skip validations. How are you saving it? Is it part of a nested form?
In any case, you should look at the errors like this:
>>t = Ticket.last
>>t.valid?
>>t.errors.each{|attr,msg| puts "#{attr} - #{msg}\n" }
The way you have it above, you are getting a new object with the second Ticket.last call and validation hasn't been run on that one, so you can't see what the errors are.
Try something like:
t = Ticket.last
t.save
puts t.errors.full_messages.inspect
The errors object won't be populated until you try to save the activerecord object.