Ruby, iOS, and Other Development

A place to share useful code snippets, ideas, and techniques

All code in posted articles shall be considered public domain unless otherwise noted.
Comments remain the property of their authors.

2009-03-23

Managing iPhone Development

I've spent a lot of time fighting with certificates, keys, and provisioning profiles in the time I've been working on iPhone apps for clients and myself. I finally figured out how to make it easy to manage multiple sets of certs/keys (i.e. one per client). Even if you are only working with a single set, though, it's still helpful to keep iPhone stuff separate from the rest of your keychain. I'm going to approach this as if you are starting from scratch, but it's easy enough to fix if you have already set up certs and keys and it should be pretty obvious how to go about it from these instructions.

First off, you need to know your tools. Keychain Access is where all of your certificates and keys (and passwords, and a variety of other things irrelevant to this discussion) live. Xcode, iPhone Configuration Utility, and iTunes all deal with the same store of provisioning profiles, but only the configuration utility is actually good at it. Download it from Apple right now and install it. When working within the iPhone Configuration Utility (hereafter referred to as iPCU) neither iTunes nor Xcode should be running since they will need to be restarted anyway to see any changes you make.

Before anything else, you need to download and install the WWDR intermediate certificate if you haven't already. Get it here and open it in Keychain Access (hereafter referred to as KA). You'll want to install it in either the login or System keychains (it doesn't matter much). Now that you're in KA, create a new keychain (File menu) and name it for the particular program portal you're working with at the moment. I recommend saving it in the default location, ~/Library/Keychains, but if you save it somewhere else just make sure you remember where. You'll need to set a password for it, and you can choose to be as secure or insecure as you like about it; the certs/keys would otherwise be in the login keychain, which is open by default as long as you are logged in, so anything is more secure than the alternative. Follow the program portal instructions for creating a Certificate Signing Request (CSR). Notice that creating the CSR created a private key in the login keychain in KA. Drag that private key to the new keychain you just created (the client keychain). Upload the CSR and go through the process of getting a developer certificate and a distribution certificate. (You'll need both eventually, and you can use the same CSR for both; if you don't, a new private key may be generated for the second CSR, and you'll need to drag that from the login keychain to the client keychain as well.) Install the certificates in the client keychain rather than System or login.

When you are done with this process, you should have a private key (maybe two — see above), a developer certificate, and a distribution certificate in the client keychain. I like to set the keychain to lock after a period of inactivity so Xcode asks me for a password when it codesigns and I know it's doing what I expect. Remember where you saved the keychain file? Make a backup copy of it now and put it somewhere safe (source control, offsite backup, optical media, whatever). If this is the only program portal you deal with, you're done. If not, right-click (or ctrl-click) on the client keychain and choose Delete Keychain "[whatever]".

Alert: Delete Keychain

IMPORTANT: Be sure to choose "Delete References" and not "Delete References & Files"!!! If you choose the wrong one, you will be glad you made that backup copy. KA will close the keychain, but you can open it again when you are working with that program portal again. You can then repeat the process for any other program portals that involve you.

Clearly, if someone else has created the distribution certificate you need to use you will need to get the cert and private key from that person instead. You can still put them in the client keychain once you have them, of course. If you have already been dealing with certs in your login keychain, you might have lots of private keys lying around and no good way to tell which key goes with which cert. I feel like there should be an easy way to tell, but I haven't found it. Instead, create a new keychain and put all but one of the private keys into it, leaving one in the login keychain, then lock that temporary keychain (i.e. click its lock icon in KA). Build something in Xcode that requires the codesigning cert you are testing and see whether it asks for a keychain password. If not, the key you left in the login keychain goes with that cert; otherwise, switch keys and try again. (You can be cleverer about it by locking away half the keys so it's a binary rather than linear search, plus you can test more than one cert at a time, but I leave that as an exercise for the reader.) Eventually you will be able to associate keys with certs and put them in their appropriately separate keychains.

Next up, we'll look at provisioning profiles. There are three kinds of profiles: development, ad hoc, and app store. Both ad hoc and app store are considered distribution profiles, but they behave differently. In fact, ad hoc profile behave more like development profiles than app store profiles. (Note: there may be still yet another profile type for enterprise distribution, but I have no experience with that.) A development or ad hoc profile permits an app with a particular app ID (or ID prefix) to be installed on any of a set of physical devices when signed by one of a set of certificates. For ad hoc, it's only one certificate: the distribution certificate. A device must have the provisioning profile installed on it to run the app, which Xcode does automatically for development profiles. I've had a lot of trouble with ad hoc profiles, and I'm still not confident I can get things working 100% of the time, but I have a better grasp on it than I used to. For the sake of my own sanity I am going to assume that you have figured out how to set up app IDs, devices, and provisioning profiles in the program portal.

There isn't a whole lot more to it, really, except keeping track of which profiles belong to which portals if you are dealing with more than one. I recommend naming the profiles carefully when you create them or, failing that, keeping a text file listing what each profile identifier is for. Whenever you have a new profile you'll want to use to build an app, I recommend installing it in iPCU rather than Xcode. It seems to work more dependably for me. Also, if it's an ad hoc profile, I recommend installing it on the device using iPCU rather than iTunes if at all possible. If you use the multiple certs/keys keychains trick, I'd like to tell you that there is a similarly good way to manage provisioning profiles; I don't know of one. The good thing, though, is that Xcode is smart enough to check the currently open keychains when presenting you with a list of provisioning profiles in a project's (or target's) build settings. If the cert for a particular profile is not available (even if the keychain is locked, its contents are available as long as KA has it listed), it will be grayed out with a message saying <matching certificate identity with private key not found in login keychain>. No matter how many provisioning profiles you have installed from other program portals, only the ones related to the certs and keys you have open will be available, which helps avoid silly mistakes.

I hope this is helpful to someone out there. I know I wish I'd known this stuff when I started developing for the iPhone. Enjoy!

Update! The 3.0 SDK deals with things a little differently. The separate keychains trick still works well, but you need to manually set the default keychain in Keychain Access to whichever one is appropriate for your current project.

Labels: ,

2008-03-08

SSL Certificates and Net::HTTPS

I was getting tired of seeing "warning: peer certificate won't be verified in this SSL session" from Ruby's net/https library, so I started looking around for how to get it to actually verify the SSL certificate. I found lots of links on how to tell it not to bother verifying, but it wasn't until I found someone's Japanese blog that I found the clue I was looking for. Now, I don't know Japanese, but I can read Ruby. For the benefit of other English speakers/readers out there, I now present the solution.

First off, I'm giving a full example request using basic authentication (not that GMail uses basic authentication, but this is an example) because I was unable to find a good example elsewhere and made it this far by trial and error. The following will produce the warning I mentioned:

require 'net/http'
require 'net/https'
require 'uri'

url = URI.parse 'https://myname:mypass@mail.google.com/'
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = (url.scheme == 'https')
request = Net::HTTP::Get.new(url.path)
request.basic_auth url.user, url.password
response = http.request(request)

To avoid the warning, we can either tell it not to warn us and blithely accept whatever certificate we receive, or we can give it enough information to authenticate the certificate against the root CA certificates. In the following example, we'll do both. If we find the file /usr/share/curl/curl-ca-bundle.crt then we will verify, otherwise we will silently ignore the issue:

require 'net/http'
require 'net/https'
require 'uri'

RootCA = '/usr/share/curl/curl-ca-bundle.crt'

url = URI.parse 'https://myname:mypass@mail.google.com/'
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = (url.scheme == 'https')
if File.exist? RootCA
 http.ca_file = RootCA
 http.verify_mode = OpenSSL::SSL::VERIFY_PEER
 http.verify_depth = 5
else
 http.verify_mode = OpenSSL::SSL::VERIFY_NONE
end
request = Net::HTTP::Get.new(url.path)
request.basic_auth url.user, url.password
response = http.request(request)

And there you have it. Not all that complicated, but poorly documented. Now it's more findably (i.e. Googleably) documented. Enjoy!

Update 2009-05-21: Thanks to a comment from Chewi, here's an even better approach:

require 'net/http'
require 'net/https'
require 'uri'

RootCA = '/etc/ssl/certs'

url = URI.parse 'https://myname:mypass@mail.google.com/'
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = (url.scheme == 'https')
if File.directory? RootCA
  http.ca_path = RootCA
  http.verify_mode = OpenSSL::SSL::VERIFY_PEER
  http.verify_depth = 5
else
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE
end
request = Net::HTTP::Get.new(url.path)
request.basic_auth url.user, url.password
response = http.request(request)

Labels: ,

2008-01-07

Randomizing an Array Revisited

It was pointed out in a comment on my post about randomizing arrays in Ruby that the sort_by{rand} is O(n log n), and it can be done in linear time, i.e. O(n). This is, of course, correct. Efficiency wasn't my primary concern in the original post, so much as a quick and easy to remember solution. That said, it's worth presenting the more complicated but more efficient algorithm.

I could just give the link to the blog post linked in the comment, but for the convenience of the reader I'll repeat the solution here (with minor changes that make me happier without changing the algorithm):

class Array
  def shuffle
    array = dup
    size.downto 2 do |j|
      r = rand j
      array[j-1], array[r] = array[r], array[j-1]
    end
    array
  end
end

Enjoy!

Labels: ,

2008-01-01

Do You Understand What Your Web Framework is Doing?

It's a new year, and I'm going to start off 2008 wrong with a code-free post. Sorry about that. This stems from realizing how little many developers (judging from postings to a variety of mailing lists) seem to understand about what their web frameworks do for them when it comes to generating code in other languages (particularly JavaScript and SQL). It's Rails-flavored, but not Rails-specific.

Here's a quick quiz. I'm assuming that you, the reader, are a web developer familiar with JavaScript, SQL, and some reasonably modern web app framework:

  1. Does JavaScript validation on a web form guarantee that when the form is submitted the server will receive valid data?
  2. Should foreign key columns each get their own index?
  3. How is JSON parsed into data structures in memory in a browser?
  4. Are multi-table joins inefficient?
  5. Can a web page make requests to a host other than the host from which the page itself was requested?

We'll come back to that. I am going to start by talking about a web browser (client) interacting via HTTP(S) with a web server. There are three pieces here, not two. The HTTP protocol matters since it is easy to work with and understand and there are lots of tools for working with it. There are some important differences between the web client/server environment and a more traditional client/server system:

  • Connections are not persistent, and consist of only a single request and response. (Note that HTTP keep-alive does not change this; what persists is the TCP connection, and does not affect the application layer.)
  • Interaction can only be initiated by the client, not the server. This is a result of the previous difference.
  • The server cannot assume anything about the data received from the client.

Most people developing web sites/applications think in terms of the server software they are developing. Much as first-time GUI developers often find it baffling, the inversion of control involved in modern web programming confuses many developers. The server has full control when responding to a request but, once it has generated that response, control reverts to the client. For one thing, that means that data on the client does not get to the server unless the client decides to send it. It also means that data from the server does not get to the client unless the client decides to request it. One needs to work from the point of view of the user in front of the browser.

A common question on the Rails mailing list is how to use RJS to retrieve some value from the client. While the desire isn't ridiculous, and it can be done in a roundabout way with a certain amount of jumping through hoops, phrasing the question that way shows a lack of understanding of where the RJS-generated code will be executing. (The hoop jumping involves having the RJS generate an AJAX request back to the server to submit the value back to some URL on the server.)

There was a recent thread on the Rails list complaining about the functionality in Rails (largely RJS and various helpers) that attempts to hide the complexity of interactions between client-side and server-side code and largely results in maintainability problems in the code and misunderstandings for the developer. I don't agree with everything in either the original post or the various responses, but it highlights a problem Assaf identified months ago.

Assaf is concerned with bad (inefficient and/or incorrect) SQL being generated because the developer doesn't understand what the framework is doing underneath. I'm concerned about bad (incorrect, unmaintainable, and/or hard to debug) JavaScript being generated. Rails makes it easy to get results without understanding what it is doing for you, which is great for prototyping and dangerous for production.

It is important to understand what code is being executed where, when, and how. When developing a rich user experience in a web browser, one must understand the DOM, the event model, the browser security model, the JavaScript language, the single-threaded nature of JavaScript execution in the browser, XMLHttpRequest, etc. just as one must understand database indexing, column types, SQL, table/row locking, etc. to develop a production-quality database-backed web application.

Let's go back to that quiz. You shouldn't have had to think too hard about any of these, and you should feel certain about your answers. And those answers should be:

  1. Does JavaScript validation on a web form guarantee that when the form is submitted the server will receive valid data? Nope. The server receives data over HTTP, and that HTTP connection could come from any program, not just a web browser. Furthermore, JavaScript can be turned off in most browsers. On top of that, most browsers make it possible to mess with the web page live and/or the data being submitted. Client-side validation is a user interface nicety, but provides no guarantees about the data the server sees.
  2. Should foreign key columns each get their own index? Sometimes. It depends very much on what queries will involve them. A join table (i.e. one with more than one foreign key that represents a many-to-many relationship between tables) usually benefits from an index on all foreign keys, sometimes even multiple indices of the same columns in different orders. Tables only queried by columns other than their foreign keys generally don't benefit from indexing those foreign keys, even if the table is usually joined against the tables to which those foreign keys refer.
  3. How is JSON parsed into data structures in memory in a browser? Since JSON is JavaScript, it is executed with eval() to be parsed with JSON.parse() into memory in the browser.
  4. Are multi-table joins inefficient? This depends on the number of tables, available indices, and the database engine. Joining 18 tables in MySQL can make the query optimizer hang for hours (that's the query optimizer, not executing the query), regardless of available indices on the tables involved. A query on tables lacking indices on appropriate columns will require full table scans in any database, which is always slow (unless the unindexed tables have very few rows). It is always worth asking your database engine to explain and profile the queries you'll be running. Incidentally, database logs from running your unit/functional/integration/whatever tests are a great place to start.
  5. Can a web page make requests to a host other than the host from which the page itself was requested? Yes, but not with XMLHttpRequest. At the simplest level, an img tag makes a request from any arbitrary URL, though the response is not available to JavaScript. To interact with a different host with almost the same flexibility as an XMLHttpRequest, one uses a script tag. See this blog post for a discussion.

How did you do? If you didn't get them all, you need to keep learning. If you got them all right, don't get too cocky; you may still not know everything you need to know to avoid the pitfalls of a code-generating framework. I keep learning about things I thought I knew thoroughly, and I wouldn't have it any other way. Enjoy!

Labels: , , ,

2007-05-21

Named Array Slots

Sometimes you have an array of data that isn't quite complicated enough for a full-fledged data model, but you want to access elements by name rather than positionally. Probably you even have a bunch of these arrays with positions corresponding to named fields. These arrays might have come from a DBI query, or CSV, or parsing some arbitrary data file, but ultimately you have a need to make your code more readable and avoid poking at these data structures with error-prone magic numbers. What you actually want is a Ruby module that defines methods to access the fields, with which you can then extend the Array objects.

Suppose that your arrays represent users and have four elements, in order: name, gender, email, zip. The naïve, ad hoc way of doing things, then, is:

module MyFields
  def name
    self[0]
  end
  def name=(val)
    self[0] = val
  end
  def age
    self[1]
  end
  def age=(val)
    self[1] = val
  end
  def email
    self[2]
  end
  def email=(val)
    self[2] = val
  end
  def zip
    self[3]
  end
  def zip=(val)
    self[3] = val
  end
end

What a mess, and that's for just four fields! Let's do a little dynamic programming. It's still simple and ad hoc, but it's better:

module MyFields
  %w(name age email zip).each_with_index { |field,i|
    define_method(field) { self[i] }
    define_method("#{field}=") { |val| self[i] = val }
  }
end

Much better, and we can change the list of fields pretty easily. Still, if we have several different sets of fields (e.g. rows from several different database tables) that's a lot of syntax for something pretty simple. Also, if both the field names and data are coming from an external data source, you may only care about some limited number of those fields but still need to get all of them properly named in the correct order. Ultimately, you'd like to be able to take an array of arbitrary objects, convert the objects to strings, and get a module with which you can extend your row arrays out of it. Something like this:

class Array
  ConvertElementsToFields = lambda { |f|
    f = "#{f}" # get as a new string, even if it's already a String
    f.downcase!
    f.gsub!(/[^\w]+/, '_')
    f
  }
  def field_names_module(&convert)
    convert ||= ConvertElementsToFields
    fields = self
    Module.new do |mod|
      const_set 'Fields',
        fields.map(&convert).each_with_index { |f,i|
          f.freeze
          define_method(f) { self[i] }
          define_method("#{f}=") { |val| self[i] = val }
        }.freeze
      unless instance_methods.include? "field_list"
        define_method("field_list") { mod::Fields }
      end
    end
  end
end

The simple case, where we know the list of fields ahead of time, looks like this:

MyFields = %w(name gender email zip).field_names_module

The more complicated case where we don't know the field names/positions ahead of time is almost as easy. Consider a result from a DBI query:

MyFields = result.fetch_fields.field_names_module { |field| field.name }

Still pretty easy, even for the complicated case. Enjoy!

Labels: , ,

2007-02-22

ArrayOfHashes

More often than I might have expected I wind up dealing with arrays of hashes. A lot of this comes from making JSON-RPC calls, incidentally, but that doesn't matter. The important thing is that I kept writing code like this:

values = foo.map{|x|x["bar"]}
...or worse...
some_values = foo.map{|x|x["bar"]}
other_values = foo.map{|x|x["baz"]}

I came to the conclusion that what I really wanted was a way to treat arrays of hashes in a special way, and that means mixing in a module! Here's the ArrayOfHashes module:

module ArrayOfHashes
  def transpose
    hash = {}
    each_with_index { |h,i| h.each { |k,v| (hash[k] ||= [])[i] = v } }
    hash
  end

  def [](key)
    map { |h| h[key] }
  end
end

It's pretty simple, actually. Array#transpose assumes it's an array of arrays, so this just overrides it with the assumption that it's an array of hashes. The result is a hash of arrays, generated in the spirit of the original Array#transpose. Overriding the [] operator is a little bit weirder, but it is (nearly) equivalent to using transpose[] without doing the full transpose.

So now those two examples become...

values = foo.extend(ArrayOfHashes)["bar"]
...and...
hash = foo.extend(ArrayOfHashes).transpose
some_values,other_values = hash["bar"],hash["baz"]
...respectively. Enjoy!

Labels: ,

2007-02-14

Have you seen that key?

This is tiny, but oh, so cool. From time to time I find myself needing a way of checking whether I've seen something. Consider, for example, uniq_by. Here's the implementation of Enumerable#uniq_by I gave previously:

module Enumerable
  def uniq_by
    seen = {}
    select { |v|
      key = yield(v)
      (seen[key]) ? nil : (seen[key] = true)
    }
  end
end
Now we make the "seen" hash a bit smarter:
module Enumerable
  def uniq_by
    seen = Hash.new { |h,k| h[k] = true; false }
    select { |v| !seen[yield(v)] }
  end
end

...and isn't that more concise? It just feels better. Yes, I could swap the true and false in the hash block so I don't need the negation in the #select block, but that has weird semantics. It probably would make more sense to use #reject to avoid the negation, especially since that doesn't force it to return an array:

module Enumerable
  def uniq_by
    seen = Hash.new { |h,k| h[k] = true; false }
    reject { |v| seen[yield(v)] }
  end
end

Enjoy!

Labels: ,

2006-12-30

Randomizing an Array and Other FAQs

This is a quickie, but I'm so incredibly tired of seeing it on the ruby-talk list (and occasionally Rails list) that I'm posting it anyway. It's also (kinda, sorta) a followup to the previous post.

If you need to randomize an array, use the following:

random_array = my_array.sort_by{rand}

Now quit asking about it. Here are some other little FAQs I'm tired of seeing on the lists:

  • Symbols can be thought of as immutable, internalized strings if you like, but strings and symbols are, indeed, different. If you really need to know more you can search the web, since others have covered it in more depth.
  • Hashes are unordered. Deal with it. There is no good reason for a hash to maintain the order of its keys; the purpose of a hash is amortized constant time lookup. If you need to iterate through the hash keys in some order, use #sort or #sort_by before iterating.
  • Rails has a bunch of stuff added to Ruby. This includes things like HashWithIndifferentAccess (which allows the params hash to be accessed by string or symbol) and Symbol#to_proc (which lets you use &:foo in place of {|x|x.foo}). Ruby Facets makes some of that available to normal Ruby. Ruby 2.0 will also have some of it built in.
  • Rails isn't the only thing Ruby is good for. For that matter, it is neither the only Ruby web framework, nor is ActiveRecord the only Ruby ORM.

Phew. Are we done? Anyone else want to post their pet peeve FAQ? Leave a comment. Oh, yeah, almost forgot.

Enjoy!

Updated! See Randomizing an Array Revisited.

Labels: , ,

2006-10-23

Using sort_by instead of sort

Q: What's wrong with this code?

some_collection.sort { |a,b| a.foo <=> b.foo }

A: Efficiency. That block will be called n log n times (expected number of comparison operations involved in a quicksort), which means that the foo method will be called twice as many times. It also isn't terribly readable, since it looks like the block is there to perform some important function, when really it's just defining on what attribute the element are being sorted. The way to avoid this is:

some_collection.sort_by { |a| a.foo }

In Rails, it is also possible to use the idomatic

some_collection.sort_by &:foo

...though this is somewhat less clear and should probably be avoided. The sort_by method calls the block once on each element of the collection and uses it as a surrogate sort value. Instead of foo being called 2n log n times, it is called n times, as is the block. It is also clear that the foo attribute is being used as the value for comparison.

Q: Okay, but what if it's more complicated like:

some_collection.sort do |a,b|
  val = a.foo <=> b.foo
  val = a.bar <=> b.bar if val == 0
  val = a.baz <=> b.baz if val == 0
  val
end

A: That basically says "sort by foo, then bar, then baz." You still can and should use sort_by:

some_collection.sort_by { |a| [ a.foo, a.bar, a.baz ] }

Arrays are compared element-by-element, with the first element being most significant and subsequent elements increasingly insignificant. By providing an array as a surrogate object, the sort precedence works as intended. Enjoy!

Labels: ,

2006-07-20

SymbolicKeyHash

Symbols aren't strings. They aren't even immutable strings. They are symbols. They are also very good keys for hashes, assuming the code building the hash thought so also. It is often the case, however, that hashes received by your code may be keyed by either strings or symbols, or a mixture thereof, and you'd like to be able to handle either case simply and elegantly. The simplest thing to do is to call #extend on the offending hash with the following module:

module SymbolicKeyHash
  def [](key)
    case key
    when Symbol
      include?(key) ? super(key) : super(key.to_s)
    when String
      include?(key) ? super(key) : super(key.to_sym)
    else
      super(key)
    end
  end
end

That's good as far as it goes, but it really isn't nice to mess with the interface of an argument you've been passed. Instead, we rely on a Proxy to wrap it:

def bar(hash)
  hash = Proxy.new(hash)
  hash.extend SymbolicKeyHash
  do_something(hash[:first_thing], hash[:second_thing])
end
Enjoy!

Labels: ,

2006-07-06

Syntax coloring

Someone asked recently about syntax coloring for Ruby code, specifically in the context of a blog. I responded on the list, but I thought I'd share how I create my posts here. First off, I use Firefox when creating a post on Blogger. This is in part because it's a great browser in general, but more specifically so I can use the mozex extension to work on my post in Vim.

Nearly all of my posts include some Ruby code, and it's much nicer to display it with syntax coloring. I used to use Vim's own HTML conversion, but it's slow and it uses explicit styles with colors. Upon hearing about it, I started using the syntax gem in the following Ruby script:

#!/usr/bin/env ruby

unless [1,2].include? ARGV.size
  $stderr.puts "Usage: #{$PROGRAM_NAME} <syntax> [file]"
  exit 1
end

require 'rubygems'
require 'syntax/convertors/html'

convertor = Syntax::Convertors::HTML.for_syntax ARGV.shift

highlighted = convertor.convert(ARGF.read)
highlighted.sub!(/^<pre>/, "<pre class=\"code\">\n")
puts highlighted

To actually write the code I generally open another window in Vim so I can use non-HTML syntax coloring on it and so I can test and debug it in a separate file. When it's ready I paste it into the blog post and run it through the highlighting script above. The actual coloring comes from the page's CSS rules, which you can see by viewing source. If you have any questions on the process, please leave a comment. Enjoy!

Labels: ,

2006-06-07

Ungreedy Regular Expressions in Ruby

I was recently working on a script to condense or pretty-print CSS. Condensing is actually pretty easy, but pretty-printing involved preserving comments while sorting style directives within rules. (For those who aren't familiar with CSS, its comments are delimited by /* and */ just like in C.) Matching comments, particularly multiline comments, is pretty easy as long as you can make your regular expressions ungreedy. The naïve, greedy regex /\/\*.*\*\//m (note the m option at the end, which sets the multiline option for the Regexp) will not stop at just one comment but will match everything from the beginning of the first comment to the end of the last comment, including all the uncommented code in between. This is clearly wrong, and the problem is that * (and +) is greedy (i.e. matches as much text as it can).

If greedy matching is the problem, how do we make it ungreedy? It turns out that Ruby takes a page from Perl regular expressions and whereas * (and +) is the greedy version, *? (and +?) is the ungreedy version. Thus our problem regex becomes /\/\*.*?\*\//m and works as desired.

This may not be quite as significant as previous posts, but it's really handy to know when you need it.

Labels: ,

2006-05-19

Kernel#qualified_const_get

The question of how to get a class by name comes up with some regularity on the ruby-talk and rails mailing lists. The first response is usually to use Object::const_get. The response to that is that it doesn't handle classes within namespaces, e.g. Foo::Bar. I might argue that Object::const_get("Foo::Bar") should do the right thing and retrieve the value of Bar from the Foo class/module, but the fact of the matter is that it does not. Having an itch to scratch, I wrote Kernel#qualified_const_get:

module Kernel
  def qualified_const_get(str)
    path = str.to_s.split('::')
    from_root = path[0].empty?
    if from_root
      from_root = []
      path = path[1..-1]
    else
      start_ns = ((Class === self)||(Module === self)) ? self : self.class
      from_root = start_ns.to_s.split('::')
    end
    until from_root.empty?
      begin
        return (from_root+path).inject(Object) { |ns,name| ns.const_get(name) }
      rescue NameError
        from_root.delete_at(-1)
      end
    end
    path.inject(Object) { |ns,name| ns.const_get(name) }
  end
end

One of the advantages of this usage is that it handles partially qualified constant names. The following does the right thing in both cases:

require 'qualified_const_get'

module Foo
  module Bar
    class Baz
      def initialize
        puts 'You found me!'
      end
    end
  end
  def self.find_it
    klass = qualified_const_get("Bar::Baz")
    klass.new
  end
  module Quux
    def self.find_it
      klass = qualified_const_get("Bar::Baz")
      klass.new
    end
  end
end

Foo::find_it
Foo::Quux::find_it
Enjoy!

Labels: , ,