davidcelis/recommendable — pull request #84 by aaronroyer (2013-12-27 00:51:08+00:00)
Title: Subclassable ratables

Currently, when a ratable has subclasses, the likes applied to the subclasses are essentially abandoned. If the base class is "abstract" then recommendable doesn't seem to work for that ratable. Take this example.

```ruby
class Book < ActiveRecord::Base
end

class FictionBook < Book
end

class NonfictionBook < Book
end

class User < ActiveRecord::Base
recommends :books
end

user1.like(nonfiction_book1)
user1.like(nonfiction_book2)
user1.like(nonfiction_book3)

user2.like(nonfiction_book1)
user2.like(nonfiction_book2)

# We don't get a recommendation
user.recommended_books # => []
```

Recommendations are generated if we only work with Book objects, but not for the subclasses. The reason this happens is because the data for the subclasses are stored in Redis keys namespaced with 'fiction_book' and 'nonfiction_book', even though they're not configured ratable classes. When recommendations are generated, Recommendable iterates over ```Recommendable.config.ratable_classes```, finds Book and looks for data at keys namespaced with 'book'. It doesn't find any of the subclass likes and they are disregarded in calculating recommendations. If only subclasses are used then no recommendations are generated at all.

This PR makes all subclasses of ratables configured with ```recommends``` use keys corresponding to the superclass (or more distant ratable ancestor) so all of the recommendations go in the same "bucket". In the above example, ```user2``` would get ```nonfiction_book3``` as a recommendation. If ```user1``` also liked a fiction book, then ```user2``` would get a recommendation for that as well - Book and all of its subclasses act as the same type as far as Recommendable is concerned.

The subclass objects also start showing up in places like ```Book.top``` where they didn't before.

If we wanted to place FictionBook in its own ratable category then we could just add ```recommends :books, :fiction_books``` and then FictionBooks would be in their own category and Books and NonfictionBooks would still be in the Books category, if that makes sense.

This change only needed a few lines additional code and some tweaks to Redis key mapping. Almost everything in this PR is adding tests to make sure the subclass ratings and recommendations work the same as the normal ones.