I'm new to RoR and I need some help with associations. I'm using rails 6.0.3.4 and ruby 2.7.0.
Users can create cases and cases belongs to a certain district. Districts belongs to a state. It has to be that way, because cases can't belongs to a state.
Now I want to show the number of cases for a certain diagnosis for each state. I have to use district, to get all the cases for a state. How should I build the where(...) condition?
<!-- State -->
<div class="card">
<div class="card-body">
<h5 class="card-title">State</h5>
<p><%= State.find(1).titel%> (<%= #diagnosis.cases.where(...).count %>)</p>
</div>
</div>
My Models
case.rb
class Case < ApplicationRecord
before_create :set_pseud
belongs_to :user
belongs_to :diagnosis
belongs_to :district
belongs_to :report, optional: true
end
district.rb
class District < ApplicationRecord
has_many :users
has_many :cases
has_many :reports
belongs_to :state
end
state.rb
class State < ApplicationRecord
has_many :districts
has_many :users
end
For better understanding my schema.rb:
ActiveRecord::Schema.define(version: 2021_02_11_140244) do
create_table "cases", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "first_name"
t.string "last_name"
t.string "gender"
t.date "birthdate"
t.string "place_of_residence"
t.string "diagnosis"
t.bigint "user_id"
t.datetime "confirmed_at"
t.datetime "created_at", precision: 6
t.datetime "updated_at", precision: 6, null: false
t.bigint "diagnosis_id"
t.bigint "district_id"
t.bigint "report_id"
t.string "pseud"
t.index ["diagnosis_id"], name: "index_cases_on_diagnosis_id"
t.index ["district_id"], name: "index_cases_on_district_id"
t.index ["pseud"], name: "index_cases_on_pseud"
t.index ["report_id"], name: "index_cases_on_report_id"
t.index ["user_id"], name: "index_cases_on_user_id"
end
create_table "diagnoses", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "illness"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "districts", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "name"
t.string "place"
t.integer "postal_code"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.bigint "state_id", null: false
t.index ["state_id"], name: "index_districts_on_state_id"
end
create_table "reports", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci", force: :cascade do |t|
t.bigint "district_id"
t.text "comment"
t.datetime "date"
t.bigint "user_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["district_id"], name: "index_reports_on_district_id"
t.index ["user_id"], name: "index_reports_on_user_id"
end
create_table "states", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "titel"
t.string "abbr"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "users", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.integer "role"
t.string "first_name"
t.string "last_name"
t.bigint "district_id"
t.bigint "state_id"
t.index ["district_id"], name: "index_users_on_district_id"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
t.index ["state_id"], name: "index_users_on_state_id"
end
end
You should add a further association into state:
has_many :cases, through: :districts
Rather than finding your state in the view, you should do that in the controller and pass it to the view in an instance variable:
#state = State.find(params[:id])
I've assumed you're using a show action here rather than manually coding the state ID for some reason.
You can then do something like this:
#state.cases.where(diagnoses: { id: #diagnosis.id }).count
Or if you prefer, you can skip the .id on #diagnosis:
#state.cases.where(diagnoses: { id: #diagnosis }).count
Related
I have some models with a has_many_through join. I am trying to get a list of classification_fields and their properties where the classification_id is in the array I am pasing through. This following query doesn't seem to be getting anything. What am I doing wrong?
Get the parent ids and query the classificationfields:
#parentids = #classification.self_and_ancestors_ids.to_a if params[:class_id].present?
#details = ClassificationField.includes(:classifications).where(classification_id: [#parentids] ) if params[:sub].present?
classification model:
belongs_to :parent, class_name: "Classification", optional: true
has_many :children, class_name: "Classification", foreign_key: "parent_id", dependent: :destroy
has_many :class_fields
has_many :fields, through: :class_fields, source: :classification_field
accepts_nested_attributes_for :fields, allow_destroy: true
has_closure_tree
classification_field model:
has_many :class_fields
has_many :classifications, through: :class_fields
class_fields model:
belongs_to :classification
belongs_to :classification_field
Form where I am rendering dynamic form fields based on the classification fields details:
<%= form.fields_for :properties, OpenStruct.new(#sr.properties) do |builder| %>
<% #details.each do |field| %>
<%= render "srs/fields/#{field.field_type}", field: field, form: builder %>
<% end %>
<% end %>
Schema:
enable_extension "plpgsql"
create_table "class_fieldmembers", force: :cascade do |t|
t.integer "classification_id"
t.integer "classification_field_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "class_fields", force: :cascade do |t|
t.bigint "classification_id", null: false
t.bigint "classification_field_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["classification_field_id"], name: "index_class_fields_on_classification_field_id"
t.index ["classification_id"], name: "index_class_fields_on_classification_id"
end
create_table "classification_fields", force: :cascade do |t|
t.string "name"
t.string "field_type"
t.string "required"
t.string "classification_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "displayname"
end
create_table "classification_hierarchies", force: :cascade do |t|
t.integer "ancestor_id", null: false
t.integer "descendant_id", null: false
t.integer "generations", null: false
t.index ["ancestor_id", "descendant_id", "generations"], name: "classification_anc_desc_idx", unique: true
t.index ["descendant_id"], name: "classification_desc_idx"
end
create_table "classifications", force: :cascade do |t|
t.string "name"
t.string "displayname"
t.text "description"
t.boolean "inuse"
t.integer "sort_order"
t.integer "parent_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "srs", force: :cascade do |t|
t.string "summary"
t.text "description"
t.string "status"
t.string "priority"
t.bigint "user_id", null: false
t.bigint "classification_id", null: false
t.text "properties"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["classification_id"], name: "index_srs_on_classification_id"
t.index ["user_id"], name: "index_srs_on_user_id"
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.boolean "admin"
t.string "first_name"
t.string "last_name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
add_foreign_key "class_fields", "classification_fields"
add_foreign_key "class_fields", "classifications"
add_foreign_key "srs", "classifications"
add_foreign_key "srs", "users"
Query results from console:
[1] pry(main)> ClassificationField.includes(:classifications).where(classification_id: [1, 6] )
ClassificationField Load (0.7ms) SELECT "classification_fields".* FROM "classification_fields" WHERE "classification_fields"."classification_id" IN ($1, $2) [["classification_id", "1"], ["classification_id", "6"]]
ClassificationField Load (0.6ms) SELECT "classification_fields".* FROM "classification_fields" WHERE "classification_fields"."classification_id" IN ($1, $2) /* loading for inspect */ LIMIT $3 [["classification_id", "1"], ["classification_id", "6"], ["LIMIT", 11]]
=> #<ClassificationField::ActiveRecord_Relation:0x4a10>
Any help is appreciated.
Try this. Convert query result to array of objects using .to_a
#details = ClassificationField.includes(:classifications).where(classification_id: [1, 6] ).to_a
I think I have it now.
#details = ClassificationField.joins(:class_fields).where(class_fields: {classification_id: [#parentids]}).to_a if params[:sub].present?
I'm new to Ruby on Rails and I got two problems where I need some help:
I got the tables "cases" and "users". The table case includes the column "first name" and "last name". Now I want to add a unique string (pseudonymization) to each case for a special kind of identification without using the ID. This string should be build from the third letter of the first name and the total amount of letters plus the third letter of the last name and again the total amount of letters of the last name. E.g. for the name "Bill Smith" the string would be: L4I5.
Now the "users" should be able to find a specific case using that created special string. There should be a kind of a searching field where they can type in the string and click on search. Then the case with all the parameters should be shown.
What is the best way, to implement these two functions/features? I'm using rails 6.0.3.4 and ruby 2.7.0.
For better understanding see my schema.rb below.
ActiveRecord::Schema.define(version: 2021_01_28_100706) do
create_table "cases", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "first_name"
t.string "last_name"
t.string "gender"
t.date "birthdate"
t.string "place_of_residence"
t.string "diagnosis"
t.bigint "user_id"
t.datetime "confirmed_at"
t.datetime "created_at", precision: 6
t.datetime "updated_at", precision: 6, null: false
t.bigint "diagnosis_id"
t.bigint "district_id"
t.bigint "report_id"
t.index ["diagnosis_id"], name: "index_cases_on_diagnosis_id"
t.index ["district_id"], name: "index_cases_on_district_id"
t.index ["report_id"], name: "index_cases_on_report_id"
t.index ["user_id"], name: "index_cases_on_user_id"
end
create_table "diagnoses", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "illness"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "districts", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "name"
t.string "place"
t.integer "postal_code"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.bigint "state_id", null: false
t.index ["state_id"], name: "index_districts_on_state_id"
end
create_table "reports", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci", force: :cascade do |t|
t.bigint "district_id"
t.text "comment"
t.datetime "date"
t.bigint "user_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["district_id"], name: "index_reports_on_district_id"
t.index ["user_id"], name: "index_reports_on_user_id"
end
create_table "states", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "titel"
t.string "abbr"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "users", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.integer "role"
t.string "first_name"
t.string "last_name"
t.bigint "district_id"
t.bigint "state_id"
t.index ["district_id"], name: "index_users_on_district_id"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
t.index ["state_id"], name: "index_users_on_state_id"
end
end
You could add a column to the case table and a callback in the model to set it how you want. And to search just add a scope
#migration
add_column :cases, :new_column, :string, index: true
#model
class Case < ApplicationRecord
before_create :set_new_column
scope :by_new_column, ->(term) { where('new_column = ?', term) }
...
private
def set_new_column
self.new_column = "#{first_name[0]}#{last_name.length}"
end
end
Then in your controller or where ever, you could use like:
Case.by_new_column("C5")
Users can upload tracks and create playlist. I have a model for playlist and i have a model for playlist_track which is for users that can save to the playlist. I can create a new playlist but how do i add the tracks to the playlist in the views?
ActiveRecord::Schema.define(version: 2018_12_06_050857) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "active_storage_attachments", force: :cascade do |t|
t.string "name", null: false
t.string "record_type", null: false
t.bigint "record_id", null: false
t.bigint "blob_id", null: false
t.datetime "created_at", null: false
t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
end
create_table "active_storage_blobs", force: :cascade do |t|
t.string "key", null: false
t.string "filename", null: false
t.string "content_type"
t.text "metadata"
t.bigint "byte_size", null: false
t.string "checksum", null: false
t.datetime "created_at", null: false
t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
end
create_table "playlist_tracks", force: :cascade do |t|
t.integer "playlist_id"
t.integer "track_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "playlists", force: :cascade do |t|
t.string "title"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "plays", force: :cascade do |t|
t.integer "user_id"
t.integer "track_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "tracks", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "genre"
t.integer "user_id"
t.string "name"
t.date "release_date"
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "username"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
end
Here is my track model
class Track < ApplicationRecord
has_many_attached :mp3
belongs_to :user, optional: true
has_many :playlist_tracks
has_many :playlists, through: :playlist_tracks
has_many :plays
end
and here is my playist_track model
class PlaylistTrack < ApplicationRecord
belongs_to :playlist
belongs_to :track
end
I'm trying to get all Activities related to users of the same school (a string on the user object), but the error I'm getting is this:
Can't join 'Activity' to association named 'users'; perhaps you
misspelled it?
activity_controller.rb
#school = current_user.school
#bathroom = Activity.includes(:users).where(name: 'Bathroom').where( :user => { :school => #school} )
and the schema:
create_table "activities", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "student_id"
t.string "status"
t.integer "user_id"
t.index ["student_id"], name: "index_activities_on_student_id"
t.index ["user_id"], name: "index_activities_on_user_id"
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "name"
t.string "grade"
t.string "school"
t.integer "maxout"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
Not users but user because Activity belongs to User
#bathroom = Activity.includes(:user).where(name: 'Bathroom').where( :users => { :school => #school} )
But User has many activities so User.includes(:activities)
Of course, you have to provide associations:
class Activity
belongs_to :user
class User
has_many :activities
I am trying to write a scope or a method where I take the attribute (last_eaten) of an instance (line_item) and compare it to the current date. If last_eaten has a date of 1-7 days ago, it gets put in an array that will be called last_week. If last_eaten has a date of 8-14 days ago, it gets put in an array that will be called 2_weeks_ago.
I've tried quite a few things as you can see with the commented out code and several things that I had already erased, but I can't get anything to work. I'm relatively new to rails and any help would be greatly appreciated.
Model
class LineItem < ActiveRecord::Base
belongs_to :recipe
belongs_to :recipe_collection
#scope :last_week, lambda {where("line_item.last_eaten >= ?", 7.days.ago)}
#scope :last_week, lambda { |weeks| where("last_eaten > ?", weeks) }
#scope :three_weeks, lambda { where( #line_item.last_eaten < 21.days.ago.to_date) }
##line_item = LineItem.where(last_eaten: params[:last_eaten]) -- returns nil
##line_item = LineItem.where(last_eaten: params[:last_eaten] < 21.days.ago.to_date)
#def menu
# list = []
# if LineItem.last_eaten.day.to_i > 21.days.ago.day.to_i
# LineItem.last_eaten.each do |recipe_id|
# LineItem.recipe_id << list
# end
# end
# list
#end
end
Schema
ActiveRecord::Schema.define(version: 20151229223926) do
create_table "directions", force: :cascade do |t|
t.text "step"
t.integer "recipe_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "directions", ["recipe_id"], name: "index_directions_on_recipe_id"
create_table "ingredients", force: :cascade do |t|
t.string "name"
t.integer "recipe_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "ingredients", ["recipe_id"], name: "index_ingredients_on_recipe_id"
create_table "line_items", force: :cascade do |t|
t.integer "recipe_id"
t.integer "recipe_collection_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.date "last_eaten"
end
add_index "line_items", ["recipe_collection_id"], name: "index_line_items_on_recipe_collection_id"
add_index "line_items", ["recipe_id"], name: "index_line_items_on_recipe_id"
create_table "recipe_collections", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "recipes", force: :cascade do |t|
t.string "title"
t.text "description"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "image_file_name"
t.string "image_content_type"
t.integer "image_file_size"
t.datetime "image_updated_at"
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "users", ["email"], name: "index_users_on_email", unique: true
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
scope :last_week, lambda {where("line_item.last_eaten >= ?", 7.days.ago)}
should work...
But further reading made me realise that your table is called line_items, not line_item
When you're doing sql-snippets, you need to refer to the name of the table in SQL, rather than treating it like an individual rails object's name. This means always use the pluralised version :)