Nested objects and collection size - ruby-on-rails

I run into a weird issue since I upgraded to Rails 3.2.
My application have some objects managed with nested_forms, but when i try to reach a collection's size after having built a new nested object, the nested object is taken in count.
For ex.:
1.9.3p0 :004 > e = Expense.last
Expense Load (22.6ms) (...)
=> #<Expense id: 1, (...)
1.9.3p0 :005 > e.comments.size
(0.3ms) SELECT COUNT(*) (...)
=> 0
1.9.3p0 :006 > e.comments.build
=> #<Comment id: nil, content: nil, commentable_id: 1, commentable_type: "Expense", created_at: nil, updated_at: nil, creator_id: nil>
1.9.3p0 :007 > e.comments.size
=> 1
In this case, I expect the e.expense.size to return 0 since the Commentobject has not been saved yet.

Building the comment creates ... 1 comment. It doesn't exist in the database (yet) so e.commment.count would return 0, but it exists in the application, so size returns 1. So that sort of makes sense.
But I do see there's a little potentially unexpected result here. If a comment doesn't exist in memory, it looks like Rails checks with the DB (the SELECT COUNT(*)...), whereas if it finds one in memory it does not.
I wonder what would happen if you had several existing comments already, then checked e.comments.size in that case?
Which version of Rails were you coming from that worked differently?

Related

Could not find_by an object from database in Rails job

I have created a Ruby on Rails job (extending from ActiveJob::Base) that parses a CSV file and inserts its rows as records (Students) in a database. That is working as expected.
However, when I try to fetch another object from the DB (the Career for each Student, which is part of each CSV row as a pair (career_code, career_name)), I'm getting this error:
undefined method 'find_by' for Career:Class
(I have also tried using Career.where instead).
I find this quite strange, since I'm already saving my Student, which is also an ActiveRecord::Base child class.
Here's the relevant part of my job:
ActiveRecord::Base.transaction do
student_hash.keys.each do |k|
some_student = student_hash[k]
student = Student.new
student.csv_id = some_student.id
student.first_name = some_student.first_name
student.last_name = some_student.last_name
student.file_number = some_student.file_number
# student.career = Career.where(code: some_student.career_code)
student.career = Career.find_by code: some_student.career_code
puts student.save! # Why this works, and the above line doesn't?
end
end
And also, the desired output, as I can see it in the Rails console:
Loading development environment (Rails 4.2.4.rc1)
2.1.3 :001 > Career.where(code: 11)
Career Load (0.5ms) SELECT "careers".* FROM "careers" WHERE "careers"."code" = ? [["code", 11]]
=> #<ActiveRecord::Relation [#<Career id: 4, name: "Informática", created_at: "2015-09-30 22:05:07", updated_at: "2015-09-30 22:05:07", code: 11>]>
2.1.3 :002 > Career.where code: 11
Career Load (0.2ms) SELECT "careers".* FROM "careers" WHERE "careers"."code" = ? [["code", 11]]
=> #<ActiveRecord::Relation [#<Career id: 4, name: "Informática", created_at: "2015-09-30 22:05:07", updated_at: "2015-09-30 22:05:07", code: 11>]>
2.1.3 :003 > Career.find_by code: 11
Career Load (0.4ms) SELECT "careers".* FROM "careers" WHERE "careers"."code" = ? LIMIT 1 [["code", 11]]
=> #<Career id: 4, name: "Informática", created_at: "2015-09-30 22:05:07", updated_at: "2015-09-30 22:05:07", code: 11>
2.1.3 :004 >
Probably this is a really stupid question, but I'm quite a beginner using Rails. Do I need to import some sort of "Context" (this seems unlikely, since Student seems to be properly resolved)? I'm using Rails 4.2.4.rc1 by the way.
Thanks in advance
Any chance at that point in the code Career isn't an AR, but some other class? That would at least explain why the methods don't exist.

Migrate models between different environments in Rails

