You’re looking at a draft of a chapter from a work in progress, tentatively titled Scripting Mac Applications With Ruby: An AppleScript Alternative, by Matt Neuburg.
Covers rb-appscript 0.6.1. Last revised Jun 23, 2012. All content ©2012 by the author, all rights reserved.


Prev: The Application Object
Next: Commands
Contents

Chapter 5: Properties and Elements

1. Properties

2. The Magic Word get

3. Copies vs. References

4. References as Objects

5. The class_ Property

6. Elements

7. Element Specifiers

7.1. All

7.2. By Index

7.3. By Name

7.4. By ID

7.5. By Position Name

7.6. By Range

7.7. By Relative Position

7.8. By Boolean Test

8. Distribution Over Multiple Internal References

9. The Object Model

10. Shortcut References

11. Canonical References

12. Failed References

13. Assembling References at Runtime

14. Invalid Reference from the Application

The world of a scriptable application, as revealed and made accessible to us through Apple events, is a world of objects. We may imagine a scriptable application as dispensing objects to us; just about any Apple event we send to a scriptable application will need to specify such an object.

Much of the art of scripting a Mac application with Apple events is knowing what objects the application is willing to dispense, and how to specify a desired object. Even if you don’t particularly need an object as a value, you will still need to know how to refer to an object, in order to make your scriptable application do anything. We have already learned how to refer to one of the scriptable application’s objects — its representation of itself, the :application object, which comes to us as an Appscript::Application instance. (It will become clear later in this chapter why I speak of the :application object using a Ruby symbol, a name starting with a colon.) This object, alone, can be made to do a few things, without reference to any other object; we can tell it, for example, to activate, or to quit. But the really interesting stuff doesn’t start happening until we can refer to other objects that the scriptable application is ready to dispense.

Let’s take iTunes as an example. Let’s say my copy of iTunes contains a song called “Broke and Hungry Blues”, and that I’d like to tell iTunes to play it. To do that, I need somehow to refer to the song “Broke and Hungry Blues.” How do I do that? Let’s go further. The song “Broke and Hungry Blues” has an artist. That, too is an object. How can I script iTunes to tell me the artist of the song “Broke and Hungry Blues”? You can see already that anything interesting we want to do with iTunes is going to involve objects beyond the mere :application object. This chapter tells you about the nature of such objects and how to refer to them.

When I speak of an “object” in the world of a scriptable application, this hasn’t much to do with the notion of object-oriented programming, as embodied in a language like Ruby (though of course, because Ruby is object-oriented, if we ask a scriptable application to give us one of its objects, it will arrive as a Ruby object). But a scriptable application’s objects do have certain architectural similarities to Ruby objects. One such similarity is that an object in the world of a scriptable application can have attributes, which are somewhat like a Ruby object’s instance variables. Every attribute is either a property or an element. (In a sense, “attribute” is simply a made-up word intended to embrace the notions of property and element together.) What’s more, it turns out that the way we specify an object is in terms of some other object’s attributes — its properties and elements. So we need to know about properties and elements in order to get anything done in the world of a scriptable application. We need to know about them in order to understand the nature of a scriptable application’s objects, and we need to know about them in order to refer to a scriptable application’s objects. That’s why properties and elements are the subject of this chapter.

Also, an object in the world of a scriptable application has a “class,” though this is only vaguely similar to the Ruby notion of a class. As we’ll discover in this chapter, a scriptable application object’s “class” is little more than a list of what properties and elements it has.

1. Properties

A property is an object attribute that is a single named value. For example, every song in iTunes has an artist, much in the same way as we can imagine creating a Song class in Ruby whose methods refer to an @artist instance variable. Just as we could then get and set a particular Song instance’s @artist value (if the Song class has the appropriate accessors for the @artist instance variable), so too we can get and set a particular song’s artist value in iTunes.

In rb-appscript, the way to refer to a property of an object in the world of a scriptable application is with a method whose name is the name of the property (somewhat analogous to an instance variable accessor). So, for example, if we did have a reference to a particular song in iTunes, and if this reference were in a variable, say, the_song, then we could refer to its artist using an artist method, like this:

the_song.artist

So from now on, when I want to speak of a property of an object, I’ll use the name of the method that you would send to that object using rb-appscript to refer to that property. For example, I’ll speak of an artist property (using that typography), or of a song’s artist.

2. The Magic Word get

We now know how to refer to a property; but how do we obtain that property’s value? The primary way is by sending it the get message. In telling you this, I’m getting a little ahead of myself, because get is a command, and formal discussion of commands doesn’t really start until the next chapter. But get is such a useful thing to say, especially when you’re testing to see whether you’re successfully referring to a property, that we may as well start using it right now.

For example, an :application object itself has properties, many of which can be quite interesting. The iTunes :application object, for one, has many useful properties, such as player_state, which tells whether iTunes is currently playing a song, or paused in the playing of a song, or not playing any song; and current_track, which tells what song is currently playing or paused. But sending, say, the player_state message to iTunes is not, of itself, a particularly useful thing to do:

