Making a Ruby Gem is Easier than You Think

After using countless Ruby Gems, I’ve finally started making my own. The idea is pretty intimidating, but it actually isn’t so different to making your own scripts or Rails apps since Bundler handles almost everything for you.

To set up all the basics for a Ruby Gem (as well as initialize a git repository for it), simply run

$ bundle gem your_gems_name

from your terminal and you’re off to the races!

This will create (among other things) a gem spec file, which is where you list all your dependencies (if any), as well as information (including version) about your gem.

The real meat of your gem (the logic, etc) will go in /lib/ and should all be namespaced for your gem to avoid conflicts with any classes/methods that your users might have written themselves (or even ones you have written yourself for your own apps).

To install your gem to your local machine to test it out and see if it does what you want it to do, simply run

$ rake install

from your terminal while inside your gem’s directory. If/when you are ready to release your (hopefully functioning) gem to RubyGems.org, run

$ rake release

and there it goes! Be sure to appropriately change the version any time you make changes before releasing again, to avoid breaking the gem for people that may have installed previous versions and written all their code based on that.

I decided to start out simply enough, just to get the gist of everything, and made an API wrapper for the SF Parks API (the API itself is at https://data.sfgov.org/api/views/z76i-7s65) because its API is formatted really weird and is deeply nested and hard to traverse. I took it and navigated all the weirdness and made it into a PORO for ease of use in a Rails app. So now, if anybody wants to use the SF Parks API, they can do so quickly and easily.

This got me all fired up about making my own gems, so I’ve already begun work on a rating gem, since I’ve personally noticed a dearth of rating gems out there. There’s ratyrate, but that requires Devise (which I don’t use), so I thought maybe other people might want a rating gem that does not use Devise. I’ve already written my own rating system, so all that remains is to transfer my existing system into the gem structure so others can install it. Hooray!

Finally Installed Leaf Spy

So I finally installed Leaf Spy, because everybody in the Leaf community has been going on and on about how I need to have it. The reason I had been putting it off for so long is because I needed to buy a blue tooth plug in for my on board computer, and ugh that’s just another step. For that, I picked the Konnwei KW902.

It is a lot of fun, and has been relatively informative, and I can see how it could be helpful for very long trips. For example, I discovered that the state of charge meter seems to tick-down at random intervals:

At “fully charged”, the state of charge was 92%. Makes sense, because I had read that Nissan built in safeguards that don’t actually allow you to charge to 100%. I lost the first bar after driving it down to 82%. Then I lost the next bar at 77%. Then I lost the next bar at 75%. Then I lost the next bar at 66%. It didn’t seem to make a lot of sense. Note: when fully charged, the state of charge meter has 12 bars, so each “bar” disappearing should (ideally)  indicate an ~8% change.

I finally installed LeafSpy
One of my Leaf Spy screens… there’s state of charge, tire pressure, annnnd… a bunch of other information of some sort

So I feel like there are only a few choices for what is going on here:

  1. Leaf Spy’s SOC meter is inaccurate
  2. Nissan’s SOC meter is innacurate
  3. Both SOC meters are innacurate

To be honest, I couldn’t say which is the correct answer, as I only used this app while I was driving home from work, so hardly a long trip pushing it to the limits. You gotta figure, Nissan’s SOC has to be pulling its info from the same computer that Leaf Spy is, so it makes no sense that they would be so different. But, oh well.

Another thing is, Leaf Spy has a very, very homemade feel. Like, very homemade. It is a ginormous pain getting it up and running. I understand that blue tooth pairing is, by definition, a horrific experience but pairing Leaf Spy will make you question all your life’s choices.

I found what works for me, is that I have to change my Leaf Spy port from 1 (which is what I use to listen to audiobooks) to “secured.” Many sources say that changing the port to 16 will work, but for me with my Nexus 5 and my audiobooks, 16 did not work at all. Only “secured” worked. Then, I had to make sure the device is on and I could “see” it in my blue tooth pairing screen on my phone, and then pair it (this part only needs to be done once, the first time). Once paired, I had to turn off my bluetooth, then open Leaf Spy and allow Leaf Spy to turn my blue tooth back on (it only works if I let it do this), and then 70% ish of the time, Leaf Spy pairs and I’m good to go! Sometimes, yes, I go through all the steps and I get bupkis. But, as I said, it is very home made.

I get it, it is a labor of love, and as I understand it is just one guy building and maintaining this app. And it is super cool when it works! But it is a massive pain in the butt to get it going. I haven’t used Leaf Spy on any massive trips yet, but I’ve already taken my Leaf on several long road trips without Leaf Spy – just going on my general feeling for how much further it can go, and I haven’t yet turtled or ran out of juice or anything. My gut method has been working pretty well for me, surprisingly. But, this app and methodology is much beloved by the community, so the next time I road trip I’ll use it to see if it helps.

The main things I learned from Leaf Spy are:

  • My battery has 80% capacity remaining (which I had already guessed based on the mileage I was getting coupled with the battery capacity meter)
  • My tire pressure was low, so I filled them back up (ironically, I had noticed a gradual decline in my mileage for a month or so until I installed Leaf Spy, and I saw that I had low tire pressure. The very next day, my dashboard low tire pressure warning came on. So, Leaf Spy could alert you to the exact tire pressure before this happens, but in my case this didn’t exactly happen)

Error Handling in Rust

There are a lot of things to love about Rust, not least of which are its excellent documentation and package handler (Cargo). But I also really like the error handling.

In Rust, you can choose to handle the error via having the program panic (which will abort the entire program), but this is generally considered something best avoided – especially if you are writing a program that other people will be using.

if n < 1 || n > 10 {
        panic!("Invalid number: {}", n);
    }

For example, if you had this in your code, and you passed in a number lower than n or greater than 10, you would not get a very good error message and it would be annoying.

An exception would be if the error is raised by something that should not happen and therefore indicating an actual bug in the program (and not simply the user typing the wrong thing, etc). In this case, you would want your program to panic and stop everything – because there is something way wrong that you didn’t foresee (and so probably could not have written a specific case for it).

You can unwrap something, which will either return the result of a computation or panic. But this, of course, runs into a lot of the same annoying things as panic.

You can also specify that, if you run into an error, for the program to simply continue. For example:

fn main() {
    println!("Guess the number!");
    println!("Enter your guess:");

    let secret_number = rand::thread_rng().gen_range(1, 101);
    
    loop {
        let mut guess = String::new();

        io::stdin().read_line(&mut guess)
        .expect("Failed to read input");

        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => continue,
        };

        println!("You guessed {}", guess);

        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Higher!"),
            Ordering::Greater => println!("Lower!"),
            Ordering::Equal => {
                println!("You Win!");
                break;    
            }
        }
    }
}

If the person types in something that is a number, then it will be compared to the randomly generated number and told whether they have won or not. But if the person types in something that is not a number, then the the program will continue and the rest of the loop will simply skip and then ask the user to enter another number.

And there are many more ways to handle errors in Rust. You can write error messages for what happens when no arguments were passed when they were expected, or to display a different error if the argument fails to parse (and therefore is not the appropriate type), or to simply proceed as normal if everything checks out:

fn double_arg(mut argv: env::Args) -> Result<i32, String> {
    argv.nth(1)
        .ok_or("Please give at least one argument".to_owned())
        .and_then(|arg| arg.parse::<i32>().map_err(|err| err.to_string()))
        .map(|n| 2 * n)
}

fn main() {
    match double_arg(env::args()) {
        Ok(n) => println!("{}", n),
        Err(err) => println!("Error: {}", err),
    }
}

And there are many more ways to write helpful error messages and to handle the errors in such a way that your program behaves the way it should (which includes panic-ing when needed). This is just another part of what makes Rust so powerful and such a pleasure to work with.