Taking the guesswork out of iPhone X purchasing with ruby

Eric Turner
Eric Turner’s Blog
9 min readNov 26, 2017

--

https://www.flickr.com/photos/williamhook/26388870449

I’m not exactly what you’d call a patient man.

So when I decided I wanted an iPhone X, I was a bit disappointed by the 2–3 week delivery estimate on Apple’s online store. I didn’t want to wait that long if I could avoid it. So I searched for alternate solutions and it turns out there was indeed another way.

You see, Apple occasionally opens up reservations for same-day pickup of new products at their retail stores. But this doesn’t happen often, so you need to be quick and notice immediately if you want a chance of getting the device you want.

Now, as a programmer, the idea of having to constantly check Apple’s site to see if the stores in Tokyo had stock wasn’t very appealing, so I decided to see if I could take out some of the guesswork involved and automate this process. In short, I set out to create a program that would repeatedly check for iPhone X stock at the Apple stores near me, and notify me if it found any.

Here’s how I went about this using my programming language of choice, ruby.

Step 1 — Find your data source

The first thing I needed to do was find a reliable source of stock data at my chosen Apple stores. So I visited Apple’s website:

This is the page for purchasing an iPhone X. Notice the “Check Availability” link. This is the key to finding the data we need. See, when you enter your zip code here, it will do a search for your chosen iPhone at all Apple Stores near you. For example, if you’re in New York city:

Cool. But this by itself isn’t a great data source, since we’d need some fairly complex HTML/JS scraping to reliably extract the data we want from here.

However, the key to simplifying this data extraction lies in the “Network” tab of our browser’s dev tools:

Bingo. We can see that the site makes an XHR request to the following address:

https://www.apple.com/shop/retail/pickup-message?pl=true&cppart=ATT/US&parts.0=MQAK2LL/A&location=10001

Note that this isn’t the actual link I used, just an example of the endpoint.

And let’s take a look at a subset of the response to this request:

It shows a list of stores in JSON format, with entries per store for the iPhone type we selected (in my case iPhone X 64GB Silver), including whether it’s currently available for pickup or not.

Whenever I do any kind of web scraping, I first look for API endpoints being used in the background, and checking the “XHR” requests under your browser’s “Network” tab is a great way to find them.

Now that we know where to find the data we want, let’s actually write some code!

Step 2 — Set up your project directory

The first thing we need to do is create the basic files for our ruby project. I’m a big believer in conventions, so I used the following hierarchy, inspired by the structure typically used in ruby gems.

The first step is to create a new project directory, which I callediphone_x_checker.

Then within that directory, I created a simple Gemfile, a blank README and a LICENCE.txt file.

In the end the only gem I needed was “rest-client”, so here’s what my Gemfile looked like:

I then ran bundle install to install this gem locally.

Next, I created a lib directory and inside that added a file called iphone_x_checker.rb:

This file is the entryway to the project and just requires all the other files and runs a simple command to begin checking Apple’s site.

You can also add a bin directory with a separate file to run the project, but because this was a quick one-off just for personal use, I didn't add one and instead started the checker by simply running ruby iphone_x_checker.rb.

Now let’s talk about the actual components themselves.

Step 3 — Build out the required components

As you’ve seen briefly in the above code, I created several components in order to accomplish the task at hand. While I believe the names make them fairly self-explanatory, here’s a quick description of each one:

  • Checker - Keeps track of how and when to check Apple's site for the latest stock data.
  • Notifier - Base class for notifying the user (me) when stock is found.
  • Notifiers::EmailNotifier - Concrete class implementing notification via Email (SMTP); it's used in Notifier.
  • Fetcher - Simple class to hit the API endpoint using rest-client and return the raw response data.
  • Parser - Parse the raw response from Fetcher into a ruby hash.
  • Response - Contains the methods for extracting what we actually care about from our parsed response data.
  • Printer - I had some puts statements with similar formatting littered throughout, so I extracted this class to DRY that up somewhat.

So that’s the basic architecture I went with; it might not be perfect, but I think it’s a relatively DRY design, seeing as it uses the Strategy Pattern forNotifiers, which allows me to swap them without affecting unrelated classes. Also the EmailNotifer class is fairly generic, so I could easily re-use this in other projects with only minor changes.

Next, I’d like to dive a bit deeper and show how I actually implemented the various classes. Let’s start with actually fetching the JSON from Apple’s server.

Fetcher

This ended up being very simple; I just imported the excellent rest-client gem, stored the URL in a (frozen) string constant, and added a simple fetch_data method to fetch the data.