itu = Appscript.app("iTunes.app")
itu.player_state

Hmmm, the script runs fine, but nothing happens. Perhaps this is because we didn’t ask for any output; let’s try puts:

itu = Appscript.app("iTunes.app")
puts itu.player_state #=> app("/Applications/iTunes.app").player_state

That’s a response, but it isn’t very interesting; basically, we said player_state and rb-appscript has just said player_state right back at us. That’s because we’ve correctly formed a reference to the iTunes :application object’s player_state property, but that’s all we’ve done; we haven’t proceeded to access its value. To do so, we need to send the get command to the property:

itu = Appscript.app("iTunes.app")
p itu.player_state.get #=> :paused

Now we’re cooking with gas! We have actually scripted iTunes to do something useful: we asked it a question and received the answer. So remember the magic word get when you actually want to retrieve and examine a property’s value. I’ll be using it quite a lot throughout the remainder of this chapter.

3. Copies vs. References

When you say get to a scriptable application and you receive a value, that value will fall broadly into one of two very different categories. We can illustrate this difference by contrasting the iTunes :application object’s player_state property value with its current_track property value:

itu = Appscript.app("iTunes.app")
p itu.player_state.get
#=> :paused
puts itu.current_track.get 
#=> app("/Applications/iTunes.app").sources.ID(41).user_playlists.ID(399).file_tracks.ID(412)

Intuitively we can see that these are very different sorts of thing. The value :paused is simple. The value app("/Applications/iTunes.app").sources.ID(41).user_playlists.ID(399).file_tracks.ID(412) looks more like a Ruby expression; it looks like the kind of thing we would say, in Ruby, to a scriptable application. And in fact that’s exactly what it is.

We can perceive the difference more rigorously by asking Ruby for the underlying class of each value:

itu = Appscript.app("iTunes.app")
puts itu.player_state.get.class #=> Symbol
puts itu.current_track.get.class #=> Appscript::Reference

Let’s call values of the first type copies, and values of the second type references. The Ruby class of a copy will vary; it might be String, or Fixnum, or Symbol, or any of a number of various other things. But the Ruby class of a reference will always be Appscript::Reference (except for the :application object, which is an Appscript::Application; but it amounts to the same thing, because Appscript::Application is a subclass of Appscript::Reference).

Here’s what’s really going on. We are communicating with a scriptable application. That scriptable application is populated by a world of objects. For example, in the iTunes world, there are playlists and songs. In the Finder world, there are files and folders. When we say get to a scriptable application, the scriptable application cannot actually send back to us any of its own objects. It can’t even send a pointer to any of its own objects. The two worlds — our world, and the world of the scriptable application — are separate and are not synchronized with each other.

So the scriptable application uses two strategies to send us a value:

4. References as Objects

A reference that you form is itself a Ruby object. So you can form a reference now, assign it to a variable, and then use the reference later. You could use it by sending it a message that forms a further reference; or you could use it by sending it a command, such as get, that causes an Apple event to be sent to the scriptable application. It’s important not to confuse those two uses of a reference, because you don’t want to send an Apple event unnecessarily.

Here’s an example of forming a reference, assigning it to a variable, and then using the reference later:

itu = Appscript.app("iTunes.app")
cur = itu.current_track # now cur is an Appscript::Reference
 # ... time passes
puts cur.name.get #=> Broke and Hungry Blues

Only one Apple event was sent, in the last line; merely forming the reference, in the second line, doesn’t send an Apple event. Similarly, we could have done it this way:

itu = Appscript.app("iTunes.app")
curnam = itu.current_track.name # now curname is an Appscript::Reference
 # ... time passes
puts curnam.get #=> Broke and Hungry Blues

Again, no Apple event was sent until the last line, when we said get, because that’s a command.

What you should not do absent-mindedly is this:

itu = Appscript.app("iTunes.app")
cur = itu.current_track.get # we sent an Apple event
 # ... time passes
puts cur.name.get #=> Broke and Hungry Blues

That code works — we did learn the name of the current track — but we sent two Apple events where one would have done. This is bad practice if done accidentally.

On the other hand, you might do it intentionally. If you want to capture a reference to the current track because you’re going to be interested later in what the name of the current track is now, then maybe you’d better use get after all. Because, you see, objects in the scriptable application world can change. If you wait until later to find out about the current track, there might be a different current track. So if you want to talk later about what is now the current track, then you do want to get the current track now and save in a variable the object reference that the scriptable application gives you. In fact, maybe you’d better get the name right now! If you postpone getting the name, you might find that by the time you come to ask for it, your reference is useless because the track has been deleted! (Oh, it’s a slippery world, the world of a scriptable application.)

5. The class_ Property

We have seen that, in our Ruby world, a value that we obtain from a scriptable application with get might be something like a String, or a Symbol, or an Appscript::Reference. That is its Ruby class. But in the scriptable application’s world, things have classes too. It can often be useful to know what class the scriptable application thinks a value is. To distinguish between the Ruby notion of class and the scriptable application’s notion, we’ll use the term class_ to refer to the latter.

