algoliasearch-rails
algoliasearch-rails copied to clipboard
System Stack Error when indexing objects.
I placed a binding before the stack too deep error occurs when indexing an object.
From: /Users/Foo/.rvm/gems/ruby-2.2.3/gems/algoliasearch-rails-1.14.1/lib/algoliasearch-rails.rb @ line 114 AlgoliaSearch::IndexSettings#get_attributes:
109: @additional_attributes.each { |name, value| res[name.to_s] = value.call(object) } if @additional_attributes
110: res
111: else
112: object.class.unscoped do
113: binding.pry
=> 114: res = @attributes.nil? || @attributes.length == 0 ? object.attributes :
115: Hash[@attributes.map { |name, value| [name.to_s, value.call(object) ] }]
116: @additional_attributes.each { |name, value| res[name.to_s] = value.call(object) } if @additional_attributes
117: res
118: end
119: end
We are indexing an Admin object, here is the rails model code for that object:
class Admin < User
def avatar
# avatar method uses `self.class` and we need that to be `User`
# not ideal as it creates an extra query + object... but ignore this
# as the ticket exposes an issue that could affect valid implementations
User.find(id).avatar
end
end
Admin model inherits from the User model. The avatar method instantiates a User object and calls the avatar method on it.
In the binding I run:
[14] pry(<AlgoliaSearch::IndexSettings>)> User == Admin
=> false
[15] pry(<AlgoliaSearch::IndexSettings>)> User.first == Admin.first
Admin Load (50.7ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1
Admin Load (46.7ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1
=> true
The User & Admin model are identified as structures but when I instantiate a User and Admin object they are identified as equals.
The correct behaviour would be the following (output from outside the binding):
2.2.3 :003 > User.first == Admin.first
User Load (52.8ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1
Admin Load (45.4ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1
=> false
Due to this weird behaviour the Admin avatar method is the equivalent of def avatar; Admin.find(id).avatar; end which is obviously an infinite loop.
Hi @v4n ,
It could be a bug related to STI. You are using STI, isn't it ?
I'm trying to understand your models' setup to replicate, could you help me with the following questions?
- Why is the
Admin#avatarmethod needed since this method should be inherited from theUsermodel ? - What happens when you run
Admin.last.avatar, does it lead to a stack too deep error as well ?
I do not understand how the Admin#avatar method is not infinitely recursive since with STI User.find(id) should return an Admin instance (same as self)
Thanks
@AlexanderEkdahl I did mentioned why in the comment above:
# avatar method uses `self.class` and we need that to be `User`
# not ideal as as it creates an extra query + object... but ignore this
# as the ticket exposes an issue that could affect valid implementations
If a run Admin.last.avatar in the binding (algoliasearch-rails.rb @ line 114 AlgoliaSearch::IndexSettings#get_attributes) this will lead to a stack to deep error. If I run the same method call on the Admin object outside the binding, e.g. in a controller of my app, it works perfectly fine.
Let me propose another example:
class User < ActiveRecord::Base
def type
"I am a #{self.class}"
end
end
class Admin < User
def non_admin_friends
User.where(:friend => id)
end
end
# Inside the binding (algoliasearch-rails.rb @ line 114 AlgoliaSearch::IndexSettings#get_attributes)
> non_admin_friends = Admin.non_admin_friends
> non_admin_friends.first.type
=> I am a Admin # this should be I am a User. Works on the rails console.
Hi @v4n
So it seems that this unscoped block messes with the STI internals.
While we are working on finding a more generic fix, can you try to change the Admin#avatar definition to the following (it'll recast the admin object into an instance of User instead of Admin) :
class Admin < User
def avatar
self.becomes(User).avatar
end
end
Thanks @AlexanderEkdahl using becomes solves the issue for us.
@alexandremeunier do you have an idea how to improve that?