Array.valN: Working smarter with Rails, with Ruby
Posted by Christopher Wojno Mon, 13 Aug 2007 18:37:00 GMT
Rails has a powerful form builder mechanism. However, if you want to make a list of things and have the data flow back to you without involving ActiveRecord, you’re in for some hurt; well, you were, unless you decide to use this module. Warning: this article is moderately advanced. I’m assuming you’re familiar with ActionControllers and the ActionView in my examples. If you’re familiar with how to use the basics of Rails, you should probably only read the first 1/2. Read the first code block to see what my code can do. But to see why this code is useful, read on to the examples.
Say I have a set of fox phrases. Now, if a certain fox is to have a list of catch phrases, and you don’t want to store them in a database, you can’t use Rails’ form helpers (text_field, hidden_field, etc.) and the params[] calls without a good deal of ugly, hack code (creating spoof instance variables to display data and creating a fake active record objects to receive that data from a form). To help beautify code, I created an additional way of accessing (setting and getting) elements in an array (valN). Behold the ArrayIterAccessors:
>> @catch_phrases = ['Addiction is like Pokemon!','Hey! There goes my pickup!','Chucky Bacon!'] => ["Addiction is like Pokemon!", "Hey! There goes my pickup!", "Chucky Bacon!"] >> @catch_phrases.val2 => "Chucky Bacon!" >> @catch_phrases.val2 = 'Chunky Bacon! Chunky Bacon! Chunky Bacon!' => "Chunky Bacon! Chunky Bacon! Chunky Bacon!" >> @catch_phrases => ["Addiction is like Pokemon!", "Hey! There goes my pickup!", "Chunky Bacon! Chunky Bacon! Chunky Bacon!"]
“Now,” you say, “why the heck is he not just using the []!?” Well, I say, Try doing that within ActionView (rhtml template) for a text field:
<%= text_field 'catch_phrases', '[0]' %>
That will fail horribly. Depending on what you set @catch_phrases to, you’ll get:
undefined method `[0]' for ['Addiction is like Pokemon!','Hey! There goes my pickup!','Chucky Bacon!']:Array
And that doesn’t get you anywhere. So, supposin’ you say, oh, I dunno:
# My special module (should be a plugin but for demonstration
# purposes, I'm defining it inline
# Skip this code for now
class CatchPhrasesController < ApplicationController
module ArrayIterAccessors
alias :old_method_missing :method_missing
def method_missing( sym, *args, &block )
sym_s = sym.to_s; t = sym_s[/\d+/]
if t and sym_s[/\Aval\d+=?\Z/]
if sym_s[/=\Z/]
self[t.to_i] = args.first; return self[t.to_i]
else
return self[t.to_i]
end
else
return old_method_missing( sym, *args, &block )
end
end
end
# this is an action for your CatchPhrases Controller URL:
# http://site.example.com/catch_phrases/new
def new
Array.send( :include, ArrayIterAccessors )
@catch_phrases = ['Addiction is like Pokemon!','Hey! There goes my pickup!','Chucky Bacon!']
end
end
### in your view: new.rhtml ###
<% @catch_phrases.each_with_index do |item,index| %>
<%= text_field 'catch_phrases', "val#{index}" %>
<% end %>
So when your view renders, you’ll see 3 text fields, each with the catch phrase pre-filled (so you’ll have an addiction catch phrase, a pickup catch phrase, and a chunky bacon catch phrase). You can edit them and when you submit the form, you can reconstitute your array by:
@catch_phrases = params[:catch_phrases].keys.collect{|k| k.to_s[/\d+/] }.sort.collect{|k| params[:catch_phrases][('val'+k.to_s).to_sym] }
If you didn’t change the catch phrases, you’ll get the exact array: @catch_phrases, back. And if you DID change the phrases, you’ll get an array, in order, of the altered catch phrases!
Pitfalls
- No ActiveRecord (well, I claimed that as a plus above) means: no validation. That’s up to you, unless you include a validatable mixin (there are a few of those). This also means there is no way of reporting errors back automatically. I suppose I could write something later to do this.
- That last line is ugly and slow.
- The use example is for demonstration purposes and is awful: DO NOT USE IT. That will mix the module in EVERY time you render the “new” action. To do this correctly take the module and the
Array.send :include, ArrayIterAccessors
code out of your controller and:- Put it in a plugin OR
- Create a new file in lib/array_iter_accessors.rb (in your Rails project) and put the module into that. Then, in your environment.rb file (at the bottom), put the
Array.send :include, ArrayIterAccessors
code. That way, the mixin will only be done once.
What I did
I overrided the method_missing method for the Array class (then mixed it in). I scan for any method calls for methods beginning with “val” and ending in a number. I also parse for an ’=’ in case you’d like to use it for assignment too. Then, I call the array’s [] operator to access the values at the desired position. And because I’m using the array’s built-in operator, you’ll see all the error messages and behavior you’re used to seeing without my module.
Why val?
Because it’s two letters shorter than “value.” What? You expected a profound reason? Sorry if this precludes your ability to keep track of your girlfriends.
You have enough here to write your own plug-in, or just to use it when you need it. Happy riding.