A class_ is not quite the same as the Ruby notion of a class. In the scriptable application’s world, there are not really classes and instances the way there are in a truly object-oriented medium like Ruby. In fact, class_ is merely a property of an object in the scriptable application’s world; in a way, it’s just a name. However, it’s a bit more than this: it’s a name plus a promise. The promise is that if two values have the same class_, they have the same attributes. (Not the same attribute values; the same attribute names.) Properties are attributes, so this means they have the same properties. (Not the same property values; the same property names.)

So, if you know the properties of a class_, and if you have a reference to an object of that class_, you now know what that object’s properties are. That’s very important, because it tells you some things you can do with that object. For example:

itu = Appscript.app("iTunes.app")
p itu.current_track.class_.get #=> :file_track

So, the current track in iTunes is at this moment a :file_track object; its class_ is :file_track.

Observe that the class_ name is reported to us a symbol. This is why I refer in this chapter to “the :application object”; what I mean is, the scriptable application’s single object whose class_ is reported as :application.

Now, I happen to know something about :file_track objects: I know, for example, that a :file_track object has a name property. Let’s prove it:

itu = Appscript.app("iTunes.app")
puts itu.current_track.name.get #=> Broke and Hungry Blues

It worked! I was right. Of course, I’ve already done this several times already in this chapter, so you’re not surprised that it works. But you should be asking yourself: how did I know that a :file_track object has a name property? The iTunes dictionary told me so. (Remember, I said in Chapter 3 that the dictionary was human-readable. This is the kind of information that the dictionary is good at providing to human beings. I’ll discuss the dictionary in detail in a later chapter.)

Another similarity between a class_ and a Ruby class is that one class_ can inherit from another. For example, the current_track need not be a :file_track; it might be a :URL_track, and there are other possibilities as well. But all such possibilities have certain things in common, and this commonality is expressed by the fact that these classes all inherit from the :track class. The current_track property, if it has a value, will be a :track, meaning a :track or some subclass thereof.

The notion of inheritance here is extremely simple-minded; what’s inherited is, in fact, exactly the list of properties. For example, a :file_track has a name property because it inherits this fact from :track. But there is also a difference between a :file_track and a :track (if there weren’t, they would be effectively the same class_). A :file_track has a location property (pointing to the file); a :track doesn’t. A :URL_track, on the other hand, instead of a location property, has an address property (the URL).

Figure 5–1 uses these facts to suggest the relationship between class_, properties, and inheritance.

image

Figure 5–1

Unfortunately, the scriptable application has very limited introspective ability; so, unlike Ruby, you can’t quiz an object in the scriptable application’s world as to its inheritance. You can ask the current_track for its class_ and learn that it is a :file_track, but you cannot then ask it for its superclass and learn that this is :track; you have to know in some other way that :file_track inherits from :track. That other way is, of course, the dictionary.

The definition of a class_ whose objects are represented to us as a simple value, such as a string, is basically universal across all scriptable applications. But in the case of a class_ whose objects are represented to us as an Appscript::Reference, every scriptable application is free to define differently what properties objects of that class_ will have (and how that class_ fits into the inheritance structure). For example, :item is a class_ in iTunes, the Finder, and BBEdit, but it’s different in each. This is just one of the many things that makes scripting Mac applications tricky.

6. Elements

An element is an object attribute that is an object reference specified in terms of its class_. Think of the object as being endowed with arrays of object references, where all the objects referred to in each array are of the same class_ (or subclasses of the same class_). Then we can use the name of the class_ to refer to such an array; actually, in rb-appscript, we use a method whose name is the plural of the class_.

So, for example, the iTunes :application object has :playlist elements. This means we can think of the iTunes :application object as having an array of :playlist object references, and we can refer to this array by sending the iTunes :application object the playlists message.

itu = Appscript.app("itunes.app")
p itu.playlists.get

The output here is an array of object references; I’ll just show the start of the array (and the results on your machine will, of course, be somewhat different):

[app("/Applications/iTunes.app").sources.ID(41).library_playlists.ID(231),
app("/Applications/iTunes.app").sources.ID(41).user_playlists.ID(399),
app("/Applications/iTunes.app").sources.ID(41).user_playlists.ID(444), 
...]

In iTunes, :library_playlist is a subclass of :playlist, and :user_playlist is another subclass of :playlist. So the iTunes :application object is holding on to a bunch of references to objects that are :playlist objects (meaning :playlist and its subclasses), so when we ask for its playlists, we’re talking about the array of those references. Not surprisingly, we could also obtain some of those very same references as part of a different array, by using a subclass name plural, such as library_playlists:

itu = Appscript.app("itunes.app")
p itu.library_playlists.get
#=> [app("/Applications/iTunes.app").sources.ID(41).library_playlists.ID(231)]

That was a much shorter array, consisting of just one item — a reference to the very same playlist that was the first item of the playlists array.

