#ruby #rails #fullstack #webdev
What Is It? What Does it Do?
When first presented with the idea I envisioned a simple application with a visually appealing, intuitive user interface with basic functionality for users to record details of local trails, add, images and details about these trails, and record and share their hikes on them.
The Easy Parts
I felt fairly confident with my knowledge executing CRUD functionality through MVC architecture. Though Rails is a mammoth framework, the basics of it are pretty straightforward, and the documentation is very clear, easy to navigate and well written and maintained. That being said Im sure I will eat my words when someone later points out a rookie mistake I overseen in my rails application. I kept waiting to get stuck, to hit a wall, or a challenge I couldn't overcome but for the most part I was able to sail through the implementation of these features without ever really being stumped. I found it refreshingly easy to use.
And then Came the *Other* Parts…
Pure grit. That was what it ended up taking me to finish this application. Not because it was conceptually so challenging that I couldn't complete it. All my hurdles came in surprise packages from outside the development environment. First was my international move from the U.S. to an island in Mexico which tied up a huge chunk of my time and resources for a short period. Ok, no big deal. That was a choice and I had planned, researched and prepared for a graceful transition (Or so I thought). Then began the domino effect which started while wrapping up the tail end of the project, right as I was recording my demo video and writing my blog post, days before planning to schedule my assessment.
The hurdles started with the amateur electrical wiring in our temporary accommodations that shocked me with 220V on a daily basis through open neutral circuit and quickly rendered my just over one year old MacBook air useless. Gah! Ok, no problem. --Gritting teeth, while opening wallet-- So… even though I was on an island of 70,000 people in Mexico… I found a repairman who replaced my motherboard within a day for very affordable price. Lucky me! What were the chances? Yeay! Next hurdle, the replacement motherboard worked beautifully but …also revealed there was more damage than anticipated, and the faulty wiring had also destroyed my built in speakers, microphone and audio jack. No audio input, meant no demo video, no online assessment. --Gritting teeth, while opening wallet again-- Through a very complicated arrangement aka “Operation Cozumel”, I was able to locate a secondhand Dell XPS 15 replacement laptop back in Austin, TX. I wired money for it, hired someone to go check it out, purchase it and ship it to Oklahoma ,and then had someone else fly it from Oklahoma to Mexico. Okay. That took a few weeks but problem solved. I also purchase a “No Break” uninterrupted power supply with led voltage monitor to prevent anymore damaged electronics. Now, working laptop in hand, crisis averted. I got this. Sigh, I moved from electrified paradise into new permanent apartment, where the property manager promised fast wifi that had been set up for previous remote worker inhabitants. And…nope. I can’t even load google on my 2Mbps wifi connection. Doh! --Gritting teeth, while opening wallet and peering in to see if there is anything left-- Yeay! Superhero computer repair guy also turns out is actually a network guru in disguise (with a Masters in Network Engineering). He saves the day once again by appearing with a brand new wifi extender, which I regretfully discover after a day does very little through my concrete walls. But, that’s okay, because the next day my newly adopted superhero (let’s call him Edgar) resells me my neighbors long range wifi radio antenna transmitting 400Mbps from a dedicated fiber connection on the other side of town. (I think every programmer needs an ‘Edgar’ in their life. ) And so… that it how I was able to complete my blog post and demo, one paragraph at a time, and how I became the proud new owner of a Ubiquti NanoBean AC Gen 2 Power Beam and possibly the best internet connection on the island, with my own superhero.
Aside from the logistical challenges I overcame there were few programmatical challenges along the way. I began my application some months and moved on to another project, so there were several months in between and I had the experience of having to reacquaint myself to it, and when I returned the application had a deprecated gem and a security vulnerability.
Upgrading the deprecated gem in my application was an easy fix. I simply replaced the chromedriver-helper gem with the new webdrivers gem in my gemfile. Chromedriver-helper was created to run system tests for the Chrome browser and was deprecated in favor of webdrivers with discontinued support as of 03-31-2019.
Regarding security issues in my application, an alert appeared in my Github profile on May, 29, 2019 warning me of a security vulnerability of 'high severity'. The widely used OmniAuth gem that creates an alternative sign up and login method for new users by allowing them to sign in to popular accounts such as facebook, gmail, and Github to authorize and authenticate their identity had been recognized as a security risk. Apparently the OmniAuth request phase is 'vulnerable to Cross Site Request Forgery when used as a part of the Rails framework'. This vulnerability creates an opportunity for a secondary account to be able able to sign into a primary account without the primary users consent. No patch was available at the time of writing this blog post but there is a suggested fix which instructs developers to add the omni auth-rails_csrf_protection gem to the application gemfile and ensure that any links that initiate an OAuth request phase and use an HTTP GET request is converted to an HTTP POST request form with an authenticity token value, most easily by replacing suspect instances of link_to with button_to.
Other little bugs included, form validation with missing error messages, flash messages appearing again later on the wrong pages, unexpected behaviors between associated objects, for example if deleting a trail that another user had saved an associated hike to cause all kinds of errors with missing attribute values. Making the application fluid and responsive for different browser window sizes to accommodate different devices also proved to be challenging and time consuming. One by one I worked through each to an appropriate solution. Of the more complicated solutions I implemented was the use of polymorphic associations between models.
When creating an architectural outline for my application, I realized that the image objects I was creating, did not belong to one other object model in my program, but rather could belong to almost any model in my project. A profile image would belong to a user, an image of a trail would belong to its trail, and a users images of a specific hike would belong to the hike. I needed to create an image model that could morph its association to which ever model that instance of an image belonged to. As per the Rails guide: “A slightly more advanced twist on associations is the polymorphic association. With polymorphic associations, a model can belong to more than one other model, on a single association.”
Polymorphic associations are a fairly straight forward concept but while I am practicing limiting my own scope, it is a concept with enough material to explore in a separate blog post dedicated strictly to this topic where I would also like to explore polymorphic route helpers so I will only cover it briefly cover here.
To implement polymorphic associations:
- Create your migration to build your model
- Add the [model] id and [model] type line to your migration file
- Run rake db:migrate
- Add the belongsto association to your polymorphic model
- Add the reciprocal associations to your hasmany models
- Create methods to create the appropriate restful routes
The unforeseen complication of this type of association was that my rails path helpers no longer worked. When I discovered this I made the amateur error of reinventing the wheel without checking for an existing solution. I ended up spending a whole lot of time building many neatly encapsulated helper methods to create my own paths for each polymorphic variation. While refreshing my understanding of polymorphic associations for this blog post I stumbled across polymorphic route helpers that it did not even occur to would already be available. Again, I would like to revisit this in depth in a future blog post.
This is a two part project and I intend to extend my app by giving users the capability to share feedback with and interact with other users by adding JS comments to Trails. I would also like to use google maps api to enable users to add location maps of trails with start and end markers, use geolocation to display user or device position in relation to these trails and add google directions service to map direction to trail heads.
If I Had a Do Over
If I were to do this project again, I would simplify. I would focus on limiting scope and improving the quality and syntactical sugar of my code. I would write the best little program instead of a mediocre larger more complex one. I think there is eloquence, and efficiency in simplicity. I would take time more time to brainstorm user scenarios and app behavior to define and understand the clear relationships between objects as the user interacts with them. For example - the Trails and Hikes relationship. If a user deletes a Trail what happens to the references to that Trail by associated Hikes on that Trail that other users have created. I would clearly define these behaviors and what ifs at the beginning of the architectural process rather than being surprised by them as bugs while nearing completion and having to create methods as afterthoughts to correct them. I would avoid the added complication of polymorphic associations, if practical. I would refactor my code to reduce the number of database inquiries. I was torn between reducing potential bugs caused by storing old database requests in instance variables and making frequent new requests to the database to prevent variables from holding on to stale data. I choose the latter, but I like to find a more efficient way to reduce the number of this requests and keep my variables current. To sum up, a revised version of my application would include fewer db requests, smaller scope, simplified code base, higher quality code, polymorphic route helpers instead of custom path helper methods.
In the spirit of not repeating myself, mistakes included, a couple of take-aways that Im sure will be useful to retain for future projects would be the use of polymorphic associations on my object models if I needed to recreate similar relationships. Simple things like remembering to add regex for end of string in date and number range validations, and watching out for missing attribute references on forms, and form label helpers. I learned about handling deprecated gems in my app, and correcting security vulnerabilities created by the use of popular gems. With my unanticipated electrical, hardware and network issues, I got side lessons in hardware repair and replacement, open neutral electrical wiring, uninterrupted power supplies, wifi extenders and long range wifi antennas, and a refresher on setting up linux development environments with my replacement laptop. All in all I would say these challenges provided me with a pretty well rounded learning experience.
A demo of this application can be found here!atxrenegade/Harleigh Abel