-
11
NOV
2011Doing it Wrong
Continuing with my Breaking All of the Rules series, I want to peek into several little areas where I've been caught doing the wrong thing. I'm a rule breaker and I'm determined to take someone down with me!
My Forbidden Parser
In one application, I work with an API that hands me very simple data like this:
<emails> <email>user1@example.com</email> <email>user2@example.com</email> <email>user3@example.com</email> … </emails>
Now I need to make a dirty confession: I parsed this with a Regular Expression.
I know, I know. We should never parse HTML or XML with a Regular Expression. If you don't believe me, just take a moment to actually read that response. Yikes!
Oh and you shouldn't validate emails with a Regular Expression. Oops. We're talking about at least two violations here.
But it gets worse.
You may be think I rolled a little parser based on Regular Expressions. That might look like this:
#!/usr/bin/env ruby -w require "strscan" class EmailParser def initialize(data) @scanner = StringScanner.new(data) end def parse(&block) parse_emails(&block) end private def parse_emails(&block) @scanner.scan(%r{\s*<emails>\s*}) or fail "Failed to match list start" loop do parse_email(&block) or break end @scanner.scan(%r{\s*</emails>}) or fail "Failed to match list end" end def parse_email(&block) if @scanner.scan(%r{<email>\s*}) if email = @scanner.scan_until(%r{</email>\s*}) block[email.strip[0..-9].strip] return true else fail "Failed to match email end" end end false end end EmailParser.new(ARGF.read).parse do |email| puts email end
-
10
APR
2008Five ActiveRecord Tips
This article was written for the Railcasts 100th Episode Contest. I think the idea is great and I look forward to reading great tips from all who decide to participate.
1.
create_or_find_by_…
I imagine most of you know that
ActiveRecord
can handle finders like:MyARClass.find_or_create_by_name(some_name)
This will attempt to find the object that has
some_name
in itsname
field or, if the find fails, a new object will be created with thatname
. It's important to note that the order is exactly as I just listed it: find then create. Here are the relevant lines from the current Rails source showing the process:record = find_initial(options) if record.nil? record = self.new { |r| r.send(:attributes=, attributes, guard_protected_attributes) } #{'yield(record) if block_given?'} #{'record.save' if instantiator == :create} record else record end
The above code is inside a
String
literal fed toclass_eval()
, which is why you see interpolation being used.Unfortunately, this process is subject to race conditions because the object could be created by another process (or
Thread
) between the find and the creation. If that happens, you are likely to run into another hardship in that calls tocreate()
fail quietly (returning the unsaved object). These are some pretty rare happenings for sure, but they can be avoided under certain conditions.