Saturday, March 5, 2011

Celebrating some small victories

Things have finally been going really really well here at Henslowe's Cloud. I feel like I've hit some point where the learning curve gets way less intense, and I can do a lot of very complex things.

However, they were a total pain in the rear to figure out, so I wanted to write down what I did, and maybe help someone else out. As you may recall, I decided to use the production as the central point of my schema. It's just a massive join table, essentially, bringing together all the different elements of a production. It makes sense, because a production is the unique confluence of a play, a set of actors, a theater, a space, a director, a designer, technicians. It has defined dates. So that was good. Then I faced this problem: How do you connect actors to characters? In two different productions at the same theater, the same actor could play Hamlet and Laertes. I had to tie that specific casting choice to the specific production. This meant that I had to put a join table on my join table, basically. Each Production :has_many castings. A casting is the confluence of a user (actor), a character, and a production. User A could play Hamlet in two different productions. User A could play Hamlet in Production 1, but Laertes in Production 2, at the same theater, and there aren't any conflicts. I know it seems needlessly complex (and IT IS), but it's working. Everything simpler created conflicts if I wanted to have multiple productions with the same play and users.

It sounds complicated, but here's how it works. (I'm just including the relevant bits of code here).


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


So, you see, a Casting just joins a character and a user on a production. That's it!


class Production < ActiveRecord::Base
attr_accessible :castings_attributes
has_many :castings, :dependent => :destroy
accepts_nested_attributes_for :castings
has_many :actor_users, :through => :castings, :source => :user
belongs_to :play
has_many :characters, :through => :play
end


Here's one part that took me FO EVAH to figure out--that attr_accessible line MUST INCLUDE :castings_attributes . NOT just :castings.

Also, note the has_many :actor_users bit--that's the solution to the problem I posted about before, where I couldn't figure out how to use the nested_has_many_through plugin. I can just make up the "actor_user" part. I could make it "red_penguins" and I don't have to declare it anywhere. The source has to be a real model, though.

So, and here's the part where I'm really proud of myself, if a play has a certain list of characters, and that list of characters never changes, when I create a new production, I want to create all the castings too, and give them their character names. Then all the casting director has to do is select the actor to fill that role. Here's my create action from my Productions controller.


def create
@production = Production.new(params[:production])
@production.play.characters.each do |c|
@production.castings.build(:character_id => c.id)
end
if @production.save
flash[:notice] = "Successfully created production."
redirect_to @production
else
render :action => 'new'
end
end


There's probably a more elegant way to write that, but it works, and that's good enough for me.
Click here for a screenshot of the Productions/edit page, where you can see this in action.

Once I figured out all that stuff with stacking the builds, I was in business. I also wrote a thing that will build all the acts for a play as it's creating the play. I'm going to go into more detail on this than I probably need to, because I searched and searched but could not find a tutorial on how to set up a temporary variable in rails. I hope some poor shmuck like me has an easier time googling.

Here we go. Just follow "number_of_acts." That's my temporary variable.

First of all, you need to add an attr_accessor line in the model that will take the temporary variable. You also need to include the temporary variable in the attr_accessible line. Mine looked like this:

class Play < ActiveRecord::Base attr_accessible :title, :author_id, :publication_date, :genre_ids, :summary, :number_of_acts attr_accessor :number_of_acts end


Then, in my view, I defined the number_of_acts and gave it a value. (This form might look odd. I'm using Formtastic, and I'm IN LOVE. I don't have much else to say about Formtastic, other than it's really awesome and totally the way forms should be.)

<% title "New Play" %>
<% semantic_form_for @play do |f| %>
<% f.inputs do %>
<%= f.input :author, :label_method => :name %>
<%= f.input :title %>
<%= f.input :publication_date, :start_year => 1300 %>
<%= f.input :genres, :as => :check_boxes, :collection => Genre.find(:all) %>
<%= f.input :summary %>
<%= f.input :number_of_acts, :as => :numeric %>
<% end %>
<%= f.buttons %>
<% end %>

<%= link_to "Back to List", plays_path %>



So, then I take the number_of_acts and pass it to the create action in my Plays controller.

def create
@play = Play.new(params[:play])
number_of_acts = @play.number_of_acts.to_i
n = 1
number_of_acts.times do |b|
@play.acts.build(:number => n)
n +=1
end
if @play.save
flash[:notice] = "Successfully created play."
redirect_to @play
else
render :action => 'new'
end
end

Note that I even make my robot slaves set the act number as they go!

There's still a lot to do, of course. The next step is to write the acts edit page and all the stuff to do with scenes and French scenes (a French scene is generally a sub-scene unit, which lasts while all the same characters are on stage. When someone exits, it's a new French scene, although it may well be the same scene. These are very useful if you're trying to figure out doubling.). Once I get that working, I want to add fill-in predictive text to the casting interface, so you can just start typing someone's name and it will populate before you get very far into it. I have no idea how to do that, but it would be convenient.

I also need to revisit the authorization/authentication stuff. When I rearranged everything to center on the production, I'm sure I thoroughly ruined the auth work I had done. I'll probably just tear it all down and start over. I'd love to use Google OpenID or Facebook Connect for logins, because it would be easier and arguably more secure than handling it myself. I'm not sure how to do that, but I'll figure it out.

The good news is, I'm very close to a complete model. Once I have that done, I can start asking it questions, and that's where the real power is.
  • How many times has this actor played Ophelia?
  • Who is available for rehearsal on Friday night? What scenes can we do with those people?
  • Here's my casting list--have I messed up my doubling? Is any actor onstage as two characters at once?
  • How many seats are left for Friday's show?
And the list goes on.
(one more question--HTML messes up my pretty ruby indenting. Is there any way to tell it not to do that?)

So, HERE is a question:
I've learned tons about Ruby and Rails in the past year and a half. I'm learning more every day. I started with this crazy complex problem, though. What less-complex problem should I solve? I constantly see simpler things making crazy bank, and I think, "Why didn't I think of that?"

No comments:

Post a Comment