ActiveRecord Include, how to use in nested records? - ruby-on-rails

I currently have the following:
#threads = current_user.threads.includes(:user, :thread_members)
I then take threads and do the following:
#threads.each do |thread|
thread_members = thread.thread_members_active(current_user)
#threadList << {
:id => thread.id,
:uuid => thread.uuid,
:user_id => thread.user.id,
:last_activity_at => thread.last_activity_at,
:user_count => thread_members.length,
:user_photos => thread_members.collect { |thread_member|
{
:id => thread_member.user.id,
:photo => thread_member.user.photo(:thumb),
:name => thread_member.user.full_name
}
},
:caption => thread.caption
}
end
The issue here is that every EACH loop, rails is hitting the DB for the same basic records. Rails sees to be caching as I see CACHE in the log but it's mighty messy. Leaves me wishing I could do some type of includes so there wasn't so many db requests.
Any ideas on how this can be optimized? Something around including all the users in one db hit?
Thanks

If you don't want any DB queries in the loop, you have to define everything that's used there in the named associations that are included, so instead of a thread_members_active method you'd define a thread_members_active association which has the same behavior. Note that the association also needs to use includes on user. Can't give you more right now, but maybe that helps a bit.
Edit: Check out the "Eager loading of associations" part of this doc:
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html

Related

Issue including a second and a third level association

This query is not working, pease help. I'm trying to include a second and a third deep-level of association.
Pedido > has_one(products_pedido) > has_one(product_size)
#pedidos = Pedido.includes(:pedidos_payments, :products_pedidos => { :product_size } , :estado, :brand, :customer ).where(:is_quote => false)
Ps: I know products_pedido is mispelled according to ActiveRecord good practices :).
Without a stacktrace here's what I suggest:
Assuming your has_one method name is products_pedidos, your issue looks like a problem with your hash syntax.
Your syntax creates a hash with key products_pedidos that returns a hash without a value. This is probably where the error is occurring.
#pedidos = Pedido.includes(:products_pedidos => { :product_size })
What you likely want is this which returns a hash with key products_pedidos with value product_size
#pedidos = Pedido.includes({products_pedidos: :product_size })
The Entire query might look like:
#pedidos = Pedido.includes(
:pedidos_payments,
{products_pedidos :product_size},
:estado,
:brand,
:customer
).where(is_quote: false)
Here's a great post explaining a bit more about ActiveRecord nested relationship loading: Rails - Nested includes on Active Records?. I'd also suggest fixing the naming on products_pedido to follow good naming practices.

Rails as_json includes with conditions

