Jump to content

The ultimate community for Ruby on Rails developers.


Photo

Extending Objects


  • Please log in to reply
9 replies to this topic

#1 levymetal

levymetal

    Passenger

  • Members
  • 4 posts

Posted 29 August 2013 - 02:58 PM

Hi Guys,

 

I'm working on an app but I'm having some trouble getting my head around how to structure my models. I've been in the front-end game for a long time now so I'm a little rusty with the back-end side of things. 

 

First off, the problem, as summarised as I can get it. 

 

This is a fitness app. There's a database of exercises, which holds attributes on the name of the exercise, instructions, video etc. For example.

class Exercise < ActiveRecord::Base
  attr_accessible :name, :description, :video_url
end 

The app will also have the ability to create a program, which will contain many exercises, while adding fields like weight and reps to the exercise.

 

This is where I'm a bit confused. From a database point of view, I can see myself creating a new table such as program_exercises, which contains weight and reps has a foreign key pointed to an entry in the exercises table and a foreign key pointed to the programs table. So, in rails, I could do something like this:

class Program < ActiveRecord::Base
  has_many :program_exercises
end

class Program_Exercise < ActiveRecord::Base
  attr_accessible :weight, :reps
  belongs_to :exercise
  belongs_to :program
end

This should, in theory, work. However the terminology just doesn't seem correct, in real life a program_exercise doesn't really belong_to an exercise, it is an exercise, just with added data! I thought about inheritance, but that seems to be more in terms of inheriting methods/properties from a Class rather than inheriting actual instance data (plus, there'll be many program_exercises referencing back to a single exercise).

 

The problem is also compounded, because users should also be able to track their own progress for a particular exercise, which could lead to another model like so:

class User_Exercise < ActiveRecord::Base
  attr_accessible :completed_weight, :completed_reps
  belongs_to :user
  belongs_to :program_exercise
end

Again, it works in theory, but now in Rails a user_exercise belongs to a program_exercise which belongs to an exercise. Which, I don't particularly like, because to get the exercise name on a user_exercise would be user_exercise.program_exercise.exercise.name. Considering that at it's core, a user_exercise is still just an exercise, I should just be able to go user_exercise.name instead.

 

I suppose I could do something like this, but to have to redefine things that exist seems weird.

class User_Exercise < ActiveRecord::Base
  ...

  def name
    program_exercise.exercise.name
  end
end

Is there a better way to do this? Have I missed something, acts_as, or some type of instance inheritance? I'd appreciate any insight into this problem.

 

Thanks in advance.



#2 Rowel

Rowel

    Controller

  • Members
  • 109 posts

Posted 29 August 2013 - 03:11 PM

http://guides.rubyon...ugh-association

 

I think has_many :through association is what you're looking for?  Then put the reps and weight attribute in the join table. 


  • james likes this

#3 james

james

    Guard

  • Moderators
  • 221 posts
  • LocationLeeds, U.K.

Posted 30 August 2013 - 02:43 AM

in real life a program_exercise doesn't really belong_to an exercise, it is an exercise

 

 

I have an issue with that statement and perhaps this is where your confusion lies.

 

If I turn up to the gym one day and talk to a fitness trainer I'll be told that I'm an over weight unfit slob that should stop sitting in front of my PC writing software and get some exercise.

 

I'll most likely ask the trainer what exercises I should do so I can become the slim, fit god that I used to be when I had a real job without killing myself in the process.

 

I'm sure I'll be told that I'll need to do some running, some swimming, some cycling (all the stuff I love doing), some weight training, maybe rowing leg presses, push up's etc (all the things I hate doing) and I'm sure I'll be told to pay a lot of money and an exercise program listing all these exercises will be put together, giving me details of how often I need to do each exercise, what distance I will need to run to start with, increasing over time etc...

 

A voila! I have an exercise program tailored just to me

 

So an exercise program consists of many exercises and belongs to me! There aren't any other overweight programmers looking for help with my medical conditions at the same gym

.

For each of the exercises in my program I'll need to know when I am supposed to be doing that exercise and I'll need to know the weights or the number of push ups or the number lengths of the pool or the number of miles I have to cycle or how many times a week I have to run to work instead of catching the bus. (I'm not going to do them all at exactly the same time that's impossible and if I started all of them in the same week then I really would be dead pretty quickly)

Which means my program has many exercises but the details of those exercises change over time right?

To model that you would have a user that belongs_to an exercise program consisting of (has) many exercises right?

So right there is my issue with your statement. The program is a number of exercises, hence an exercise_program.

 

You are pretty much on track but you need stop thinking in programming terms (which is quite clearly what you are doing as you would never use the term  "program exercises" in any normal conversation) and write down the scenarios in human language as I have just done and all should start to become clear. You can then read through your writings, picking out nouns and verbs to make up your models and their attributes.

 

If you are going to play the role of analyst and talk to your clients you HAVE to get the approach right.

 

The above may well mean you need a mind shift in terms of what data is stored in what table opr it may not. the point is that you SHOULD STOP what you are doing on your PC and get a pen and a piece of paper and start writing your requirements, do some flow charts and only then should you consider data modeling and UI

 

I hope that brings some clarity to your thought processes and design.


Programming is just about problem solving!


#4 levymetal

levymetal

    Passenger

  • Members
  • 4 posts

Posted 30 August 2013 - 12:03 PM

Thanks for your response, it's much appreciated.

 

Actually, I feel that I understand the domain & problem very well. It is all written down, mapped out, and understood. My problem is turning that into code.

 

You're right, a program is a number of exercises. Hence, in my original post, I created a program class which has many program_exercises (exercises with specific data to that program). exercise_program might be a better name than just program, but naming semantics isn't really my problem.

 

My problem is this:

  • There is a database of exercises, containing Name, Description, Video etc. It does not contain information about weights & reps, because this will differ for each program, and many programs will use the same exercise.
  • An admin can create a program, which contains many exercises. When they create the program, they add weight and reps to the exercise, which is unique to that program
  • A user can then join a program. In order to track their progress, they can add their completed weight & reps to each exercise, which is unique to that user and program.

How can this be modelled in Rails? A program cannot simply have many exercises, because there is additional data associated with each exercise unique to the program. Thus, I created program_exercise which is able to store additional data for each exercise which is unique to the program, while referring to original exercise model to get the name, description etc. Then, there is a user_exercise model, which allows a user to track their progress of a specific exercise inside a specific program.

 

In human language, all three of these are exercises. First off is a database of exercises containing the details & instructions (i named: exercise). Second are the exercises in a program, which inherit the original information from the database, and add weight and reps (i named: program_exercise). Third, there are the exercises that a user completes, which inherits both the original information from the database and the weight/reps from the program, and then adds other personal statistics to the exercise (i named: user_exercise).

 

Just so you're aware that this isn't a misunderstanding on my part - a program does not belong to a user; a program is pre-created with set weights and reps, then users can join that program. If the user can't do the weights or reps, they can enter in their own details to track their progress. While counter-intuitive, those are the requirements.

 

Your answer is very detailed however it applies to a different problem than the one I'm having. I'm trying to figure out the proper way to structure my models inside rails; I'm not trying to understand the domain. So it would be great if you could provide your insight on how you'd go about modelling this in Rails.

 

Cheers



#5 levymetal

levymetal

    Passenger

  • Members
  • 4 posts

Posted 30 August 2013 - 01:56 PM

http://guides.rubyon...ugh-association

 

I think has_many :through association is what you're looking for?  Then put the reps and weight attribute in the join table. 

 

Thanks for that info, I think that's what I'm looking for but I can't seem to figure out how to use it in my situation. Based on my structure it would be something like this

class Program < ActiveRecord::Base
  has_many :program_exercises
  has_many :exercises, through: :program_exercises
end

But would it be possible to access weight/reps through a call to Program.exercises, or would I still have to get that data from Program.program_exercises?

 

This might also help, I made a UML diagram of how I've currently pictured the solution (again, I'm open to ideas on better ways to model this)

 

