It’s been a while since I’ve shared a project on here, and that’s because I have been wrestling with Rails since then. Oh Rails, it’s been real. Every section has had its challenges, but Rails was like little mind explosions every other day. It’s an incredibly powerful framework, you just have to learn its patterns, which took me a while.
tl;dr, here’s a quick video walkthrough of the simple vacation tracking app I built with Rails, OmniAuth, and Devise. I am kind of slow and rambling through this ~ 9 min walkthrough, so feel free to watch on doubletime!
The requirements for this project were a bit lengthier than previous assessments, but I just took a breath and worked through one step at a time.
Specific Project Requirements:
- 1. Use the Ruby on Rails framework
- 2. Your models must include a
has_many
, abelongs_to
, and ahas_many :through
relationship. You can include more models to fill out your domain, but there must be at least a model acting as a join table for the has_many through. - 3. The join model must also store an additional attribute describing the relationship.
- 4. Your models should include reasonable validations for the simple attributes.
- 5. You must include at least one class level ActiveRecord scope methods. To some extent these class scopes can be added to power a specific individual feature, such as “My Overdue Tasks” in a TODO application, scoping all tasks for the user by a datetime scope for overdue items,
@user.tasks.overdue
. - 6. You must include a nested form that writes to an associated model through a custom attribute writer. An example of this would be a New Recipe form that allowed you to add ingredients that are unique across recipes (thereby requiring a join model, or imagine being able to see all recipes that include Chicken), along with a quantity or description of the ingredient in the recipe. On this form you would have a series of fields named
recipe[ingredient_attributes][0][name]
andrecipe[ingredient_attributes][0][description]
which would write to the recipe model through a methodingredient_attributes=
. This method cannot be provided via theaccepts_nested_attributes_for
macro because the custom writer would be responsible for finding or creating a recipe by name and then creating the row in the join modelrecipe_ingredients
with therecipe_id
, theingredient_id
, and the description from the form. - 7. Your application must provide a standard user authentication, including signup, login, logout, and passwords. You can use Devise but given the complexity of that system, you should also feel free to roll your own authentication logic.
- 8. Your authentication system should allow login from some other service. Facebook, twitter, foursquare, github, etc…
- 9. You must make use of a nested resource with the appropriate RESTful URLs. Additionally, your nested resource must provide a form that relates to the parent resource. Imagine an application with user profiles. You might represent a person’s profile via the RESTful URL of /profiles/1, where 1 is the primary key of the profile. If the person wanted to add pictures to their profile, you could represent that as a nested resource of /profiles/1/pictures, listing all pictures belonging to profile 1. The route
/profiles/1/pictures/new
would allow to me upload a new picture to profile 1. - 10. Your forms should correctly display validation errors. Your fields should be enclosed within a fields_with_errors class and error messages describing the validation failures must be present within the view.
- 11. Your application must be, within reason, a DRY (Do-Not-Repeat-Yourself) Rails app. Logic present in your controllers should be encapsulated as methods in your models. Your views should use helper methods and partials to be as logic-less as possible. Follow patterns in the Rails Style Guide and the Ruby Style Guide.
Project Notes
I’ve been traveling quite a bit over the past few months, and I thought that it would be nice to have a better way to organize my trip details. Basically I wanted something just a bit more sophisticated than my boring Google Doc.
In planning my app I decided that I wanted the following user story:
- Person can log in & create a user account with email or via Facebook
- Users have profile pages with a couple of details about themselves (location, bio, etc.)
- Users can create a trip. The trip will be to a single destination, and they can save specific places/attractions to check out during their trip.
- Here’s what my model relationships looked like. Physically drawing out my relationships in my notepad made creating the models & troubleshooting easier later on down the road.
- a user
- has_many :trips
has_many :destinations, :through => :trips
has_many :attractions, :through => :trips
- has_many :trips
- a destination
- has_many :trips
has_many :users, :through => :trips
has_many :attractions
- has_many :trips
- a trip (join for user & destination)
- belongs_to :user
belongs_to :destination
has_many :trip_attractions
has_many :attractions, :through => :trip_attractions
- belongs_to :user
- an attraction
- belongs_to :destination
- has_many :trip_attractions
- has_any :trips, :through => :trip_attractions
- a trip_attraction (join for trip & attraction)
- belongs_to :trip
belongs_to :attraction - **my join table was “trip_attractions” when it looks like it should have been “attractions_trips” according to Rails naming conventions (alphabetical order, both models plural). I may go back and clean this up.
- belongs_to :trip
- a user
Things that tripped me up:
- I knew from the beginning that I wanted to use Devise for my authentication, but I had a bit of initial apprehension as the opportunities to mess it all up seemed high. I had a few stops and starts, but it turned out to be fairly easy to add custom fields to my User model.
- Nested forms were a bit tricky- the naming tripped me up, such as destinations_attributes vs. destination_attributes. >: ( RLY???
- The thing that stumped me the most was working on my Attractions model. I wanted to have the option to create a trip from the Attractions New/Edit page, and somewhere along the way it just wasn’t happening. For this problem, and just overall, the thing that saved me was walking through my app, and step by step, comparing page errors with the SQL that was firing, then looking back at my text editor, along with checking against my hand-drawn model relationships diagram. Geez, I really need a second monitor.
- A couple of times I had to concede defeat and just hard reset with Git back to a better, happier state of the project. At times it was overwhelming as my Google doc list of “to-dos” or rather “to-fixes” grew and grew and grew, but breathing and tackling each issue one by one was the only way to move forward.
Ideas for next steps:
My next steps are actually pretty clear, as the next LV assessment asks me to add some jQuery magic to this app. I love jQuery, so that should be fun. Other things that I’d like to do eventually:
- I forgot to mention in my walkthrough that I created model methods for destination popularity. So on the destinations index page, it shows destinations in order from most to least popular city. I also want to add upcoming_trips and past_trips as methods to the Trip class, so you can filter only relevant information on your Trips page
- Add user reviews & maybe a 5 star rating option to Attractions, making it more like TripAdvisor
- Deploy to Heroku – Never done it before, so why not.
- Add Profile photos / avatars, add photos to Attraction & Destination page
Anyways, onward!