Batman.js: Related model undefined for polymorphic association not found - polymorphic-associations

I'm running into an issue trying to implement polymorphic associations on a Batman model. I'm getting this error in the console:
Related model undefined for polymorphic association not found.
I'm having a hard time tracking down where I am going wrong. Where should I look to find the missing piece?
My models look something like this:
class Admin.Product.PopularCollectables extends Batman.Model
#belongsTo 'collectable', polymorphic: true
class Admin.Item extends Batman.Model
#hasOne 'popular_collectable', as: 'collectable'

When Batman loads a related model in an association, it checks a namespace. By default, the namespace is Batman.currentApp (which is your app after you call MyApp.run()), but you can also pass a namespace when you declare the association:
class Admin.Item extends Batman.Model
#hasOne 'popular_collectable', as: 'collectable', namespace: Admin.Product
That way, Batman will look for PopularCollectable on Admin.Product instead of on Admin.

I was able to workaround this issue by embedding the belongs_to side of a has_many relation inside the parent Batman Model instance when it is sent to the client from Rails:
format.json {render :json => #post, :include => {:comments => {:include => :comments}}}

Related

Batman.js: Related model undefined for polymorphic association not found. Same namespace

I am trying to set up the polymorphic association in Batman as documented here. I am using LocalStorage and keep getting the following message:
Related model undefined for polymorphic association not found.
My model is a follows:
class App1.Model extends Batman.Model
#persist Batman.LocalStorage
class App1.Superpower extends App1.Model
#resourceName: 'superpower'
#encode 'id', 'name'
#belongsTo 'superpowerable', polymorphic: true
class App1.Hero extends App1.Model
#resourceName: 'hero'
#encode 'id', 'name'
#hasMany 'superpowers', as: 'superpowerable', polymorphic: true
class App1.Villain extends App1.Model
#resourceName: 'villain'
#encode 'id', 'name'
#hasMany 'superpowers', as: 'superpowerable', polymorphic: true
and the code I use to instantiate all:
superman = new App1.Hero(name: "Superman")
superman.save()
super_strength = new App1.Superpower(name: "Super Strength")
super_strength.save() # -> gives "Related model undefined for polymorphic association not found."
invincibility = new App1.Superpower(name: "Invincibility")
invincibility.save() # -> gives "Related model undefined for polymorphic association not found."
superman.get('superpowers').add super_strength
superman.get('superpowers').add invincibility
superman.save()
super_strength.save()
invincibility.save()
console.log superman.toJSON()
console.log super_strength.toJSON()
console.log invincibility.toJSON()
According to this question it depends on the namespace, but they are all the same in my code, so I'm really wondering what is going wrong here.
Thanks in advance
Uh oh... I wrote that example so if it doesn't work, I'm to blame :)
Just to be sure, did you call App1.run() at the beginning of that script? I ran into that issue while testing last week -- gotta call run to load associations!
Edit:
Oh, it looks like a matter of using the API just right. You have to be explicit with the #{label}_type and #{label}_id of the related models. Batman.js will load the associations from JSON just fine, but you'll have to specify them when initializing a new record, for example:
super_strength = new App1.Superpower
name: "Super Strength"
superpowerable_id: superman.get('id'),
superpowerable_type: 'Hero'
I put up a JSFiddle where it's working: http://jsfiddle.net/2atLZ/2/
I'll go back to the docs and add a note about this! Long term, would be great if the API just accepted #{label} then extracted #{label}_id and #{label}_type... but for now, it's not so.!

How can I include scoped model in Rails?

If I just including nested model in query such this
#projects = current_user.projects.all(include: :reviews)
everything ok. But Review model has some scope, that I need implement in query above. I trying this
#projects = current_user.projects.all(include: :reviews.unreaded)
and gets error. What is the right way to do this?
One option would be to create an association based on the scope, roughly:
#projects = current_user.projects.all(include: :unread_reviews)
Then create an unread_reviews association, roughly:
class Project < ...
has_many :unread_reviews, :conditions => ['read=?', true], :class_name => "Review"
(Replace the above has_many with your association particulars, obviously.)
This technique is discussed in the association docs.

Rails and Backbone architecture

