Rails convention of adding a bang to the end of a method makes it throw an exception if it fails. But my experience isn't matching that behavior.
My controller for a many-to-many relationship (an audit trail). The relationship object cannot be created, only updated by posting events to the audit trail object. (Which means you create by updating...)
I have a User object and a Foo object I'll call the relationship Bar.
bar=Bar.where(:user_id=>params[:user_id]).where(:foo_id=>params[:foo_id]).first
if bar
authorize! :update, bar
else
user=User.find(params[:user_id])
authorize! :bar_create, user
foo=Foo.find(params[:foo_id])
bar=Bar.create!(:user_id=>user.id, :foo_id=>foo.id)
end
The create method does not work. I debugged, and bar.save worked fine, but the entire point of create is to avoid having to make that second call to save. I experimented, and discovered that create! works just fine.
Edit:
As I continued on, I discovered that create! did not, in fact, always save. No errors in the underlying object, just mysteriously not saved.
I've had to do a create call followed by a save call, which... honestly, I just don't understand.
Edit: Per request, adding model code -- simplified to the relevant statements by removing unnecessary methods, validation calls, and the like. (While writing this, I noticed that I haven't yet added the has_many :through calls, but... doesn't seem like those should be relevant to the issue at hand.
class User < ActiveRecord::Base
has_secure_password
has_many :progresses
end
class Bar < ActiveRecord::Base
belongs_to :user
belongs_to :foo
has_many :bar_events
validates :user, :presence=>true
validates :foo, :presence=>true
scope :user_id, -> (user_id){ where user_id: user_id}
scope :foo_id, -> (foo_id){ where foo_id: foo_id}
end
class Foo < ActiveRecord::Base
end
There is a validation error or more in one of the associations in Bar foo or user. Try inspecting those objects:
bar=Bar.create!(:user_id=>user.id, :foo_id=>foo.id)
puts bar.errors.inspect, bar.user.errors.inspect, bar.foo.errors.inspect
That will print the errors of all those objects to the terminal running rails server. The only reason create would not save is due to validation errors in itself or nested associations.
Related
We have an accepts_nested_attributes_for with a dependent: destroy that works fine with the _destroy param arg.
We added a validation on the child, and that works as expected.
But when we combine the two the save on the parent throws an unhandled error instead of returning false.
class Foo < ActiveRecord::Base
accepts_nested_attributes_for :bars, allow_destroy: true
...
end
class Bar < ActiveRecord::Base
before_destroy :can_do?
def can_do?
unless yeah_sure
errors.add(:base, I18n.t("the.translation"))
false
end
end
...
end
The bar_spec tests yeah_sure in both cases, with errors being empty or present (and the correct message is in there).
When I stepped through the rails portion there are 3 levels of catch, rollback/cleanup, and release in active_support and transaction.
I also tried to rescue in the controller, both method level and a begin block, and neither of those trapped the error, which is strange.
Any idea why foo.save is throwing an error instead of returning false?
Rails 4.2.10
You can use the inverse_of to run the validation on nested attributes.
Also, you can reject the attributes using reject_if block
please refer the following link.
I'm having troubles autosaving objects which are created this way. Consider having following models:
class SearchTerm < ActiveRecord::Base
has_many :search_term_occurrences, dependent: :destroy
end
class SearchTermOccurrence < ActiveRecord::Base
belongs_to :search_term
validates :search_term, presence: true # Problematic validation
validates_associated :search_term # See Gotchas in answer
end
So when I try to do the following:
term = SearchTerm.new term: 'something'
term.search_term_occurrences << [SearchTermOccurrence.new, SearchTermOccurrence.new]
term.save!
I get the following:
ActiveRecord::RecordInvalid: Validation failed: Search term occurrences is invalid, Search term occurrences is invalid
But when I omit the validation on belongs_to search_term. Everything is saved properly.
My question is: How to save parent object and its association (newly created) whilst having validations on child objects without saving associated objects one by one and then saving parent object within transaction? I want Rails to handle the transactional logic.
So after some playing with Rails console I've found an answer. The problem was the order of saving.
I chose different approach of saving: Instead of going from top to bottom (SearchTerm has_many -> SearchTermOccurrences) I tried to save it from the bottom up (SearchTermOccurrence belongs_to -> SearchTerm). So instead of doing this:
term = SearchTerm.new term: 'something'
term.search_term_occurrences << [SearchTermOccurrence.new, SearchTermOccurrence.new]
term.save!
I did this:
occurrence = SearchTermOccurrence.new
occurence.search_term = SearchTerm.new(term: 'search query')
occurence.save!
This suddenly worked. So I'm guessing that Rails can only validate parent records in belongs_to association while newly created but cannot validate has_many child records.
EDIT 1
Gotchas:
If you are choosing this cascade save approach, make sure you add validates_associated for belongs_to record otherwise you might end up with inconsistent database, since validates :search_term, presence: true can pass even though SearchTerm itself is invalid. I've added validates_associated to SearchTermOccurrence.
you should save the term instance first before adding SearchTermOccurrence objects into it.
term = SearchTerm.new term: 'something'
term.save!
term.search_term_occurrences << [SearchTermOccurrence.new, SearchTermOccurrence.new]
because SearchTermOccurrence have validation of it's parent SearchTerm present true so SearchTerm.new will not create any ID of SearchTerm into database until it is saved.
try above code
I have two models, a Parent and a Child (as outlined below). The child model has a before_save callback to handle some external logic, and if it encounters any errors, the callback invalidates that model being saved.
class Parent < ActiveRecord::Base
has_one :child
accepts_nested_attributes_for :child
validates :child, :presence => true
validates_associated :child
end
class Child < ActiveRecord::Base
belongs_to :parent
before_save :external_logic
validates :parent, :presence => true
def external_logic
begin
# Some logic
rescue
#Invalidate child model
errors.add(:base, "external logic failed")
return false
end
end
end
The problem that I'm running into is that the Child model instance is created as through the nested attributes of the Parent model. When the external logic fails, I want the child model AND the parent model to not be saved, but instead the parent model is being saved on its own. How can I achieve this?
Please note, I am aware of validation callbacks, but they are not suitable in this case. The child model callback has to be a before_save.
EDIT #1
I already know about transactions, and don't consider someone telling me "hey, wrap it around a transaction externally" to be a valid response. This question is explicitly about how to solve this issue through a before_save call.
Why I can't use validations on create - as mentioned in the comments, the external bit of logic needs to be guaranteed to run ONLY before a database save. Validation calls can happen multiple times with or without altering the database record, so that's an inappropriate place to put this logic.
EDIT #2
Ok, apparently having the before_save return false does prevent the parent being saved. I have verified that through the console and actually inspecting the database. However, my rspec tests are telling me otherwise, which is just odd. In particular, this is failing:
describe "parent attributes hash" do
it "creates new record" do
parent = Parent.create(:name => "name", :child_attributes => {:name => "childname"})
customer.persisted?.should be_false
end
end
Could that be an rspec/factory_girl bit of weirdness?
EDIT #3
The test error is because I'm using transactional fixtures in Rspec. That was leading to tests that incorrectly tell me that objects were being persisted in the database when they really weren't.
config.use_transactional_fixtures = true
Okay so your problem is with the ActiveRecord::Callbacks order.
As you can see on the linked page first validation is processed and if validation was successful then before_save callbacks are run. before_save is a place where you can assume every validation passed so you can manipulate a bit data or fill a custom attribute based on other attributes. Things like that.
So what you could do is just say for the Child model:
validate :external_logic and just remove the before_save :external_logic callback.
It's equivalent with what you want to do. When a Parent instance is created it will just error out if the Child object fails to validate, which will happen in your :external_logic validation method. This is a custom validation method technique.
After OP update:
Still you can use :validate method. You can set it to only run on create with:
validate :external_logic, :on => :create.
If you are running into issue that you need this to run on update as well, that is the default behavior. Validations are run on .create and .update only.
OR If you want to stick to before_save:
The whole callback chain is wrapped in a transaction. If any before callback method returns exactly false or raises an exception, the execution chain gets halted and a ROLLBACK is issued; after callbacks can only accomplish that by raising an exception.
I see you did return false so it should work as expected. How do you use Parent.create! method? What are the arguments there?
Make sure you are using it like (supposing .name is an attribute of Parent and Child):
Parent.create!{
:name => 'MyParent'
# other attributes for Parent
:child_attributes => {
:name => 'MyChild'
# other attributes for Child
}
}
This way it both the Parent and Child object will be created in the same transaction, so if your before_save method returns false Parent object will be rolled back.
OR
If you cannot use this format you could just try using pure transactions (doc, example in guides):
Parent.transaction do
p = Parent.create
raise Exception if true # any condition
end
Anything you do inside of this transaction will be rolled back if there is an exception raised inside the block.
Basically: My model requires at least one instance of an associated model be present. Should I use validates_presence_of to assert this validation, or should I write some custom validation code?
Here are the essentials of my model:
class Event < ActiveRecord::Base
has_and_belongs_to_many :channels
validates_presence_of :channels, :message => "can't be empty"
end
(I assume things would be the same if I used has_many in place of has_and_belongs_to_many.)
Instead of the validates_presence_of line I could do this:
def validate
errors.add(:channels, "can't be empty") if channels.size < 1
end
I replaced the latter with the former in the Rails app I'm working on and am wondering if there might be any problems.
So to be more sure, I wrote the following rspec coverage, and both implementations respond the same:
describe Event do
before do
#net = Factory.create(:network)
#net_config = Factory.create(:network_config, :network => #net)
end
it "must have a channel" do
e = Factory.build(:event, :network => #net, :channels => [])
e.should have(1).error_on(:channels)
end
end
That is, if I remove the validation code, the above spec fails; if I put in either version of the validation code, the above spec passes.
So I might assume that my new implementation is ok. But I've read that validates_presence triggers a database load which, in turn, would wipe out any in-memory objects constructed from nested attributes. The proxy_target method, on the other hand, will return the in-memory objects without triggering a load. Some links on proxy_target: http://rubydoc.info/docs/rails/ActiveRecord/Associations/AssociationProxy http://withoutscope.com/2008/8/22/don-t-use-proxy_target-in-ar-association-extensions
In my particular case I'm not using ActiveRecord::Relation, but I wonder if I need to be cautious about this.
I'm working with a nested form that encompasses a total of 7 models, each with different validations. When simply editing the form, the validations run and display fine, and data is saved properly. However, I need to have different validations run depending on who is submitting the form (ie, admins can skip some otherwise required fields).
I thought I could get certain validations to be skipped by using attr_accessible :editing_user in my models, then set this within the controller.
class ModelExample < ActiveRecord::Base
attr_accessible :editing_user
validates_presence_of :email, :unless => "editing_user == 'admin'"
end
class ModelExamplesController < ActionController::Base
def create
#model_example = ModelExample.new(params[:model_example])
#model_example.editing_user = 'admin'
#model_example.save
end
end
I used this basic structure within the nested models, checking to see if I could save properly. This is where the weird behavior starts. For some reason, it looks like ActiveRecord is trying to save nested models multiple times, running validations each time. What makes this odd is I call #model_example.save, which should just return false if it fails. But, the first validation goes through (since editing_user is set), but later validations fail and raise exceptions, so the normal .save methods ends up raising an exception instead of returning.
Does anyone know how to either avoid having ActiveRecord do all extra validations and saves, or how to persist editing_user across those duplicate actions?
ha! just did this yeasterday, well, almost same use case anyway (persist the user). Here is how i solved it (with all credit to my buddy Jason Dew that I copied from):
class User < ActiveRecord::Base
module ClassMethods
attr_accessor :current
end
extend ClassMethods
end
This code block adds a singleton accessor :current to the User class methods, and can be called as User.current. nicer looking that a method called self.currrent
then in the app controller
before_filter :require_user #=> which in this case goes off and sets the current_user var
before_filter {|c| User.current = current_user}
which passes the app controller to the block and sets the User.current var.
Then in any other model
class MyClass < ActiveRecord::Base
def log
"This was done by #{User.current}"
end
end