As with properties, so with elements, the way we know what kinds of element an object has is chiefly because we know something about the object’s class_. (And we know it by consulting the dictionary.) But of course we don’t know beforehand exactly what elements of each kind an object has; to find that out, we have to ask the scriptable application. The answer depends on the situation in the scriptable application at the moment. My copy of iTunes has the particular playlists it has because those are playlists I’ve given it; your copy has a different set of playlists (and my copy could have a different set of playlists a few minutes from now, because I can create or delete a playlist).

The relationship between an object and its elements is often thought of as “has” or “contains”: iTunes (the application) “has” playlists, a playlist “contains” tracks. But perhaps it would be better expressed as “might have” or “can contain”, since at a given moment an object’s array of elements for a certain class_ might be empty. A playlist might contain no tracks, for example.

7. Element Specifiers

We’ve just seen that the iTunes :application object has many :playlist elements, and that we can get an array of references to these with the playlists method. But how can we refer to a particular :playlist element? To do so, we need to form an element specifier. There are actually eight different forms of element specifier.

7.1. All

The method referring to an entire element array is the plural of the class_ name. To obtain this array as a Ruby array of references to all the elements, send it the get message. That’s what we did in the examples in the previous section:

itu = Appscript.app("itunes.app")
p itu.library_playlists.get
#=> [app("/Applications/iTunes.app").sources.ID(41).library_playlists.ID(231)]

7.2. By Index

The items in an element array are numbered sequentially, starting at 1. (Rubyists, take note: in the scriptable application world, numbering starts with 1, not 0.) This number is called the item’s index. The syntax for referring to an element by index is elements[n], where what’s in brackets is an integer. So, for example:

itu = Appscript.app("itunes.app")
p itu.playlists[1].get
#=> app("/Applications/iTunes.app").sources.ID(41).library_playlists.ID(231)

Okay, now pretend I’m jumping up and down, waving my arms, and screaming: the [1] in that example is not the Ruby Array item accessor. The playlists method doesn’t return an array; it returns a reference (an Appscript::Reference). The above example doesn’t fetch the entire array of playlists and then get item 1 of that array; it asks the iTunes :application object for just one playlist, namely, the first playlist in its (internal) array. And that’s a very good thing. It can be time-consuming and foolish to ask a scriptable application for an entire array of references when all you want is one particular reference.

If we did want to fetch the entire array, we would use get on the element array itself (the previous type of element specifier). Then we would have a Ruby array, and its first item is numbered 0:

itu = Appscript.app("itunes.app")
p itu.playlists.get[0]
#=> app("/Applications/iTunes.app").sources.ID(41).library_playlists.ID(231)

Be very, very sure you understand the difference between that example and the previous one. They do very different things, even though the output is the same.

As in Ruby, the last item in an element array can be referred to with index -1, the next-to-last item can be referred to with index -2, and so on. Could Ruby have adopted this feature from AppleScript? (I don’t think so.)

The index of an object can change in real time. For example, among windows, windows[1] is usually the frontmost window. If you bring a window to the front (or if the user does), its index may change. Similarly, among the files in a folder in the Finder, index order is alphabetical display order; if you change a file’s name (or if the user does), its index can change. This fact is the basis of many bugs in beginners’ scripts.

7.3. By Name

Objects in an element array usually have names. The syntax for referring to an element by name is elements["name"], where what’s in brackets is a string. So, for example:

itu = Appscript.app("itunes.app")
p itu.playlists["blindlemon"].get
#=> app("/Applications/iTunes.app").sources.ID(41).user_playlists.ID(526)

Objects nearly always have a name property, whose value is the same as the name used to specify the object as an element:

itu = Appscript.app("itunes.app")
p itu.playlists["blindlemon"].name.get #=> blindlemon

Unlike Ruby, string comparison in the scriptable application world is generally not case sensitive, so the case of the name string shouldn’t matter.

7.4. By ID

Objects in an element array may have a unique identifier called an ID, which is usually a number. When they do, the syntax for referring to an element by ID is elements.ID(n), where the parameter of the ID method is the ID value. For example, we already know that on my machine the “blindlemon” playlist has ID value 526:

itu = Appscript.app("itunes.app")
p itu.playlists.ID(526).get
#=> app("/Applications/iTunes.app").sources.ID(41).user_playlists.ID(526)

And just in case you’re in doubt that it’s the same playlist, we can ask for its name:

itu = Appscript.app("itunes.app")
puts itu.playlists.ID(526).name.get #=> blindlemon

Objects that can be specified by ID will typically have an id_ property that lets you learn the object’s ID value:

itu = Appscript.app("itunes.app")
puts itu.playlists["blindlemon"].id_.get #=> 526

An object’s ID does not change in real time, the way its name and index can; therein lies much of its value to the programmer. (But an object’s ID might be different if you quit the scriptable application, start it up again, and obtain a reference to the “same” object. For example, if I quit iTunes today, then the “blindlemon” playlist might not have ID value 526 when I start up iTunes tomorrow.)

7.5. By Position Name

There are a few named positions within the element array that can be used to specify an element:

7.6. By Range

