DEV Community

Discussion on: 5 Things I Learned From Creating My First Ruby Gem From Scratch

Collapse
 
joshcheek profile image
Josh Cheek

Congrats on your gem!

#1 I 1000% agree! I use different words, I usually call what you did a "goal" or an "interface", but I'm known to have emotional issues with the word "plan". This sentence is the one one where I knew we were on the same page, this is definitely how I start most things:

I wrote my plan by imagining how a user would be interacting with the program and exactly what the program would do based on the user’s actions (e.g. Choosing “search for a recipe” would prompt the user to input a keyword, which would then put out a list of recipes that they could choose from).

#2 This can be super useful, especially as you work in a language you're not as familiar with. When I hit a hard JS problem that had me stuck for several days, I was exasperated and thinking "is this problem really so hard?!?" and then wrote it in Ruby, a language I know well, and it only took an hour! Then I translated my solution into JavaScrpit, and it worked. Pseudocode is a great way to remove the distracting details and let you think through the problem. I will say that the better you get at a language, the easier it becomes to think in that language.

#3 Okay, I love this one too. I initially liked this one more than #1, but after reading #1 more thoroughly, I'll place this one second. The less confident you are in a language/domain, the more important this is (both small steps and fake data). The harder your problem, the more important the "fake data" idea is. I wrote an entire gem to make it easy to explore small ideas from problems I'm working on, in small isolated contexts with simple fake data. My gists are full of conclusions I've reached from this step. IMO, this is the one step that most devs don't do enough, even seniors.

#4 There's a thresh hold this one, it's definitely a way to address a problematic end of a pendulum, but if you follow it too far, it can have its own issues. The pain from the other end of this pendulum might be felt when you apply it to a simple script and find it just ballooning.

#5 Aye, reflection is so valuable!

Bonus Lol, I have too many opinions on this one >.< Given you've got the aspiration to do it, and your likelihood to reflect (ie b/c of #5), let me suggest that when you decide to try it out, you try it in two flavours. The first flavour is to only test at the highest level of abstraction, ie whatever you wrote down in #1. People might call this "integration testing" or "acceptance testing" or "behaviour driven development". It will show you one end of the pendulum of testing, the constraint to meet is that you only depend on the highest level interface. And the second flavour is to only do what people call "unit testing" or "test driven development", and the idea there is that you follow your #4, and then its easy to verify that thing works. Some people would call that second flavour "test driven design", and what they mean by that is that sometimes you'll feel a kind of pain when writing that test, where it's deviating from your #4, and so interpret this pain as an indication that you need to modify the design. I don't think either flavour is better, but I think constraining yourself to just one or the other will help identify where they work well and where they work poorly, which will help you know how to adjust your real-world testing, based on situation you're in (and I could talk about this for an absurdly long time, so I'll trust your #5 to reveal the insights there).

Anyway, great article, congrats on the gem!


Oh, looked ~10s at the git repo, here's something I do: instead of committing the .gem file, use git to tag the commit you built it from. Then you can can go back and rebuild it from there, if you want to (or DL it from rubygems with gem fetch find_recipe -v 0.2.0) It's usually (not always) best to avoid committing generated files, esp if they're binary, b/c: they can bloat the repo, git isn't smart about diffs in binary data, and they're generated from another more canonical representation, which is in the repo. In this case, they're also going to accrue in that directory and then they'll start feeling spammy.

Collapse
 
morinoko profile image
Felice Forby • Edited

Hey Josh,

Thank you so much for your awesome feedback and comments!

1 I was skeptical at first, but planning really helped. I usually get stuck wondering what to do next but the plan solved. I'm sure you can do it in many ways like you said, though, for example with "goals" or interfaces. I think the "user story" method would be excellent too! We used to use user stories at my previous job for every single feature addition, no matter how big or small, and it really forced you to think about things from the user perspective, plus of course keep you on track with your code.