I am writing a script that will be in charge of transferring data from production db to a new production db.
There is one Rails app connecting to each database. In the new app we have made some migrations that change the schema (remove columns, etc)
I tried to do this:
rails console old
2.0.0p247 :004 > tag = Tag.last.dup
=> #<Tag id: nil, description: " views", account_id: 46, screenshotBase64: "", user_id: 1, created_at: nil, updated_at: nil>
2.0.0p247 :005 > ActiveRecord::Base.establish_connection(:development)
2.0.0p247 :006 > tag.save
ActiveRecord::StatementInvalid: Mysql2::Error: Unknown column 'screenshotBase64' in 'field list
As we removed the screenshotBase64 in the new db, it does not work.
Is there a way to do that with Rails ? Delete attributes from Rails model before save ?
Is there a better way to transfer all data from a user linked through its associations between two databases (changing ids in the new db) ?
Please have a try with this code
tag_attributes = Tag.last.attributes.dup
%w(id screenshotBase64).map{|method| tag_attributes.delete method}
ActiveRecord::Base.establish_connection(:development)
tag = Tag.create(tag_attributes)

Active Record has_many generates sql with foreign key IS NULL

I don't expect a model with NULL as foreign key to belong to anything!
I have the following rails app, modelling ants and ant hills (inspired by Jozef).
$ rails -v
Rails 3.2.8
$ rails new ant_hill
$ cd ant_hill
Create the ant hill and ant models. An ant can belong to an ant hill and an ant hill can have many ants.
$ rails generate model AntHill name:string
$ rails generate model Ant name:string ant_hill_id:integer
$ vim app/models/ant.rb
$ cat app/models/ant.rb
class Ant < ActiveRecord::Base
belongs_to :ant_hill
end
$ vim app/models/ant_hill.rb
$ cat app/models/ant_hill.rb
class AntHill < ActiveRecord::Base
has_many :ants
end
$ rake db:migrate
== CreateAntHills: migrating =================================================
-- create_table(:ant_hills)
-> 0.0013s
== CreateAntHills: migrated (0.0016s) ========================================
== CreateAnts: migrating =====================================================
-- create_table(:ants)
-> 0.0035s
== CreateAnts: migrated (0.0037s) ============================================
Run the following code in a console.
$ rails c
Loading development environment (Rails 3.2.8)
Create a couple of ants, persisted, that don't belong to any ant hill.
1.9.2-p290 :001 > Ant.create! name: "August"
=> #<Ant id: 1, name: "August", ant_hill_id: nil, created_at: "2012-09-27 12:01:06", updated_at: "2012-09-27 12:01:06">
1.9.2-p290 :002 > Ant.create! name: "Bertil"
=> #<Ant id: 2, name: "Bertil", ant_hill_id: nil, created_at: "2012-09-27 12:01:13", updated_at: "2012-09-27 12:01:13">
Now instantiate an ant hill, but don't save it just yet.
1.9.2-p290 :003 > ant_hill = AntHill.new name: "Storkullen"
=> #<AntHill id: nil, name: "Storkullen", created_at: nil, updated_at: nil>
I expect this ant hill to not have any ants and it doesn't.
1.9.2-p290 :004 > ant_hill.ants
=> []
I still expect the ant hill to not have any ants but now it has two.
1.9.2-p290 :005 > ant_hill.ants.count
(0.1ms) SELECT COUNT(*) FROM "ants" WHERE "ants"."ant_hill_id" IS NULL
=> 2
Same here, it should never generate a query containing "IS NULL" when dealing with foreign keys. I mean "belongs_to NULL" can't belong to anything, right?
1.9.2-p290 :006 > ant_hill.ants.all
Ant Load (0.4ms) SELECT "ants".* FROM "ants" WHERE "ants"."ant_hill_id" IS NULL
=> [#<Ant id: 1, name: "August", ant_hill_id: nil, created_at: "2012-09-27 12:01:06", updated_at: "2012-09-27 12:01:06">, #<Ant id: 2, name: "Bertil", ant_hill_id: nil, created_at: "2012-09-27 12:01:13", updated_at: "2012-09-27 12:01:13">]
After it is persisted it behaves as expected.
1.9.2-p290 :007 > ant_hill.save!
=> true
1.9.2-p290 :008 > ant_hill.ants.count
(0.4ms) SELECT COUNT(*) FROM "ants" WHERE "ants"."ant_hill_id" = 1
=> 0
1.9.2-p290 :009 > ant_hill.ants.all
Ant Load (0.4ms) SELECT "ants".* FROM "ants" WHERE "ants"."ant_hill_id" = 1
=> []
Any insight? Is this the expected behavior?
While it seems counterintuitive, I think this behavior makes sense given your examples. Take ant_hill.ants.count for example. Count is an ActiveRecord query method that hits the database, and you're essentially asking ActiveRecord to give you all the ants that do not belong to an ant hill. Rails is simply letting you do something you should not be able to do, and not complaining about it. Should this be raising an exception instead? Possibly.
If you really want to know how many ants belong to this ant_hill object, you should be using size. It queries the object when not persisted or when the association is already loaded, and queries the database otherwise.
ant_hill.ants.size
One way you can get around this oddity is to make ant_hill_id a required field by validating its presence.
TL;DR Avoid using ActiveRecord query interface if parent object is not persisted to database.

Is there a plugin/gem that lets you inherit errors from a child model in rails?

Let's assume you are using Rails, and have a form with nested models; for example, a form with Project and Tasks for the project.
Then assume that each task must have a name, however a user does not enter a name. When the user submits the form, and you use
project.update_attributes(params[:project])
=> raises error
But (as seen above) this raises an error. Are there any tools out there that will allow the errors from the task to percolate up to the project level, and NOT raise an error? Such a tool would greatly reduce duplication, and it would have to prevent both the project AND OTHER sub-tasks from being saved in the same call to 'update_attributes'.
On a side note, the problem I am trying to solve is the problem of not having to rewrite that percolation code for about 30 models,
Validation errors from associations should be available in the parent's #errors method. Example, if "Foo" has one "Bar":
ruby-1.9.2-p136 :001 > s = Foo.new
=> #<Foo id: nil, created_at: nil, updated_at: nil, enabled: true, alpha: nil>
ruby-1.9.2-p136 :002 > s.build_bar
=> #<Bar id: nil, created_at: nil, updated_at: nil, foo_id: nil, beta: nil>
ruby-1.9.2-p136 :003 > s.save
=> false
ruby-1.9.2-p136 :004 > s.errors.full_messages
=> ["Alpha is invalid", "Bar beta can't be blank"]
ruby-1.9.2-p136 :005 >
Furthermore, I don't think your models should be raising exceptions if there is a validation failure (if this is what you mean by "raise error"). You should just see #update_attributes return false, and then you can fetch the error list

Error with "to_sql" on Rails 3 Beta 4

I'm testing Rails 3 beta 4 on Ruby 1.9.2-head, and when I start a
console and do:
Game.first.to_sql
I get this error:
ArgumentError: wrong number of arguments (0 for 1)
I know it can find the Game record, because when I type:
Game.first
it returns:
=> #<Game id: 1, name: "Galaga", created_at: "2010-06-19 11:02:37",
updated_at: "2010-06-19 11:02:37">
What am I missing? I just want to make the to_sql work in a very simple
case.
.
When you run Game.first you are returning a Game object, not a ActiveRecord::Relation object as you are expecting.
To do what you're trying to do, you'll need to do:
Game.limit(1).to_sql
This lets you run it without to_sql and return the object as you expected it, although it will be in an array, which then you can run .first on it like you wanted anyways.
irb(main):004:0> Game.limit(1).to_sql
=> "SELECT `games`.* FROM `games` LIMIT 1"
irb(main):005:0> Game.limit(1).class
=> ActiveRecord::Relation
irb(main):006:0> Game.limit(1)
=> [#<Game id: 1, name: "Galaga", created_at: "2010-06-19 11:02:37", updated_at: "2010-06-19 11:02:37">]
irb(main):007:0> Game.limit(1).first
=> #<Game id: 1, name: "Galaga", created_at: "2010-06-19 11:02:37", updated_at: "2010-06-19 11:02:37">
When you dig into the source, when you run .first on an ActiveRecord::Relation it runs the following (which is the same as I showed you):
def find_first
if loaded?
#records.first
else
#first ||= limit(1).to_a[0]
end
end

Resources