Sometimes an array of elements positioned contiguously within an element array may be specified by giving specifiers for the first and last, separated by comma, like this: elements[m,n]. The values in square brackets are typically index numbers, but some applications are more generous and allow you to use a name. Some scriptable applications are averse to this mode of element specification, and iTunes seems to be one of them, so I’ll use the Finder instead:

f = Appscript.app("Finder.app")
p f.files[1,2].get

And here’s the result:

[app("/System/Library/CoreServices/Finder.app").desktop.document_files["aha.rtf"],
app("/System/Library/CoreServices/Finder.app").desktop.document_files["ahoy.rtf"]]

The Finder is one of those generous applications that let us use names instead of index values; here, I’ll use a number and a name:

f = Appscript.app("Finder.app")
p f.files[1,"ahoy.rtf"].get # (same result as before)

(There is another way of getting elements by range, where the range is marked off by element specifiers of a class_ that isn’t the element class_ we’re after. But it is so rarely implemented by scriptable applications that I’m not going to talk about it.)

7.7. By Relative Position

It may be possible to specify an element in terms of its preceding or succeeding something else. The syntax here is to chain to an object reference the next or previous method, which takes the name of a class_ (a symbol) as its parameter.

If what an element precedes or succeeds is another element of the same class_, and if this is specified by index, this mode of element specification is all but superfluous, since we need only increment or decrement the index:

f = Appscript.app("Finder")
puts f.files[1].next(:file).get
#=> app("../Finder.app").desktop.document_files["ahoy.rtf"]
puts f.files[2].get
#=> app("../Finder.app").desktop.document_files["ahoy.rtf"]

However, element specification by relative position starts to look a bit more interesting when the initial object is specified in some other way, such as by name:

f = Appscript.app("Finder")
puts f.files["aha.rtf"].next(:file).get
#=> ...document_files["ahoy.rtf"]

Or, the initial object might be specified as a property:

f = Appscript.app("finder")
puts f.home.next(:folder).get
#=> app("...Finder.app").startup_disk.folders["Users"].folders["otheruser"]

Things really start to look interesting when the initial object and the element we’re after have a different class_ from one another:

f = Appscript.app("Finder")
puts f.folders[1].next(:file).get
#=> ...document_files["aha.rtf"]

What’s going on in that example? In the Finder, on the desktop, I’ve got both folders and files, interspersed. I have a folder called “aaargh”. This precedes the file “aha.rtf”, because things in the Finder have a natural order, alphabetical order, even when they are of different types. So “aha.rtf” is the first file after the first folder. (It’s rather like the way you’d specify Easter if the calendar were a scriptable application: vernal_equinox.next(:full_moon).next(:sunday).)

An application that implements relative element specifications in a particularly useful way is BBEdit. In BBEdit, every text-element class (such as :character and :word) has :insertion_point elements, which are the positions between the characters. Relative position is a very good way to specify an :insertion_point, which you can then use to insert text at that point. For example, suppose the frontmost BBEdit document starts with “This is a test.” Then:

bb = Appscript.app("BBEdit.app")
bb.documents[1].words[4].previous(:insertion_point).contents.set("great ")

Now the document starts with “This is a great test.” (We haven’t come to the set command yet, but what a great test.)

7.8. By Boolean Test

We come at last to one of the most powerful and interesting ways of specifying elements. It may be possible to describe the desired element(s) in terms of a boolean test — that is, those elements of which a certain condition is true.

To see why this is powerful, let’s consider such a test. In iTunes there are tracks, and a track can have an artist. Imagine that we want the tracks whose artist is Blind Lemon Jefferson. Now, obviously we could just interrogate all the tracks in succession: “How many tracks are there? 11437? Fine. Tell me the artist of track 1. (We look to see if it’s Blind Lemon Jefferson.) Now tell me the artist of track 2. (We look to see if it’s Blind Lemon Jefferson.) Now tell me the artist of track 3…” This is tremendously inefficient on a number of grounds, not least the huge number of Apple events being sent. With a boolean test specifier, we send just one Apple event describing the test, and iTunes performs that test, internally, picking out just the requested elements for us.

Clearly the power of this approach has its limits. The target application must implement the test we request, and the test must be within the power of Apple events to express. It is not hard to bang up against these limits. For example, if our test would involve matching a string against a regular expression, we’re out of luck, because Apple events (and most scriptable applications) know nothing of regular expressions; so in that case, we really might need another approach (and I’ll show such an approach in examples later in this book). But Apple events can express a surprisingly wide range of conditions, and a boolean test specifier can be an elegant time-saver.

The syntax of a boolean test is elements[condition], where what’s in brackets has two essential parts:

Let’s demonstrate by performing our test for tracks whose artist is Blind Lemon Jefferson. The tracks must be specified as elements of some playlist; I’ll use the Library playlist, thus effectively testing against all tracks.

itu = Appscript.app("iTunes.app")
puts itu.playlists["Library"].tracks[Appscript.its.artist.eq("Blind Lemon Jefferson")].get

The result is an array of references to five tracks.