2 I loved the image of you writing your pseudocode for JavaScript in Ruby code, lol. I agree that it becomes less necessary as you get better and better at coding though. If I remember correctly, in the Code Complete book, the author actually advocates for ALWAYS writing pseudocode first before any code because it helps majorly reduce errors. I'm sure that's true, but sometimes it does seem like overkill.

3 I'm trying to be more careful with taking things in small steps (especially as a beginner!). I've been guilty of jumping into things, bashing out a bunch of code, just to learn that I broke everything and not knowing at what point I went wrong. Your gem looks super helpful! I'm going to check that out!

4 True!

Bonus: Yeah... testing I'm sure has it's time and place. But it would have been helpful to have a few simple tests that I could test out my code on instead of having to run the program every single time! heh. I haven't gotten too much into it yet, but I'm sure I'll form more in-depth opinions on testing later ;)


Thanks for checking out my repo too! Actually, I tried to find documentation on what exactly to do with those .gem files once you pushed them, but no luck. Are you supposed to keep them in your own file base? If so, should you delete each .gem version as you make new ones? I removed them from my git repo for now at least.

Collapse
 
joshcheek profile image
Josh Cheek

Thanks for checking out my repo too! Actually, I tried to find documentation on what exactly to do with those .gem files once you pushed them, but no luck. Are you supposed to keep them in your own file base? If so, should you delete each .gem version as you make new ones? I removed them from my git repo for now at least.

My approach is to build them into my project root and add *.gem to my gitignore (eg) so that git won't see it. Sometimes, when I get annoyed by them, I just rm *.gem to clear them out.

I've also seen people create a directory they build it into, eg gems or pkg, and add that directory to the gitignore, but I don't see much value in keeping the old .gem files around.


I looked at it a little bit more, and there's a few issues you're going to wind up hitting. In the gemspec, it defines the bin dir as exe, but the find-recipe executable is in bin. As a consequence, users who install the gem won't be able to run find-recipe (RubyGems won't know to add it to the $PATH).

The require statement in the binary is './lib/find_recipe', the leading dot means "the directory I am currently in", which will work when you're running it from your gem's root directory (b/c from there, lib/find_recipe is the file you want), but it will break when you run it from a different directory (eg people who install the gem are unlikely to be in the gem's source code when they run it). The easy immediate solution here is to use require_relative and go up a dir, but the most correct solution is to add your lib dir to an array stored in the global variable $LOAD_PATH. This variable stores the set of places Ruby will require code from... (Rubygems will add paths to this array, as you require gems). Try running ruby -r pp -e 'pp $LOAD_PATH', to see what that array looks like. After you add your lib dir, you can require everything relative to lib. In this case require 'find_recipe'. Here is where I do that in my gem.

You might find value in a walkthrough I made while teaching a class. It hits on all the most important things while building a gem, including things like the $PATH and $LOAD_PATH, executability, permissions, and so forth. It should touch on all the really nuanced things like that, which took me forever to learn! Seriously, the $LOAD_PATH is simple, but it's implicit, so I was confused by it for years! Right now, it will also be difficult for you to test the gem, b/c it calls gets and puts, which talk directly to the global input and output streams. The third day's material shows how to deal with that for the gem we build, which is similar to yours in many ways :)

Thread Thread
 
joshcheek profile image
Josh Cheek

Actually, looking @ my walkthrough, the stuff that's relevant to you probably begins halfway through the second day, here. If you want to code along with the walkthrough, you can start on the first day, so that you'll have the code, but I don't know that there's anything super new in it (maybe the stuff about how to get the absolute file path). If you don't want to code along with it, the code is here, and the commits follow along with the walkthrough, so you can go back and see what it looked like at that time.

Thread Thread
 
morinoko profile image
Felice Forby

Thanks for explaining the .gem file stuff.

And you hit right on the other thing I was confused about: how to get the 'find-recipe' command to run no matter what directory you're in! I read through your walkthroughs and the $PATH is starting to make a little more sense.

The original $PATH code and gemspec were generated by bundler's gem command, so I had just used it as is. I used your examples and it seems to work now :) thanks again!