Thursday, October 28, 2010

Grasshoppers

We came unto the land whither thou sentest us, and surely it floweth with milk and honey; and this is the fruit of it...And there we saw the giants...and we were in our own sight as grasshoppers, and so we were in their sight.




I HATE being so close and yet so far. OOOOH I hate it. I have practically everything working in my models and database. Yay! but for some reason, I can't get Users to connect to Technicians (I should probably change the name of that model to TechnicalRoles or something) or Characters (via Castings). I'm using ianwhite's nested_has_many_through plugin, and I'm sure there is something I'm just not getting. Also, I wish documentation for this sort of thing was more helpful. You get what you pay for, but maybe I could be of service to the Rails community by first having someone explain their plugin to me, and then writing better documentation. Of course, maybe they're all happy with their documentation and I just need to get better at reading it.

Sigh.

Ok, so here's what I came up with.
The Production model is the nexus of everything. It's the one unique event. A theater does lots of different plays; each play is done by lots of different theaters; actors work for various theaters. A production only happens in one moment in time and is a unique combination of participants, theater spaces, text, etc. This was my breakthrough, and I'm sure it's the right thing. To that end:

class Production < ActiveRecord::Base
  attr_accessible :date
  has_many :castings
  has_many :users, :through => :castings
  has_many :technicians
  has_many :users, :through => :technicians, :source =>:users
  belongs_to :theater
  belongs_to :play
  has_many :characters, :through => :play
end

Castings is basically a join model to connect Users to Characters. The characters are also connected to their plays.

The part that I'm hung up on is connecting Characters to Users.
These work:
>> @production.characters
=> [#, #, #]

>> @user.productions
=> [#, #, #]

These don't:
>> @user.characters
>> @character.productions


It throws this error:
NameError: uninitialized constant User::Characters
    from /home/alisha/.gem/ruby/1.8/gems/activesupport-2.3.5/lib/active_support/dependencies.rb:105:in `const_missing'
    from /home/alisha/.gem/ruby/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:2199:in `compute_type'
    from /home/alisha/.gem/ruby/1.8/gems/activesupport-2.3.5/lib/active_support/core_ext/kernel/reporting.rb:11:in `silence_warnings'
    from /home/alisha/.gem/ruby/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:2195:in `compute_type'
    from /home/alisha/.gem/ruby/1.8/gems/activerecord-2.3.5/lib/active_record/reflection.rb:156:in `send'
    from /home/alisha/.gem/ruby/1.8/gems/activerecord-2.3.5/lib/active_record/reflection.rb:156:in `klass'
    from /home/alisha/code/henslowe/vendor/plugins/nested_has_many_through/lib/nested_has_many_through.rb:43:in `construct_nested_join_attributes'
    from /home/alisha/code/henslowe/vendor/plugins/nested_has_many_through/lib/nested_has_many_through.rb:19:in `construct_conditions'
    from /home/alisha/.gem/ruby/1.8/gems/activerecord-2.3.5/lib/active_record/associations/has_many_through_association.rb:201:in `construct_sql'
    from /home/alisha/.gem/ruby/1.8/gems/activerecord-2.3.5/lib/active_record/associations/association_collection.rb:21:in `initialize'
    from /home/alisha/.gem/ruby/1.8/gems/activerecord-2.3.5/lib/active_record/associations/has_many_through_association.rb:6:in `initialize'
    from /home/alisha/.gem/ruby/1.8/gems/activerecord-2.3.5/lib/active_record/associations.rb:1300:in `new'
    from /home/alisha/.gem/ruby/1.8/gems/activerecord-2.3.5/lib/active_record/associations.rb:1300:in `characters'

I'm not Rubiriffic enough to understand that error message.

So anyway, here are the relevant bits of the models that it's looking at.
class User < ActiveRecord::Base
  has_many :castings
  has_many :characters, :through => :castings, :dependent => :destroy
  has_many :productions, :through => :castings
  has_many :technicians
  has_many :productions, :through => :technicians
end

class Character < ActiveRecord::Base
  attr_accessible :name, :play_id
  belongs_to :play
  has_many :castings
  belongs_to :user, :through => :castings
end

class Casting < ActiveRecord::Base
  attr_accessible :user, :character
  belongs_to :production
  belongs_to :character
  belongs_to :user
end

class Production < ActiveRecord::Base
  has_many :castings
  has_many :users, :through => :castings
  has_many :technicians
  has_many :users, :through => :technicians, :source =>:users
end

The problem doesn't seem to be in the Casting model -- I can get all of a user's productions, which goes through the Casting model, but I can't get all of a user's characters. I can't get all of a character's productions, but I CAN get all of a production's characters.

I'm getting NoMethod errors when I try it:

@char.productions
NoMethodError: undefined method `productions' for #
    from /home/alisha/.gem/ruby/1.8/gems/activerecord-2.3.5/lib/active_record/attribute_methods.rb:260:in `method_missing'
@char.users
NoMethodError: undefined method `users' for #
    from /home/alisha/.gem/ruby/1.8/gems/activerecord-2.3.5/lib/active_record/attribute_methods.rb:260:in `method_missing'

I'm guessing the issue lies in the Character model, but I just can't figure it out. It's probably some stupid "that was singular but by convention it should be plural" crap nonsense. And I'm tired and I feel like a grasshopper today.

5 comments:

  1. Might this be a question worth posing on a forum such as stackoverflow or one of the RoR-specific forums?

    ReplyDelete
  2. Yes, but I have a 5-step process for solving these kinds of problems.
    1) Write it out, as above.
    2) 24-hour cooling-off period. Sometimes I come back and it all makes sense.
    3) Tell Kevin Radloff that I'll help him improve his OkCupid profile if he will have a look at my code.
    4) Stackoverflow,Rubyforum,etc.
    5) Heavy drinking, despair.

    ReplyDelete
  3. I do Python and C, so I'm out of my league in Ruby, and Rails in particular, but I showed this to our main Rails developer Ben Beachy, and he commented "[...] what I think may be causing this problem. Production.Users could be joined in multiple ways." FWIW.

    At least you're out of the strange world of Perl, where they're still cleaning up after the punctuation factory exploded years ago. It's a write-only language.

    ReplyDelete
  4. Someone call me? :)

    I'm afraid I don't know an easy solution to this but I'd suggest a few routes forward (in addition to Stacy's suggestions):

    1. Back-up and consider your database schema. It feels complicated to me--though that assessment based on profound ignorance of your project goals and constraints and you should probably ignore it.

    2. Explore the problem in a simpler setting. This StackOverflow post (http://stackoverflow.com/questions/636296/triple-join-in-ruby-on-rails) shows an abstracted but similar schema that might allow you to play your way to a solution.

    3. Drink lots of coffee. I find it works better than alcohol. :)

    Ben.

    ReplyDelete
  5. In case you guys were wondering, Kevin solved it (and I gave him some OK Cupid advice). Turns out the problem was more or less what I thought it was--I'm not good at reading rails documentation.

    The "source" needs to point to an actual model and the "has_many" can point to anything, even something I just made up. Redundancy is ok here.

    So this was what I needed in my Production model:

    has_many :castings
    has_many :actor_users, :through => :castings, :source => :user

    There is no Actor_users model, but I can make calls on the Production that make it seem like that does exist.

    ReplyDelete