Having to say Appscript.its at the start of every boolean test gets old fast. One solution is to include Appscript, but I don’t like sullying the global namespace unnecessarily, so I prefer to define a synonym if I’m going to be doing any boolean tests. Here’s a rewrite of that same code; it takes more lines, but the boolean test itself is a lot more compact and readable:

itu = Appscript.app("iTunes.app")
whose = Appscript.its
blj = "Blind Lemon Jefferson"
puts itu.playlists["Library"].tracks[whose.artist.eq(blj)].get

Optionally, a boolean test may be extended by any of the boolean operators and, or, and not. These are methods sent to the test as a whole. The first two, and and or, take as parameter one or more additional tests. These tests must be complete; that is, they themselves must effectively start with Appscript.its. Here, I’ll ask for tracks whose artist is Blind Lemon Jefferson and whose title (name) contains “Blues”:

itu = Appscript.app("iTunes.app")
whose = Appscript.its
blj = "Blind Lemon Jefferson"
lib = itu.playlists["Library"]
puts lib.tracks[whose.artist.eq(blj).and(whose.name.contains("Blues"))].get

The result is an array of references to three tracks (Blind Lemon Jefferson did have a propensity for songs with “Blues” in their title).

As with name specifiers, so too string comparisons in a boolean test are generally not case-sensitive.

8. Distribution Over Multiple Internal References

Scriptable applications will often allow an operation to be distributed over multiple references that have been gathered internally. A typical such operation is referring to a property. So, suppose you use an element specifier that can result in multiple references. And suppose what you really want to talk about is not those references, but some property of each of those references. In many cases, you can simply send the method naming that property directly to the element specifier.

So let’s say what I really want to know is the name of every Blind Lemon Jefferson song whose title contains “Blues”:

itu = Appscript.app("iTunes.app")
whose = Appscript.its
blj = "Blind Lemon Jefferson"
lib = itu.playlists["Library"]
p lib.tracks[whose.artist.eq(blj).and(whose.name.contains("Blues"))].name.get
#=> ["Match Box Blues", "Broke and Hungry Blues", "Hangman's Blues"]

It will be readily seen that this is extremely cool and convenient. We’ve saved time and we’ve reduced a potential four Apple events to just one. A common technique is to ask for a list of things that can be used later to form element specifiers. For example:

itu = Appscript.app("iTunes.app")
playlist_names = itu.playlists.name.get

Now I have an array of the names of all the playlists. If those names are unique, I can use them later to do something with each playlist, using each name to form a playlists element specifier.

Another common technique is to fetch values for more than one property and associate them. The association works because the underlying array of multiple internal references is the same array, so the properties arrive in the same order. Forming such an association is particularly easy in Ruby, thanks to its zip Array method:

itu = Appscript.app("iTunes.app")
trax = itu.playlists["Library"].tracks
arr = trax.name.get.zip(trax.artist.get)

After that, arr is something like this:

[["Recuerdos de la Alhambra Tarrega", "Andres Segovia"], 
["Match Box Blues", "Blind Lemon Jefferson"], 
["Broke and Hungry Blues", "Blind Lemon Jefferson"],
...]

In some cases you may be able to refer to elements of multiple references. For example:

itu = Appscript.app("iTunes.app")
p itu.playlists.tracks.get

The result is an array of arrays, each inner array consisting of references to each of the tracks of one playlist. That’s a tremendous amount of organized information resulting from a single Apple event. The possibilities boggle the mind.

(Also, in some cases, multiple internal references can be used as a command parameter; we’ll see examples in a later chapter.)

9. The Object Model

We have now basically discussed all the ways of referring to an object that a scriptable application is willing to dispense. Furthermore, we’ve seen that all references begin ultimately with the :application object (represented by an Appscript::Application instance). So if there’s an object in a scriptable application’s world, and if the scriptable application wants to make this object available to scripters, the scriptable application had better make it possible to reach this object via a chain of references starting at the :application object.

Since elements describe a potential one-to-many containership relationship, we can loosely imagine successive containers and their elements as arranged in a tree structure. The chain of references reaches a desired object by traversing this tree. This tree arrangement of the application’s objects is called its object model.

Figure 5–2 shows a tiny part of the object model for one user’s copy of iTunes. The diagram shows elements as class_ names followed by specifiers for some particular elements. In real life, each iTunes class_ has many more elements, and the tree is much more elaborate.

image

Figure 5–2

In Figure 5–2, one :application property, current_track, is shown in order to point out that although the object model is primarily about elements, a property can provide an alternate way of accessing an object. In iTunes, another example is that the :application object has :browser_window elements, representing the primary player window; a browers window’s view property is the playlist currently displayed in the window, so this is a completely different way to access a playlist.

Also, some objects can be referred to only by way of a property; for example, the Finder :application object has a Finder_preferences property whose value is the application’s single :preferences object; no object in the Finder’s object model has :preferences as an element.

Different elements, too, can provide alternate paths through the tree to refer to an object. We’ve already seen, for example, that a :playlist has :file_track elements, :file_track being a subclass of :track; in Figure 5–2, both Beethoven tracks are in fact :file_tracks, so we could have used playlists["Beethoven"].file_tracks to refer to them.

