Rails Fitness Tracker App Part 2: Routes and Views
At this point of the project, I have successfully pulled off 3rd-party authentication using Devise and OmniAuth!
So now, along with signing up/ logging in the "old-fashioned way", a user can do the same thing using an account they already have with another provider (like Facebook or Google).
Routes and the Controllers
With my user login/signup out of the way, I can now focus on routing the controllers to my views (more on the views later).
One of my pain points in this project was routing and working with controllers. By the end of this, I understood that routes are supposed to set up the endpoints of their related controller action. Those controller actions, then, perform a bunch of tasks with whatever data is available in the strong params. While I still have much to learn about controllers and Rails' MVC architecture, I feel I am coming out of this with a bit more understanding of them.
I also wanted to use custom attribute setters for a newly-created routine's nested exercises, to guard against duplicate exercises being created. While I could have gone with something like
accepts_nested_attributes_for :exercises in my
Routine model, I wanted to ensure that both the nested attributes for exercises (1-level deep) and the related routine exercise (2-levels deep) were mass-assigned upon creation of the routine. So I went ahead and created custom attribute setters for the following:
# app/models/routine.rb def exercises_attributes=(exercise) if exercise[:name] != "" && exercise[:exercise_type] != "" && exercise[:description] != "" new_exercise = self.exercises.build new_exercise.name = exercise[:name] new_exercise.exercise_type = exercise[:exercise_type] new_exercise.description = exercise[:description] end end
If a user decides they don't want to create a new exercise while creating a new routine, the outer-if statement allows for that.
# app/models/exercise.rb def routine_exercises_attributes=(routine_exercise) new_routine_exercise = self.routine_exercises.find_by(exercise_id: self.id) new_routine_exercise.update(sets: routine_exercise[:sets], reps: routine_exercise[:reps]) end
When editing/ updating an exercise inside a routine, the
Exercise model needs its own custom attribute setter for
routine_exercises_attributes since it is stored inside of
exercises_attributes (which is ultimately stored as strong params in the routine's
Inside the controllers for
Exercise, I added some private-level set methods to DRY out some of the controller actions:
# app/controllers/routines_controller.rb class RoutinesController < ApplicationController before_action :authenticate_user!, :set_routine # other code here def set_routine @routine = Routine.find_by(id: params[:id]) end end
# app/controllers/exercises_controller.rb class ExercisesController < ApplicationController before_action :authenticate_user!, :set_exercise # other code here def set_exercise @exercise = Exercise.find_by(id: params[:id]) end end
This is going to be covered in more detail later, but it should be mentioned that, in conjunction with these class-level setter methods, there is a form with two levels of data entry:
- A nested form for if the user wants to create a new exercise while creating a new routine
- A nested form within the previous nested form that records the
repsfor the exercise's (and, by extension, also the routine's)
Speaking of nested, I also decided to go with nested resources for when I user is dealing with their exercises in a particular routine. In other words, in
config/routes.rb, I added nested resources for
:exercises, scoped to certain views, that allow for the
At this point in the project, it's time to start thinking about how I want the user experience to play out while they are creating, editing, updating and deleting routines and exercises.
config/routes.rb, I have set nested routes that describe the relationship between the
Exercise models. This provides for some nifty paths including
resources :routines, only: [:show] do resources :exercises, only: [:show, :index, :edit, :update, :destroy] end
My application should be nimble enough to "know" which routine and which exercise we're dealing with at a given time. (e.g.
routines/1/exercises/3) As such, we should have the following views:
*These views will be supplied with a
_form.erb partial to DRY out the forms a bit.
For the next post, I'm going to write about the struggles that awaited me after this point in the project. This included things like keeping track of nested attributes in multiple models, preventing changed data from overwriting other data and a lot more.
SIDENOTE: As always, if you want to check out the project for yourself, play around with it and maybe give some feedback, that'd be awesome!
Awesome here. Let me suggest you that instead of
def set_routine @routine = Routine.find_by(id: params[:id]) end
You do something like:
def set_routine @routine = Routine.find(params[:id]) end
nil when it doesn't found nothing and then you could face
NoMethodError for NilClass.
.find raises an exception and you can better handle that with a notification or message or something.