I have problems to restrict an as_json include by a dynamic attribute:
#pirates_ships = #current_account.pirates.as_json(:include => {:ships => {:only => [:id, :name]}}, :only => [:id, :last_name])
This for sure gives me all pirates with or without their ships.
But I also need to restrict the ships by e.g. ships.ocean_id
I tried resolving it by includes with conditions:
pirates.includes(:ships).where("ships.ocean_id = ?", #ocean.id).as_json(...)
The restriction works, but now all pirates without a ship are lost.
Also no luck with my own JOIN Syntax.
Any ideas?
Ahoy
UPDATE
My solution so far is to manually eager load. This way I can have my dynamic conditions:
#pirates = #current_account.pirates
#ships = #current_account.ships.where({:pirate_id.in => #pirates, :ocean_id => #ocean.id})
render :json => { :pirates => #pirates.as_json(...), :ships => #ships.as_json(...) }
My Ajax callback can now iterate over :pirates and add for each pirate his ships if any.
(I use a JS template engine clientside to generate the view from the JSON response)
Not very elegant, but performance is important in my case.
I'am still open for better ideas.
I tried dynamic has_many :ships, :conditions => ...
but that's a bit fiddly.
I think your best bet would be altering the #pirates_ships hash after generating it from as_json (I tried multiple variations of includes, etc. and couldn't get anything to work).
#pirates_ships = #current_account.pirates.as_json(:include => :ships)
#pirates_ships.each do |pirate|
pirate[:ships].delete_if{ |ship| ship.ocean_id != #ocean.id }
end
# Now, #pirates_ships should contain ALL pirates, as well as ships for #ocean

searching a model in rails for 2 values?

I wrote this retrieval statement to check if an appointment being saved or created dosent conflict with one thats already saved. but its not working, can someone please point me to where I'm going wrong?
#new_appointment = :appointment #which is the params of appointment being sent back from submit.
#appointments = Appointment.all(:conditions => { :date_of_appointment => #new_appointment.date_of_appointment, :trainer_id => #new_appointment.trainer_id}
)
the error is from the :date_of_appointment => #new_appointment.date_of_appointment this will always be false as:
thank you
At face value, there doesn't appear to be anything wrong with your syntax. My guess is that #new_appointment isn't containing the values you're expecting, and thus the database query is returning different values than you expect.
Try dumping out #new_appointment.inspect or check the logfiles to see what SQL the finder is producing, or use
Appointment.send(:construct_finder_sql, :conditions => {
:date_of_appointment => #new_appointment.date_of_appointment,
:trainer_id => #new_appointment.trainer_id
})
to see the SQL that will be generated (construct_finder_sql is a protected ActiveRecord::Base method).
Update based on your edit
#new_appointment = :appointment should be something like #new_appointment = Appointment.new(params[:appointment]). :appointment is just a symbol, it is not automatically related to your params unless you tell it to.

Overriding id on create in ActiveRecord

Is there any way of overriding a model's id value on create? Something like:
Post.create(:id => 10, :title => 'Test')
would be ideal, but obviously won't work.
id is just attr_protected, which is why you can't use mass-assignment to set it. However, when setting it manually, it just works:
o = SomeObject.new
o.id = 8888
o.save!
o.reload.id # => 8888
I'm not sure what the original motivation was, but I do this when converting ActiveHash models to ActiveRecord. ActiveHash allows you to use the same belongs_to semantics in ActiveRecord, but instead of having a migration and creating a table, and incurring the overhead of the database on every call, you just store your data in yml files. The foreign keys in the database reference the in-memory ids in the yml.
ActiveHash is great for picklists and small tables that change infrequently and only change by developers. So when going from ActiveHash to ActiveRecord, it's easiest to just keep all of the foreign key references the same.
You could also use something like this:
Post.create({:id => 10, :title => 'Test'}, :without_protection => true)
Although as stated in the docs, this will bypass mass-assignment security.
Try
a_post = Post.new do |p|
p.id = 10
p.title = 'Test'
p.save
end
that should give you what you're looking for.
For Rails 4:
Post.create(:title => 'Test').update_column(:id, 10)
Other Rails 4 answers did not work for me. Many of them appeared to change when checking using the Rails Console, but when I checked the values in MySQL database, they remained unchanged. Other answers only worked sometimes.
For MySQL at least, assigning an id below the auto increment id number does not work unless you use update_column. For example,
p = Post.create(:title => 'Test')
p.id
=> 20 # 20 was the id the auto increment gave it
p2 = Post.create(:id => 40, :title => 'Test')
p2.id
=> 40 # 40 > the next auto increment id (21) so allow it
p3 = Post.create(:id => 10, :title => 'Test')
p3.id
=> 10 # Go check your database, it may say 41.
# Assigning an id to a number below the next auto generated id will not update the db
If you change create to use new + save you will still have this problem. Manually changing the id like p.id = 10 also produces this problem.
In general, I would use update_column to change the id even though it costs an extra database query because it will work all the time. This is an error that might not show up in your development environment, but can quietly corrupt your production database all the while saying it is working.
we can override attributes_protected_by_default
class Example < ActiveRecord::Base
def self.attributes_protected_by_default
# default is ["id", "type"]
["type"]
end
end
e = Example.new(:id => 10000)
Actually, it turns out that doing the following works:
p = Post.new(:id => 10, :title => 'Test')
p.save(false)
As Jeff points out, id behaves as if is attr_protected. To prevent that, you need to override the list of default protected attributes. Be careful doing this anywhere that attribute information can come from the outside. The id field is default protected for a reason.
class Post < ActiveRecord::Base
private
def attributes_protected_by_default
[]
end
end
(Tested with ActiveRecord 2.3.5)
Post.create!(:title => "Test") { |t| t.id = 10 }
This doesn't strike me as the sort of thing that you would normally want to do, but it works quite well if you need to populate a table with a fixed set of ids (for example when creating defaults using a rake task) and you want to override auto-incrementing (so that each time you run the task the table is populate with the same ids):
post_types.each_with_index do |post_type|
PostType.create!(:name => post_type) { |t| t.id = i + 1 }
end
Put this create_with_id function at the top of your seeds.rb and then use it to do your object creation where explicit ids are desired.
def create_with_id(clazz, params)
obj = clazz.send(:new, params)
obj.id = params[:id]
obj.save!
obj
end
and use it like this
create_with_id( Foo, {id:1,name:"My Foo",prop:"My other property"})
instead of using
Foo.create({id:1,name:"My Foo",prop:"My other property"})
This case is a similar issue that was necessary overwrite the id with a kind of custom date :
# in app/models/calendar_block_group.rb
class CalendarBlockGroup < ActiveRecord::Base
...
before_validation :parse_id
def parse_id
self.id = self.date.strftime('%d%m%Y')
end
...
end
And then :
CalendarBlockGroup.create!(:date => Date.today)
# => #<CalendarBlockGroup id: 27072014, date: "2014-07-27", created_at: "2014-07-27 20:41:49", updated_at: "2014-07-27 20:41:49">
Callbacks works fine.
Good Luck!.
For Rails 3, the simplest way to do this is to use new with the without_protection refinement, and then save:
Post.new({:id => 10, :title => 'Test'}, :without_protection => true).save
For seed data, it may make sense to bypass validation which you can do like this:
Post.new({:id => 10, :title => 'Test'}, :without_protection => true).save(validate: false)
We've actually added a helper method to ActiveRecord::Base that is declared immediately prior to executing seed files:
class ActiveRecord::Base
def self.seed_create(attributes)
new(attributes, without_protection: true).save(validate: false)
end
end
And now:
Post.seed_create(:id => 10, :title => 'Test')
For Rails 4, you should be using StrongParams instead of protected attributes. If this is the case, you'll simply be able to assign and save without passing any flags to new:
Post.new(id: 10, title: 'Test').save # optionally pass `{validate: false}`
In Rails 4.2.1 with Postgresql 9.5.3, Post.create(:id => 10, :title => 'Test') works as long as there isn't a row with id = 10 already.
you can insert id by sql:
arr = record_line.strip.split(",")
sql = "insert into records(id, created_at, updated_at, count, type_id, cycle, date) values(#{arr[0]},#{arr[1]},#{arr[2]},#{arr[3]},#{arr[4]},#{arr[5]},#{arr[6]})"
ActiveRecord::Base.connection.execute sql

Acts-as-readable Rails plugin Issue

I'm using Intridea's Acts as Readable Rails plugin for a messaging system I'm currently building.
I've defined my message class accordingly:
class Post < ActiveRecord::Base
acts-as-readable
end
And everything seems to be working according to plan, but when trying to make the app show unread messages in my message view, I run into problems.
Their example: (I've changed underscores to hyphens due to formatting issues)
bob = User.find_by_name("bob")
bob.readings # => []
Post.find_unread_by(bob) # => [<Post 1>,<Post 2>,<Post 3>...]
Post.find_read_by(bob) # => []
Post.find(1).read_by?(bob) # => false
Post.find(1).read_by!(bob) # => <Reading 1>
Post.find(1).read_by?(bob) # => true
Post.find(1).users_who_read # => [<User bob>]
Post.find_unread_by(bob) # => [<Post 2>,<Post 3>...]
Post.find_read_by(bob) # => [<Post 1>]
bob.readings # => [<Reading 1>]
So it seems that if I wanted to list the number of unread messages sitting in a mailbox (for example Inbox (39) ), I should be able to do something like:
<%= Post.find_unread_by(current-user).count %>
But to no avail. I always seem to get stuck on the simple view issues after everything's set.
Any ideas?
The following will work
<%= Post.find_unread_by(current_user).size %>
or
<%= Post.find_unread_by(current_user).length %>
However if you check your development.log you should see that it gets the unread count by
Retrieving all the posts
Retrieving all the posts read by the user
Removing all of 2. from 1. in ruby
This will be very bad performance wise with lots of posts.
A better way would be to retrieve the posts read by the current user and then use ActiveRecord::Calculations to get a count without retrieving all the posts in the database
Post.count(:conditions => [ "id NOT IN (?)", Post.find_read_by(current_user)])
This should go into your Post model to follow best practices of not having finders in the view or controller
Post.rb
def self.unread_post_count_for_user(user)
count(:conditions => [ "id NOT IN (?)", Post.find_read_by(user)])
end
Then your view will just be
<%= Post.unread_post_count_for_user(current-user) %>

Resources