10. Shortcut References

Sometimes an application will permit you to omit part of the chain of elements along the path towards specifying an object. Actually, I’ve been using this feature throughout this chapter, and you may have been wondering about it. For example, in iTunes, we can say:

itu = Appscript.app("iTunes.app")
puts itu.playlists["library"].tracks[1].get
#=> app("/Applications/iTunes.app").sources.ID(41).library_playlists.ID(231).file_tracks.ID(261)

If you look in the iTunes dictionary, the :application object has no :playlist elements. Yet we can successfully say itu.playlists, with no intermediate sources element. The reason is that iTunes permits us to use a shortcut; when we say itu.playlists, iTunes assumes that we mean itu.sources["library"].playlists. This makes perfect sense, because the other :source element, itu.sources["radio"], has no playlists! So this is a reasonable convenience for iTunes to offer us. The fact that it is undocumented in the dictionary, however, is annoying; the shortcut can be discovered only by experimentation.

Similarly, this works in the Finder:

f = Appscript.app("Finder.app")
puts f.files[1].get
#=> app("/System/Library/CoreServices/Finder.app").desktop.document_files["aha.rtf"]

The :application object in the Finder dictionary does have :file elements, but it remains an undocumented mystery what this could possibly mean; files can only live in a folder, or at the top level of a volume (a disk). Yet we can say f.files anyway, and when we do, we discover that the Finder assumes we mean “on the desktop” (expressed here through the :application object’s desktop property). Again, this is a lovely convenience, but some documentation would have been nice. You can expect to encounter similar shortcuts across the object model in many other scriptable applications as you experiment.

A different sort of shortcut sometimes occurs when an element specifier points to a stretch of text (in a word-processing application, for instance). Consider this example of scripting TextEdit:

te = Appscript.app("TextEdit.app")
puts te.documents[1].words[4].get #=> test

TextEdit didn’t return an object reference at all; it extracted some actual text and returned it. There is furious debate in the scripting community as to whether this is proper behavior; personally, I think that it is not (and that TextEdit’s object model and behavior for referring to text is just about the worst in the universe). Contrast BBEdit:

bb = Appscript.app("BBEdit.app")
puts bb.documents[1].words[4].get 
#=> app("...BBEdit.app").text_documents.first.characters[11, 14]

That, at least is a true reference. To obtain the text itself requires a further step:

bb = Appscript.app("BBEdit.app")
puts bb.documents[1].words[4].contents.get #=> test

In a sense, then, TextEdit is shortcutting from a stretch of text to the text itself.

11. Canonical References

It can hardly have escaped your attention that in examples throughout this chapter, when a scriptable application is asked for a reference, it often provides it in a form quite different from that of the request. Here’s an example:

itu = Appscript.app("iTunes.app")
puts itu.playlists["library"].tracks[1].get
#=> app("/Applications/iTunes.app").sources.ID(41).library_playlists.ID(231).file_tracks.ID(261)

We specified a playlist by name and a track by number, and omitted the sources step altogether; what we got back included the sources step, the playlists and tracks have been resolved to the actual subclasses of the objects specified, and all element specifiers are by ID. Here’s another example:

f = Appscript.app("Finder.app")
puts f.files[1].get
#=> app("/System/Library/CoreServices/Finder.app").desktop.document_files["aha.rtf"]

We omitted the desktop step, and specified our file by index; what we got back includes the desktop step, files has been resolved to the actual subclass of the object specified, and the element specifier is by name. Here’s an even more extreme example in the Finder:

f = Appscript.app("Finder")
puts f.files["aha.rtf"].next(:file).get
#=> app("...Finder.app").startup_disk.folders["Users"].folders["mattneub"].folders["Desktop"].document_files["ahoy.rtf"]

Now the Finder starts all the way at the top of the folder hierarchy, with the startup_disk property, and works its way down the folder hierarchy with name specifiers to reach the object in question.

There’s no particular lesson here (and, as we’ve just seen, no particular consistency). When we get an object reference from a scriptable application, it can couch that reference in any form it likes. It certainly need not be the same as the form we supplied to start with, so we shouldn’t be surprised. What we can assume is that what the scriptable application gives us is a canonical reference, that is, a reference that we could reliably use to specify the same object. In iTunes, for example, two tracks in a playlist might have the same title, which is the track’s name property; so iTunes uses an ID specifier instead. In the Finder, however, two items in the same folder cannot have the same name, so the Finder uses name specifiers.

12. Failed References

To a Ruby programmer, exceptions are exceptional. But in the world of scriptable applications, it is quite common to see an exception raised over the most trivial offense. Such an exception, like any Ruby exception, must be handled if it is not to percolate up to top level and cause your program to terminate.

A good example is the iTunes :application object’s current_track property. If you ask for this property’s value and there is no current track, iTunes responds with an exception.

itu = Appscript.app("iTunes.app")
puts itu.current_track.get
#=> Appscript::CommandError: CommandError OSERROR: -1731 MESSAGE: Unknown object type.
#=> Appscript::CommandError: CommandError OSERROR: -1728 MESSAGE: Can't get reference.

