ActiveRecord relationships and migrations for new web app - ruby-on-rails

I'm a Rails beginner trying to build my first web app.
In my web app Physiotherapists can add Patients and create ExercisePlans from a list of Exercises. I sketched out the model relationships as follows underneath. Is this the correct way to do it?
I am mostly concerned about the following:
Is a join table the correct way to match exercises to exercise_plans?
Do I need a join table to match physiotherapists to exercise_plans?
RELATIONSHIPS
Physiotherapist
has_many :patients
has_many :exercise_plans
Patient
has_many :exercise_plans
belongs_to :physiotherapist
Exercise
has_many :exercise_plans
ExercisePlan
belongs_to :patient
belongs_to :physiotherapist
has_and_belongs_to_many :exercises
MIGRATIONS
class CreatePhysiotherapists < ActiveRecord::Migration
def change
create_table :physiotherapists do |t|
t.string :first_name
t.string :last_name
t.string :company_name
t.string :email
t.string :password
t.timestamps
end
class CreatePatients < ActiveRecord::Migration
def change
create_table :patients do |t|
t.string :first_name
t.string :last_name
t.string :email
t.integer :physiotherapist_id #the physiotherapist to which the patient belongs
t.timestamps
end
class CreateExercises < ActiveRecord::Migration
def change
create_table :exercises do |t|
t.string :title
t.string :category
t.string :bodypart
t.text :instructions
t.timestamps
end
class CreateExercisePlans < ActiveRecord::Migration
def change
create_table :exercise_plans do |t|
t.string :name
t.integer :exercise_id #an array of exercises that are in the plan
t.integer :physiotherapist_id #the physiotherapist who created the plan
t.integer :patient_id #the user for whom the exercise plan is made
t.timestamps
end
#join table for the has_and_belongs_to_many relationship with exercises
create_table :exercise_plans_exercises do |t|
t.integer :exercise_id
t.integer :exercise_plan_id
end
end

1) Yes. However, I'd use has_many :through instead of has_and_belongs_to_many. It is a much more flexible approach and allows for a customization of exercise for each specific exercise plan. For example, you might want to store a number of repetitions or duration in Activity model. http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association
ExercisePlan
has_many :activities
has_many :exercises, through: :activities
Exercise
has_many :activities
has_many :exercise_plans, through: :activities
Activity
belongs_to :exercise
belongs_to :exercise_plan
2) No, there's no need for an additional join table.
Notes:
You don't really need to have physiotherapist_id in exercise_plans, because you already have it in patients. You can exercise_plan.patient.physiotherapist.

Related

Rails join table with record_type and recored_id like activestorage

create_table :posts do |t|
t.string :title
t.string :content
end
create_table :post2s do |t|
t.string :title
t.string :content
end
create_table :users do |t|
t.string :email
t.string :name
end
create_table :likes do |t|
t.references :users
t.string :record_type
t.integer :record_id
end
enter image description here
I want to make like join table like Activestorage
with record_type and record_id
Could anyone know this name of join or site to how to create this style of join?
Looks like you need a polymorphic association
You would therefore have the type and id in your likes table, as you have described. Your models for posts and post2s would identify themselves as being of the polymorphic type.
E.g. posts model: has_many :likes, as: :likeable and likes model: belongs_to :likeable, polymorphic: true
But do read the guide.

One to one Association in Rails not working

I'm kind of new in Rails and couldn't make my Models relationship to work, and I don't know the reason. I've tried in many ways and followed many different tutorials, but couldn't figure out the right way. Do you know what's wrong with my code? How can I test it?
I have two models, with has_one association User and Profile. Profile belongs to User. So every time I add a user, I would also add a Profile.
This is the migration file for User model
class CreateUsers < ActiveRecord::Migration[5.1]
def change
create_table :users do |t|
t.string :email
t.string :password_digest
t.timestamps
end
end
end
This is migration file for Profile model
class CreateProfiles < ActiveRecord::Migration[5.1]
def change
drop_table :profiles
create_table :profiles do |t|
t.string :name
t.string :lastname
t.string :phone
t.string :address
t.string :city
t.string :state
t.string :country
t.string :gender, :limit => 10
t.string :zipcode
t.references :users, foreign_key: true
t.timestamps
end
end
end
model Profile
class Profile < ApplicationRecord
belongs_to :user
end
model User
class User < ApplicationRecord
has_secure_password
has_one :profile
end
Thank you very much!
I think the problem might be with the migration.
Profile belongs to User, so in the create_profiles migration you should have:
t.references :user, foreign_key: true
(user instead of users).
With your code, the Profile model will have users_id field when it should have user_id.