I have been hearing a lot about Backbone and wanted to use it for my latest project to learn it. However, I am coming from Rails background, and my experiences do not seem to translate well for Backbone.
For example, I have four models which need to be displayed in a product view.
class Product < ActiveRecord::Base
belongs_to :category
belongs_to :child
has_many :actions
has_many :comments
end
class Child < ActiveRecord::Base
belongs_to :user
has_many :products
end
I am using Backbone-relational to define relationships in Backbone models. The following is product Backbone model.
class Ibabybox.Models.Product extends Backbone.RelationalModel
paramRoot: 'product'
urlRoot: '/products'
relations:
[
{
type: Backbone.HasMany
key: 'actions'
relatedModel: 'Ibabybox.Models.Action'
collectionType: 'Ibabybox.Collections.ActionsCollection'
reverseRelation:
key: 'product'
includeInJSON: 'id'
}
]
In the Backbone router, I do the following.
class Ibabybox.Routers.ProductsRouter extends Backbone.Router
routes:
"": "index"
":id": "show"
show: (id) ->
#product = new Ibabybox.Models.Product({id: id})
#product.fetch
success: (product) ->
actions = product.get('actions')
child = product.get('child')
#child_model = new Ibabybox.Models.Child({id: child.id})
user = #child_model.get('user')
#view = new Ibabybox.Views.Products.ShowView({model: product, actions: actions, child: child, user: user})
$("#products").html(#view.render().el)
And on Rails controller, I do the following.
class ProductsController < ApplicationController
def show
#product = Product.find(params[:id])
render json: #product.to_json(:include => [ :actions,
:child => { :include => {:user => {:methods => [:name] }}} ])
end
end
First, am I going about this in the right direction?
Second, it feels like a lot of things to define and write to display related things for a product and the reason for feeling I might be doing something wrong....
Any help/recommendation/correction would be much much appreciated!
Thanks!
According to backbone.js doc There's More Than One Way To Do It, but some ways become pain while your application grows. After developing 3 application using backbone.js & rails here is my experience.
backbone.js is not suited for every website. Specially if your website is content based. In this case using pjax give better result.
Resource based design is best approach. And hopefully both of rails and backbone.js rock. You can easily use default rails generator with minimal changes. For complicated objects i prefer using jbuilder since i can cache objects.
If you are using explicit $.ajax,$.post,$.get probably you are wrong.[same as 2]
If you are repeating yourself there is something wrong.
Don't miss backbone events.( In your case you can use events instead of passing success callback to fetch)

set_table_name doesn't work if model is in a deep namespace - bug in rails 2.3.14?

