Rails 3.2.16 has_many collection build failure - ruby-on-rails

Have a legacy application in Rails 3.2.16 which evaluating for an upgrade and have found an issue when running tests related to an ActiveRecord association between two models. The problem is that the when attempting to construct the associated collection it fails.
I've tested the same scenario is a simple test app using the Ruby on Rails Getting Started Guide which works (details below). The one difference between my test app and the legacy app is that the legacy app models have a number of validations on them.
The models are as follows (extra details removed for ease of reading):
class Schedule < ActiveRecord::Base
has_many :bookings
...
end
class Booking < ActiveRecord::Base
belongs_to :schedule
validate :ensure_sufficient_capacity
...
# validation custom method
def ensure_sufficient_capacity
errors.add(:base, "Not enough capacity") unless capacity_available_for_booking?
end
end
The bookings_controller for the new action has the following sequence:
class BookingController < ApplicationController
def new
#find the named schedule
#schedule = Schedule.find_by_name("myschedule")
#build the booking
#booking = #schedule.bookings.build(:name => "mybooking",...)
#booking.save #fails to save
end
end
Some observations:
The booking model has a validation on it which does a check onto a referenced schedule (the other side of the relation. Can failure on the validation cause a break in the association build ?
Creating a test app as per the Rails Guides Post-Comments getting started example works with a sample of the output below shows that the has_many build operation works.
local> p = Post.new(:title => "foo")
local> #
local> p.save
local> INSERT into "posts" ("title") VALUES ($1) RETURNING "id" [["title", "foo"]]
local> c = p.comments.build(:commenter => "bar")
local> #
local> c.save
local> INSERT INTO "comments" .......

Ok - solved the problem. It seems that it wasn't verifiable based on the code base that I had or the sequence.
The findings were as follows
The test code validated that with a has_many relationship the collections.build for the associated object works and on the save adds in the id for the related object.
Adding validations to the object on the has_many which fail will stop the database transaction on save causing a rollback. This wasn't the problem as the validations were working ok and not returning false
Testing the code on the production server (Heroku) worked ok, but the Git copy I had was wrong and so on checkout of the Heroku code, via heroke git:clone -a appname I setup and tested this against my local database and the whole thing worked.
All in all - it was my fault in not seeing that I had a different version of the codebase. I had assumed that the legacy app provided to me on GitHub was the production version. Unfortunately, as a result of some time based quick fixes, the previous owners had cloned, migrated and deployed to Heroku without an update on Github or connecting the Heroku deployment to Github. This resulted in a disconnect to the source of truth and there was no documentation to point me in the right direction, requiring a lot of reverse engineering.
Anyhow a good reinforcement of the lesson - when it doesn't all add up it's often the simplest of errors causing the problem! Or even it didn't pass the sniff test.
Thanks to all of the suggestion in the comments from Ruby Racer, Pacuna,Fabrizio Bertoglio and I hope that I can reciprocate in the future. I know that I learned some more tricks, especially about checking on the errors.

Related

ActiveRecord::UnknownAttributeError when adding passive column

I'm trying to run this migration, and the goal is to not have any server downtime while adding new columns. Since these columns have no relationships to other tables, exist in the code, or are required, I assumed this would be possible. But, after running the migration I observed UnknownAttributeError's being thrown when instantiating a new instance of the model.
The error:
Controller/existing_table/some_method::ActiveRecord::UnknownAttributeError
/code/app/models/existing_table.rb:40:in `new’
The line from the error:
e = ExistingTable.new(existing_table.attributes.except("id"))
The migration:
class AddTestFieldToExistingTable < ActiveRecord::Migration
def change
add_column :existing_table, :test_field, :integer
end
end
When I go to recreate the issue by opening up a console and executing lines similar as above, I don't get the error. My guess is that there is some attributes cached and we do need to have downtime while running these "passive" migrations.
I wonder if the 'existing_table' is still in memory, doesn't have the 'test_field' attribute, and when creating a new instance of ExistingTable it doesn't know how to assign the field. Since I cannot recreate it without starting a new instance, I would be guessing on an alternative solution of re-writing the ExistingTable constructor.
Is there a way to avoid restarting the rails instance or re-writing this code? Am I on the right track?
I tried to replicate your doubt, and I did not get the error, besides, everything worked as expected.
I am using Rails 7.0.1
First, I started puma:
rails s -b 0.0.0.0
Then create the migration:
rails g migration AddPassiveToGiro passive:boolean
The migration looks like this:
class AddPassiveToGiro < ActiveRecord::Migration[7.0]
def change
add_column :giros, :passive, :boolean
end
end
On rails console I created a new record:
Giro.create giro: 'delete me', passive: true
Then I modify the view where I list those records (using haml)
.list-item-description
= giro.passive.blank? ? 'not passive' : 'is passive'
And everything worked fine, no errors.
Rails load the tables' columns only once when the Rails server is started.
Depending on your setup and how you run your migration at deployment it might be possible that the server or at least one server instance started before the migration was done. Then that server instance will not see the newly added column until the next server restart.
To be safe you need to make sure that the migration ran successfully before you deploy the code changes using that change. Depending on your server setup that might mean that you need two different deployments: One with the migration and then later another with the code changes.

Rails 6 app not validating belongs_to associations

I have a rails engine which encapsulates a piece of my application's funtionality. I have a bunch of models in the engine, which have various belongs_to associations defined. As of rails 5 these associations are supposed to be required by default, unless optional: true is specified in the definition.
I’m still able to create instances of the models without any validation errors. I haven’t specified optional: true on any of the associations, nor is the config optionconfig.active_record.belongs_to_required_by_default set anywhere. Besides, it was removed in rails 6 anyway.
I can't think of any reason the model instances would not fail validation. I would expect any instances of any model with an undefined belongs_to association would be invalid and raise an error. Why would these records pass validation?
I found my problem, thanks to #MatthiasWinkelmann for the tip. It turns out my engine was not calling load_defaults at all. I needed to add the following to spec/dummy/config/application.rb:
module Dummy
class Application < Rails::Application
config.load_defaults Rails::VERSION::STRING.to_f
... etc ....
end
end
here is an article containing more explanation:
An upgraded Rails gem does not upgrade your Rails configuration
I probably would have done better to mention in my question that I'm in the process of upgrading my application from Rails 4.2 to 6.1. The change was introduced in Rails 5.

Rails Test::Unit test of model validation surprisingly slow – I18n?

I have a simple test in my Rails app which is consistently taking much longer than other apparently similar tests.
It takes 1 to 2 seconds of real time to run test_invalid_without_name as below:
VALID_PARAMS = { name: 'Client record' }
def test_invalid_without_name
c = Client.new(VALID_PARAMS)
c.name = nil
refute c.valid?, 'should not be valid without a name'
end
The next test, test_valid_with_all_params takes less than 1/100th of a second:
def test_valid_with_all_params
c = Client.new(VALID_PARAMS)
assert c.valid?, 'should be valid with appropriate parameters'
end
The Client model is totally straightforward at this stage:
class Client < ActiveRecord::Base
belongs_to :entity, polymorphic: true
validates :name, presence: true
end
Can anyone either spot what’s wrong here, or give me an idea where I should look next to try to figure this out?
Update 1
I have used ruby-prof to profile the code, and it seems much of the time is being spent in the Psych gem. I believe this is used in ActiveRecord::..#serialize, which I am using in another model as follows:
class Opportunity < ActiveRecord::Base
belongs_to :client
serialize :details, Hash
end
However, removing the call to serialize here doesn’t make any difference to the Client tests (and I can’t see why it would, the two classes are associated, but no Opportunities are instantiated in the Client test).
Update 2
Turns out Psych is being called because I18n is using it. My assumption at this point is that the failed validation causes I18n to go to the locale files to pull out an error message. I will look into stubbing out I18n for testing...
It turns out a suitable approach to solving this was to use ruby-prof as follows.
Add to Gemfile (in the development group):
gem 'ruby-prof'
Wrap the test code in calls to the profiler:
def test_invalid_without_name
RubyProf.start
cr = Client.new(VALID_PARAMS)
cr.name = nil
refute cr.valid?, 'should not be valid without a name'
result = RubyProf.stop
printer = RubyProf::CallStackPrinter.new(result)
printer.print(File.open(File.join(Rails.root, 'profile_invalid_without_name.html'), 'w'))
end
This will create an interactive HTML document at the root of your Rails app, which shows the breakdown of where the time is spent.
In this case, it was 80%+ in I18n looking for translations. I added the following line to config/environments/test.rb to stub it out in testing:
I18n.backend = I18n::Backend::KeyValue.new({})
I will probably make the following additional improvements in future:
Be more specific with where I stub out I18n, so that full-stack acceptance tests can use the real thing.
Write a more convenient profiling approach. It looks like ruby-prof offers various other mechanisms for activating it, this was just the simplest for initial use.
It's not possible to answer the question without more information.
Does this happen when you run the each of tests individually?
Do you have anything in the setup/teardown or your test_helper.rb?
Does it happen if you pass a hash with name => nil instead of setting it?
Did you redefine the name setter?
Do you have any callbacks on your model?

Test suite problems in rails 3.1rc5

I recently upgraded an app i was working on to rails 3.1rc5..
For the most part it's been great but a few of my tests are having really weird issues..
For example in one of my cucumber specs i create a bunch of fake records using factory girl.. usually this works fine but it seemed that it wasn't creating the records for some reason..
So I commented out all of my factory stuff and replaced it with this:
c = Contact.new(:first_name => "SOMEONE", :last_name => "COOL", :dob => 10.years.ago, :sex => "male")
if c.save
puts "MYCOUNT: #{Contact.count}"
else
puts "EXPLOSIONS!!!"
end
Running this as part of the cucumber suite outputs this:
MYCOUNT: 0
So the contact record is obviously being saved (and passing validations) yet it is still not showing up when i call count??
Why??
I am using:
Rails 3.1rc5
rspec-rails
cucumber-rails
and
factory_girl_rails
I should also probably note i'm indexing my models using sunspot (solr API) https://github.com/outoftime/sunspot
It sounds like you've got a transaction rollback firing within your test:
1) Transaction opened
2) Contact.save succeeds (now have legit Contact instance and db record)
3) Something goes wrong, raises ActiveRecord::Rollback
4) Transactions rolls back, leaving legit Contact instance but no db record, count = 0
I don't know what would cause this related to your Rails upgrade, but perhaps this will help you find what's failing.
EDIT:
If you tail your log/test.log file you should see a Rollback occur if this is the case. You can look at the previous DB activity to get a clue as to what the last successful db operation was before it.
Not an exact science, but may help you decide if this is the case and get a rough idea of where it went sideways.

rake db:migrate running all migrations correctly

I'm fairly new to Ruby on Rails here.
I have 2 migrate files that were provided. The first one, prefixed with 001, creates a table and some columns for that table. The next migrate file, prefixed with 002, inserts rows into the table created in file 001.
Running the migration (rake db:migrate in command line) correctly creates the table but doesn't insert any of the data which is the problem. The code from the insertion looks like this (except with a lot more Student.create statements,
class AddStudentData < ActiveRecord::Migration
def self.up
...
Student.create(:name => "Yhi, Manfredo", :gender => "M")
...
end
def self.down
Student.delete_all
end
end
My understanding is that Student is a model object, so my Student model looks like this,
class Student < ActiveRecord::Base
end
Do I need to explicitly define a create method in Student or is that something that's given? (This project was made using scaffold)
Thanks.
Edit 1: I used Damien's suggestion and called create! instead of create but got the same response. Then what I did to see whether the code was even reaching that far was call this,
Student.create12312313!(:name => "foo", :gender => "M")
which is obviously invalid code and the migrate didn't throw any error.
Edit2: Answer found. The schema_migrations table had its version set to 3, and I only had 3 different migration files so it never ran any of the migration files I had. That's why nothing was ever updating, and the bogus creates I used were never throwing errors. The reason the student data wasn't inserted the first time was because a certain table was already in the database and it caused a conflict the first time I migrated. So what I was really looking for wasn't db:migrate but rather db:reset Several hours well spent.
The create method is inherited from ActiveRecord::Base.
So no, you don't need to define it.
One reason why your datas could not be included would be that you have validations that doesn't pass.
You can easily see the error making your datas not being included by using create! instead of create.
So if the model can't be created, an exception will be thrown and the migrations will fail.
You may want to look at Data Seeding in rails 2.3.4. And is your rails migrations really running 001_create_whatever.rb? or were you just using that as an example? since 2.2.2 (iirc) migrations have been using timestamps such as 10092009....create_whatever.rb
How old is your rails version?
The migrations won't run if their schema number is in the database.
For older versions of rails, there will be a single row with the highest migration performed in it.
For newer versions, every migration gets a unique time-stamp as its version number, and its own row in schema_migrations when it gets added.

Resources