Rails 4 has_many :through relationship: destroy parent model instance when child model instance count reaches 0

In our Rails 4 app, there are four models:
class User < ActiveRecord::Base
has_many :administrations, dependent: :destroy
has_many :calendars, through: :administrations
end
class Administration < ActiveRecord::Base
belongs_to :user
belongs_to :calendar
end
class Calendar < ActiveRecord::Base
has_many :administrations, dependent: :destroy
has_many :users, through: :administrations
has_many :posts, dependent: :destroy
end
class Post < ActiveRecord::Base
belongs_to :calendar
end
Here are the corresponding migrations:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :first_name
t.string :last_name
t.string :email
t.integer :total_calendar_count
t.integer :owned_calendar_count
t.timestamps null: false
end
end
end
class CreateAdministrations < ActiveRecord::Migration
def change
create_table :administrations do |t|
t.references :user, index: true, foreign_key: true
t.references :calendar, index: true, foreign_key: true
t.string :role
t.timestamps null: false
end
end
end
class CreateCalendars < ActiveRecord::Migration
def change
create_table :calendars do |t|
t.string :name
t.timestamps null: false
end
end
end
class CreatePosts < ActiveRecord::Migration
def change
create_table :posts do |t|
t.references :calendar, index: true, foreign_key: true
t.date :date
t.time :time
t.string :focus
t.string :format
t.string :blog_title
t.text :long_copy
t.text :short_copy
t.string :link
t.string :hashtag
t.string :media
t.float :promotion
t.string :target
t.integer :approval
t.text :comment
t.timestamps null: false
end
end
end
Each time a user quits a calendar, meaning we destroy the corresponding administration, we want to make sure that the following happens:
We check if there are other administrations for the same calendar.
If there are, we do nothing (the user is simply redirected to his dashboard).
But if there aren't any more (because the user was the last one to quit the calendar for example), then we want to destroy the calendar as well.
This should automatically destroy all the posts belonging to this calendar, thanks to the has_many :posts, dependent: :destroy line in our Calendar model, but we guess it would not hurt to check that too.
We are thinking of achieving this through a private clear_calendar method in the Calendar model, that we would use as an after_destroy callback in the Administrations controller:
private
def clear_calendar
#calendar = Calendar.find(params[:id])
unless #calendar.administration.exists?
#calendar.destroy
end
end
Does that make sense?
This is a very sensible approach, and the whole world is happy that you didn't place this logic in a controller action.
One thing to note: by placing this logic in your Calendar model, you're necessarily binding Calendar and Administration together. Perhaps you find that to be ok at this point in time, but a truly object-oriented program, instead of asking another model if it exists, would instead tell a model what it wants, instead of how it wants it done (e.g. delete self if this association doesn't exist).
I would recommend placing this logic in a PORO--perhaps a service--that removes unnecessary coupling from your database-backed objects. Perhaps that'd look something like so:
class Calendar < ActiveRecord::Base
...
private
def clear_calendar
ParentDestructionService.new(self)
end
end
class ParentDestructionService
def initialize(parent)
#parent = parent
end
.....logic goes here.....
end
This way, not only do you truly separate a how process from a class that shouldn't care about Administration, but you now have the capabilities to either stick this in a Sidekiq process, or simply thread it off. Either way, you're way more flexible. Now in the future, you'll be able to send ANY parent through that service, and things will work as intended.

uninitialized constant NameError when use has_many