I'm using Rails 2.3.14
UPDATE 3 the trick I found in update 2 only works for the first access of the association... so, I stick the set_table_name lines in my application controller. This is so weird. I hope this gets fixed. =\
UPDATE 2 if I manually / nastily / hackily set teh tables for the troublesome classes at the bottom of my environment file, I stop getting the errors.
app/config/environment.rb:
Page::Template::Content.set_table_name "template_page_contents"
Page::Template::Section.set_table_name "template_page_sections"
Why do I have to do this? is rails broken for this particular use case?
UPDATE: it appears that set_table_name isn't working when it's initially called in my Page::Template::Content class.
but if I call it right before I use the association, it works...
ap Page::Template::Content.table_name # prints "contents"
Page::Template::Content.set_table_name "template_page_contents"
ap Page::Template::Content.table_name # prints "template_page_contents"
return self.page_template_contents.first # doesn't error after the above is exec'd
ORIGINAL QUESTION:
TL; DR: I have a has_many relationship that I'm trying to access, but Rails thinks that the table that the has_many relationship uses is a different table
I've been getting this error, where when I try to access a Page::Template::Content that belongs_to a Page::Template via a has_many relationship.
Mysql2::Error: Unknown column 'contents.template_page_id' in 'where clause': SELECT * FROM `contents` WHERE (`contents`.template_page_id = 17) LIMIT 1
Looking at the error logs, I figured I needed to starting using some print statements to find out why rails was trying to find the associated objects in the wrong table.
gems_path/activerecord-2.3.14/lib/active_record/associations/association_collection.rb:63:in `find'
I just decided to print the #reflection object since everything seems to be happening around that. Here is how I did that:
require "awesome_print" # best console printer (colors, pretty print, etc)
def find(*args) # preexisting method header
ap "-------" # separator so I know when one reflection begins / ends, etc
ap #reflection # object in question
... # rest of the method
Last '#reflection' that printed before the error:
"-------"
#<ActiveRecord::Reflection::AssociationReflection:0x108d028a8
#collection = true,
attr_reader :active_record = class Page::Template < LibraryItem {...},
attr_reader :class_name = "Page::Template::Content",
attr_reader :klass = class Content < LibraryItem {...},
attr_reader :macro = :has_many,
attr_reader :name = :page_template_contents,
attr_reader :options = {
:foreign_key => "template_page_id",
:class_name => "Page::Template::Content",
:extend => []
},
attr_reader :primary_key_name = "template_page_id",
attr_reader :quoted_table_name = "`contents`"
>
there are a couple things wrong in the above block of code.
:klass should be Page::Template::Content
:name should be :contents
:quoted_table_name should be `contents`
How my models are set up:
app/models/page.rb:
class Page < LibrayItem
belongs_to :template_page, :class_name => "Page::Template"
app/models/page/template.rb
class Page::Template < Library Item
set_table_name "template_pages"
has_many :page_template_contents,
:class_name => "Page::Template::Content",
:foreign_key => "template_page_id"
app/models/page/template/content.rb
class Page::Template::Content
set_table_name "template_page_contents"
belongs_to :template_page,
:class_name => "Page::Template",
:foreign_key => "template_page_id"
class Page::Template
...
return self.page_template_contents.first
the class the association is choosing (unrelated to my page / template structure above):
class Content < LibraryItem
set_table_name "contents"
# no associations to above classes
So... what is causing this and how do I fix it?
Your issue is not due to set_table_name but rather due to how Rails finds the target model class in an association and the fact that you have a top-level model (Content) which shares its name with a model nested in a deeper namespace (Page::Template::Content). The weird difference in behaviour is most likely due to differences in what classes are actually loaded at the time the association is examined (remember that by default in development mode Rails loads model classes on demand when they are first referenced).
When you define an association (whether you specify the class name of the target model of the association explicitly as you have done or accept the default), Rails has to turn the name of the target model class into a Ruby constant so that it can refer to that target class. To do this, it invokes the protected class method compute_type on the model class that is defining the association.
For example, you have
class Page::Template < LibraryItem
set_table_name "template_pages"
has_many :page_template_contents,
:class_name => "Page::Template::Content",
:foreign_key => "template_page_id"
end
You can test in the Rails console how compute_type works for this class:
$ ./script/console
Loading development environment (Rails 2.3.14)
> Page::Template.send(:compute_type, "Page::Template::Content")
=> Page::Template::Content(id: integer, template_page_id: integer)
I see the result is, as I expect, a reference to the class Page::Template::Content.
However, if I restart the Rails console but this time cause the model class Content to be loaded first (by referencing it) and then try again, I see this (I didn't bother creating a table for the Content model, but that doesn't change the significant behavior):
$ ./script/console
Loading development environment (Rails 2.3.14)
>> Content # Reference Content class so that it gets loaded
=> Content(Table doesn't exist)
>> Page::Template.send(:compute_type, "Page::Template::Content")
=> Content(Table doesn't exist)
As you can see, this time I get a reference to Content instead.
So what can you do about this?
Well, first of all, you should realize that using namespaced model classes in Rails (certainly in 2.3) is a real pain. It's nice to organize your model classes in a hierarchy but it certainly does make life more difficult. As you can see above, if you have a class in one namespace that has the same name as a class in another namespace, this gets more hairy.
If you still want to live with this situation, I can make a couple of suggestions. One of the following may help:
Turn on class caching in development mode. This will cause all model classes to be preloaded rather than loading them on demand as is the default. It will make your code above more predictable, but may make development somewhat unpleasant (since classes will no longer be reloaded with each request).
Explicity require dependent classes in classes with problematic associations. For example:
class Page::Template < LibraryItem
require 'page/template/content'
set_table_name "template_pages"
has_many :page_template_contents, ...
end
Override the compute_type method in your classes where you know referencing associated class may go awry so that it returns the namespaced class no matter what. Without knowing your class hierachy in more detail I cannot give a full suggestion on how to do this, but a quick and dirty example follows:
class Page::Template < LibraryItem
set_table_name "template_pages"
has_many :page_template_contents,
:class_name => "Page::Template::Content",
:foreign_key => "template_page_id"
def self.compute_type(type_name)
return Page::Template::Content if type_name == "Page::Template::Content"
super
end
end

Creating AR model for Drupal database

how do I change my activerecord model default behavior for the find method?
For example, i want to search for all books inside drupal nodes database, but drupal uses only one table for all data, and uses the 'type' column to find out the type
class Book < ActiveRecord::Base
set_table_name 'node'
def find(*args)
:conditions => {:type => 'book'}
super
end
end
this is the correct approach to solve this problem?
I've solved this creating a model for Node and using named_scopes
the result was this
class Node
set_table_name 'node'
set_primary_key 'nid'
named_scope :book, :conditions => {:type => 'book'}
# if i don't overwrite this method, i would get an error when i try to use the type column
def self.inheritance_column
"rails_type"
end
end
it works, but doesn't look like the rails way of doing stuff. If I get enought time soon, I'll try to write a library to access drupal data using something like acts_as_drupal_node
now i can fetch all book entries using:
#books = Node.book.all

Resources