I display two different possible error messages, because different versions of iTunes raise different exceptions under these circumstances. But the important thing is that these are valid error messages, being passed on to you by rb-appscript from iTunes itself (the Appscript::CommandError class designation tells you so), and that iTunes evidently regards raising an exception as a perfectly reasonable way of saying “There is no current track”. A Rubyist would expect a nil result; the world of scripting applications does have something parallel, :null, but I have never once seen it used in all my years of scripting. Another alternative is :missing_value, and mercifully, scriptable applications are increasingly using this; but exceptions remain common.

The takeaway message is that you must be prepared for exceptions. In iTunes, the mere query as to whether there is a current track in iTunes can cause an exception. So your program must be constructed accordingly; don’t even mention the current track without supplying exception-handling. Here’s a sound bit of programming for reporting the name of the current track.

itu = Appscript.app("iTunes.app")
begin
  name = itu.current_track.name.get
rescue Appscript::CommandError
  puts "There is no current track."
else
  puts name
end

13. Assembling References at Runtime

Let’s say that part of a reference is not known until your script runs. For example, while scripting the Finder, you might want a list of all the files in a folder or all the folders in that folder, but the script itself is to decide which:

f = Appscript.app("Finder")
which = "files" # actual logic of choice omitted here
case which
when "files"
  p f.folders[1].files.name.get
when "folders"
  p f.folders[1].folders.name.get
end

This seems wasteful and error-prone. The case statement distinguishes two references that are nearly identical:

f.folders[1].which.name.get # pseudo-code!

The only thing that differs is which. It would be far more elegant somehow to set which to "files" or "folders" and resolve it at runtime:

f = Appscript.app("Finder")
which = "files" # actual logic of choice omitted here
p f.folders[1].which.name.get # pseudo-code!

That isn’t real code, but how can we make it real? One way is to take advantage of Ruby’s send method, which takes a string or symbol denoting the method that is to be called. This works because in rb-appscript, everything you say as you construct a reference is a method. So:

which = "folders" # actual logic of choice omitted here
p f.folders[1].send(which).name.get

However, this breaks down if the dynamically formed partial reference consists of more than a single method. For example:

f = Appscript.app("Finder")
which = "files.name" # actual logic of choice omitted here
p f.folders[1].which.get # pseudo-code!

How can we implement something like that? Poking around in rb-appscript’s source code, I think I’ve come up with a way, using some lower-level AEM methods. You’ll need to add a method to Appscript::Reference, as follows:

class Appscript::Reference
  def append(which)
    collector = AEMReference::CollectComparable.new
    which.AS_aem_reference.AEM_resolve(collector)
    aemref = self.AS_aem_reference
    collector.result[1..-1].each do |command, args|
      aemref = aemref.send(command, args)
    end
    Appscript::Reference.new(self.AS_app_data, aemref)
  end
end

And here’s how to use it:

f = Appscript.app("Finder")
refbase = f.folders[1]
refpartial = f.files.name # must say "files.name" to something
ref = refbase.append(refpartial) # f.folders[1].files.name

The third line contains a necessary trick. As the comment points out, we must form our second, partial reference in the same way as we would form any Appscript::Reference: we must start with an Appscript::Reference or Appscript::Application instance and send method calls to it. Therefore we start with our reference to the Finder, where rb-appscript will permit us to use legal Finder terms like files. The append method strips out that Finder reference (that’s what result[1..-1] does) before appending the second reference to the first.

The result of append is a new Appscript::Reference instance, and you can now proceed to send it further methods:

p ref.get #=> ["index.html"]

14. Invalid Reference from the Application

In a very small number of cases (only one is known, actually) an application may return a canonical reference that it cannot itself accept later on:

em = Appscript.app("Expression Media")
w = em.windows[1].get
itms = w.media_items.get
#=> Appscript::CommandError: CommandError OSERROR: -1703 MESSAGE: Some data was the wrong type

The symptom is that we get an object reference and then later cannot reuse it. The solution is to switch off rb-appscript’s internal object caching, like this:

em = Appscript.app("Expression Media")
em.AS_app_data.dont_cache_unpacked_specifiers # switch off object caching
w = em.windows[1].get
itms = w.media_items.get # works fine

What’s happening here is that, behind the scenes, instead of sending back to Expression Media the very same object reference that it sent us, rb-appscript is deconstructing the object reference and reconstructing it in a valid form. This takes a little longer but it fixes the problem.


Prev: The Application Object
Next: Commands
Contents

You’re looking at a draft of a chapter from a work in progress, tentatively titled Scripting Mac Applications With Ruby: An AppleScript Alternative, by Matt Neuburg.
Covers rb-appscript 0.6.1. Last revised Jun 23, 2012. All content ©2012 by the author, all rights reserved.

This book took time and effort to write, and no traditional publisher would accept it. If it has been useful to you, please consider a small donation to my PayPal account (matt at tidbits dot com). Thanks!