ed1f7eca.png



#6 james

james

    Guard

  • Moderators
  • 221 posts
  • LocationLeeds, U.K.

Posted 30 August 2013 - 03:05 PM

Actually, I feel that I understand the domain & problem very well. It is all written down, mapped out, and understood. My problem is turning that into code.

 

 

 

Well, that's the main battle won :) Semantics are extremely important. Confusion lies in not getting the table names right.

 

Taking on board all you've said the best thing I can do is describe what my approach might be. Thinking out loud what comes next is my thought processes straight from the top of my head. Scary! But I've got a few spare minuites to think this through.

 

An admin can create a program, which contains many exercises. When they create the program, they add weight and reps to the exercise, which is unique to that program.

 

 

So a program has many exercises which are both look up tables

 

Because an instance of an exercise is specific to a program then I should have an exercise_program to contain the specific data related to the program not the user. so I now have 3 look up tables.

 

A user can then join a program. In order to track their progress, they can add their completed weight & reps to each exercise, which is unique to that user and program.

 

 

From that I see a user has many exercise_progress (Ouch, that's horrible semantics), the fields, for which, need to mirror the exercise_program but contain additional data such as when the specific exercise was performed and what the actual number of reps were or the actual weights used etc....

 

I want to re-visit "user has many exercise_progress" It's clearly wrong! A user exercises. therefore exercise_progress should be user_exercise and now everything starts to become clearer

 

a user:-

has many user_exercises

belongs to an exercise_program

 

a program:-

has many exercises

 

an exercise:-

belongs to a program

*2 has many exercise_programs renamed to program_exercises

 

 

an exercise_program: *2 rename to program_exercise

belongs to exercise

belongs_to a program

 

*1 has_many user_excercises

 

a user_exercise:-

belongs to an exercise_program *2 rename to program_exercise

belongs_to a user

So lets revisit the exercise program *1

 

I'm pretty confident that now I have all the structure I need I want to explore some use cases to prove the point

 

A user signs up to an exercise program that's fine: an exercise_program belongs_to a program so I can provide a list of programs to choose from, then from the program, I will know what exercises the user has available from the exercise_programs. Another semantic ouch! It now makes sense to call the exercise_program program_exercises so lets rename that *2

 

Start again

 

A user signs up to an exercise program that's fine: a program has many program_exercises. So I can provide a list of programs to choose from then from the program I will know what exercises the user has available from the program_exercises. Now that makes more sense :) But a user doesn't have a relationship with the programs so lets revisit the models

 

a user:-

has many user_exercises

belongs to a program

has_many exercise_programs through program

 

A program can now have many users and also should have many program exercises. so we now have

 

a program:-

has many exercises

has_many users

has_many program_exercises

 

a user_exercise:-

belongs to a program_exercise

belongs_to a user

 

an exercise:-

belongs to a program

has many program_exercises

 

a program_exercise:-

belongs to exercise

belongs_to a program

 

So start again

A user signs up to an exercise program that's fine: a program has many program_exercises. So I can provide a list of programs to choose from then from the program I will know what exercises the user has available from the program_exercises -> exercise.

 

Cool, a user can now sign up to a program and a program also knows what users are using it!

 

another use case then

a user chooses an exercise to do

Well we have a list of exercises a user can do because the user belongs to a program so all that is needed here is user.program.exercise for the description and a user.program.program_exercises for the actual weights, reps etc... but that's a bit long winded. Why not go straight to the list of program_exercises then to the description of the exercise. that could be done by adding a through if needed. No need to add it now. It;s good enough to know that it can be done and proves that up to now all is good

 

Another use case

A user completes an exercise

Well that works to. A new user_exercise record gets created and attached to the program_exercise and the actual details of the exercise can be completed and compared to what should have been done by looking at the program_exercise fields for weights etc...

Brilliant that all works nicely

 

Another use case

Setting up an exercise program (there's that term again but I'm now seeing that I am using the term exercise program to mean program so all is still making sense)

so I'm not going to set up an exercise_program, I'm going to set up a program

 

I can add program_exercise records by selecting from the list of exercises and I can fill in the specific details (targets?) of each exercise

 

I think that is all good!

You can add :through's to the relationships to make your life easier but I reckon that should do the trick nicely


  • Kelli Shaver likes this

Programming is just about problem solving!


#7 james

james

    Guard

  • Moderators
  • 221 posts
  • LocationLeeds, U.K.

Posted 30 August 2013 - 03:06 PM

You posted your last reply while I was replying to you so I didn't catch it.

If my above reply doesn't answer all your questions then please feel free to come back


Programming is just about problem solving!


#8 levymetal

levymetal

    Passenger

  • Members
  • 4 posts

Posted 05 September 2013 - 03:57 AM

Thanks so much for the help, you've explained it really well and you've helped me get a much better understanding of it. Really appreciate the effort you put into the answer!



#9 james

james

    Guard

  • Moderators
  • 221 posts
  • LocationLeeds, U.K.

Posted 05 September 2013 - 09:56 AM

You are welcome.

 

REALLY IMPORTANT

 

Remember to consider "copying" lookup data into the instance data so that changes to lookup data can be made without upsetting historical data, yes you need the associations but a user that did exercise 1 last year that had a target of 100 reps, achieved those 100 reps therefore met the target but that target gets updated to 150 after a review today and suddenly that user didn't meet his target last year which is clearly wrong.

So I repeat, yes you do need the relationships so you can tell who belongs to which program etc... but an instance of that user_exercise needs to know what the target reps/laps/whatever were at the time of the exercise.

 

That's really a discussion you need to have with your client and you may need to provide additional functionality or options for your client to cater for changes to all lookup data.

Document the decisions made in that discussion, most importantly for the client to have an understanding of the consequences of changing lookup data but also because the client will most likely change those requirements over time and you need to be able to explain to them what the agreed process was and possibly charge for the extra time.


  • Rowel likes this

Programming is just about problem solving!


#10 Rowel

Rowel

    Controller

  • Members
  • 109 posts

Posted 05 September 2013 - 11:54 AM

Think of it kinda like a shopping cart.... user adds item to shopping cart, checkouts.  

 

A few months later, store owner increases price of same item.  

 

You don't want the user to visit his purchase history and it shows he bought the item at the new higher price.  

Instead You want to show the "original" price he bought the item. 


  • james likes this




0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users