I practice my RoR skills and try develop application to already created DB. It's have 4 tables: testplans, testplan_tcversions,* test_project* and nodes.
I'm code 2 models for this tables:
class TestPlan < ActiveRecord::Base
self.table_name= 'testplans'
belongs_to :test_project
has_many :test_suites, foreign_key: :testplan_id, inverse_of: :test_plan
has_one :node, foreign_key: :id, inverse_of: :test_plan
end
and
class TestSuite < ActiveRecord::Base
self.table_name='testplan_tcversions'
belongs_to :test_plan
has_one :node, foreign_key: id, inverse_of: :test_collection
end
But I get exception uninitialized constant TestPlan::TestSuite when try: #suits=TestPlan.find(4906).test_suites
I found a lot of answers that Models must singular and table must plural, but my Models names are singular, names of tables I point in self.table_name.
What I did wrong?
UPD
This my db:schema:dump
create_table "testplans", force: true do |t|
t.integer "testproject_id"
t.text "notes"
t.integer "active"
t.integer "is_open"
t.integer "is_public"
t.text "api_key"
end
create_table "testplan_tcversions", force: true do |t|
t.integer "testplan_id"
t.integer "tcversion_id"
t.integer "node_order"
t.integer "urgency"
t.integer "platform_id"
t.integer "author_id"
t.datetime "creation_ts"
end
How are your migrations set up?
If they are set up correctly, the relationship between TestSuite and TestPlan should look like this:
class TestPlan < ActiveRecord::Base
has_many :test_suites
end
class TestSuite < ActiveRecord::Base
belongs_to :test_plan
end
For this to work though, your TestSuite migration needs to have a test_plan_id column. That should look like this.
class TestSuite < ActiveRecord::Migration
belongs_to :test_plan
end
If this is set up correctly, you should then be able to call #suits=TestPlan.find(4906).test_suites.
Make sure your table names correspond to your model names. If you don't have a table named 'testplan_tcversions', the association isn't going to work.

rails model has_many :through associations

I am trying to get my relationships worked out but I am having trouble using the associations.
So I have three models Workout, Exercise and WorkoutExercise. A workout should have many exercises and a exercise should have different workouts therefore I wrote:
class Workout < ActiveRecord::Base
has_many :workout_exercises
has_many :exercises, :through => :workout_exercises
end
class Exercise < ActiveRecord::Base
has_many :workout_exercises
has_many :workouts, :through => :workout_exercises
end
class WorkoutExercise < ActiveRecord::Base
belongs_to :exercise
belongs_to :workout
end
I am running some tests but the tests aren't passing once I create a workout, exercise and then join them in the workout_exercise class. It won't let me access the exercises in the workout like this:
Workout.create
Exercise.create
WorkoutExercise.create(:workout => Workout.first, :exercise => Exercise.first)
work = Workout.first
work.exercises.count #This line causes the error: undefined method exercises
My database tables look like this:
class CreateWorkouts < ActiveRecord::Migration
def change
create_table :workouts do |t|
t.string :title
t.text :description
t.float :score
t.timestamps
end
end
end
class CreateExercises < ActiveRecord::Migration
def change
create_table :exercises do |t|
t.string :title
t.text :description
t.float :value
t.timestamps
end
end
end
class CreateWorkoutExercises < ActiveRecord::Migration
def change
create_table :workout_exercises do |t|
t.timestamps
end
end
end
When I run this tests it says exercises is undefined. Does anyone have any ideas?
Ok, so your WorkoutExercises table can't be empty. This is how it should look:
class CreateWorkoutExercises < ActiveRecord::Migration
def change
create_table :WorkoutExercises do |t|
t.integer :exercise_id, :null => false
t.integer :workout_id, :null => false
t.timestamps
end
# I only added theses indexes so theoretically your database queries are faster.
# If you don't plan on having many records, you can leave these 2 lines out.
add_index :WorkoutExercises, :exercise_id
add_index :WorkoutExercises, :workout_id
end
end
Also, you can name this table whatever you'd like, it doesn't have to be WorkoutExercises.
However, if you were using a has_and_belongs_to_many relationship, your table would have to mandatorily be named ExercisesWorkout. Notice how Exercises comes before Workout. The names have to be alphabetically ordered. Don't ask me why, it's just a Rails convention.
So, in this case, you'll do fine with your table being named WorkoutExercises. But if I were you, I'd change it to ExercisesWorkout, just in case, so that you never get it wrong.
Your code looks OK. Bug maybe has_and_belongs_to_many is a better choice. See Choosing Between has_many :through and has_and_belongs_to_many

Resources