It’s not the most generic class ever, seeing as it hardcodes the link for the exact product I want, but it is simple and focused, and it fulfills my use case.

So now we have the data itself, but in the form of a huge JSON string that’s not very much use to us yet. The next step? Let’s parse that data into something we can actually use.

Parser

The Parser class turns out to be another very simple class; it just takes some data, and uses the supplied concrete parser (by default, the JSON class, which I'm categorizing as a parser here since it fulfills my required interface, i.e. it has a parse method).

As you can see, all we need to get a hash version of our data is something like this:

Now we have our data in a form that we can actually work with (a hash). But there are still some things I want to do to this object. In particular, most of the data in this hash is irrelevant to me. All I want to know is whether any of these stores have stock or not, so I need to do some processing of this data. I could do this in one of my existing classes, but I think it makes sense to make a new one. I’ve chosen to call this class “Response”.

Response

This class contains the specifics about how to navigate our parsed data. It knows what stores I’m interested in, and how to extract the things I care about. It gets rid of all the noise and reduces our data to a simple available? interface that is either true or false.

Note that everything else is private, including the data instance variable accessor. As a rule, I don't expose methods or data publicly unless I absolutely have to.

Next let’s talk about how we can actually notify users when this class tells us that yes, the iPhone is available.

Notifier

There are many ways to notify people of things these days. Email, SMS, push notifications etc. Because of this, I decided to write the Notifier class in such a way as to not be coupled to any specific method of notification. So the Notifier class itself is fairly generic:

Notice that we have a notifier attribute here. This might seem a bit weird since we're in the Notifier class, but as I said, I had a good reason for this: it allows us to swap out concrete notification methods easily, without changing the callers of this class. I might’ve been able to name this a bit less confusingly however.

Ultimately this class just calls out to another concrete notifier to send the actual notification, and then sleeps for an hour (since if we do find stock, we don’t want to keep spamming the user; this gives me time to turn off the checker after I’ve successfully purchased the phone).

I decided to notify myself via email since it seemed the simplest, so now let’s look at the EmailNotifier itself.

EmailNotifier

So this is probably the most complicated class in this project; it contains all the logic surrounding creation of an email via SMTP.

It also makes a few assumptions:

  • You’re probably using Gmail
  • You’re just going to send the email to yourself (i.e. you can’t set the sender and receiver to different addresses)

These limit the usefulness of this class a bit, but I am using gmail and wanted the email to be sent from my own account, so this was enough for my use case.

Another thing you’ll notice is that I’m using ENV to access environmental variables. Since we're dealing with things like emails and especially passwords, it's not suitable to store this in the source code itself, which is why I've used environmental variables here. That way you can use your preferred solution to store them securely on the server that runs the code and still keep the source code in version control.

In the Github repo for this project I've included an example of how to run the file if you want to set environmental variables via the command line.

As I mentioned before, one thing I like about this class is how easy it would be to make a bit more generic and break off into a general-purpose SMTP sending library. I may do this at some point, since I often have small projects like this that involve being notified when things happen.

Next I want to dive down into the Printer class, which has already made a few appearances in the above files.

Printer

I wanted to print a message every time the checker failed to find stock, and I wanted it to include a timestamp so I could login to my server at any point and ensure it was still running.

Here’s the code I wrote to accomplish this:

I think the behavior of this class is pretty straightforward, so let’s move on to the class that puts it all together and actually initiates checking Apple’s site.

Checker

Once again we have a class with a very simple public interface: the begin_checking method loops indefinitely, checking the availability of the iPhone X using our Fetcher and the other classes I outlined above. Once again everything that doesn't absolutely need to be exposed for use in other classes is hidden in private attr_readers and methods.

Now getting back to our base iphone_x_checker.rb file, we simply create an instance of this class and tell it to begin checking:

And that’s it! This will check Apple’s site every minute, and email me as soon as it finds that there’s a silver iPhone X 64GB in any of the Apple stores in Tokyo, for in-store pick-up.

Conclusion

So how did the code perform in practice? Well I wrote this code on Thursday, November 23rd, and it ran for about three days without a peep until the fateful morning of Sunday the 26th, when sure enough I got an email at 6:00 AM sharp telling me that the iPhone X was currently available to register for in-store pickup at one of my local Apple stores.

So in the end, I was able to get my new iPhone a little bit earlier thanks to this project. My online order (which I had also made as a way of hedging my bets) still listed a 1–2 week wait time on the day I picked up my new phone in the Apple store.

By the way my code is available on Github, so feel free to check it out!

Huzzah!

--

--