Find by linked fields Rails 5 - ruby-on-rails

I have a model that works like this:
class User < ApplicationRecord
has_many :deals
...
class Deal < ApplicationRecord
belongs_to :user
has_many :clients
...
class Clients < ApplicationRecord
belongs_to :deal
If I want to find all the clients listed on a certain deal I can enter Client.find_by(deal_id: x) in the console where x is the deal ID I want, but when I try to list them all on a page I'm doing something wrong.
Here's the relevant resource route
Rails.application.routes.draw do
resources :deals, only: [:show]
end
I think the problem is I'm not doing the find correctly in the controller
class DealsController < ApplicationController
def show
#deal = Deal.find_by_id(params[:id])
#client = Client.find_by(deal_id: params[:id])
end
end
On the page I'm trying to list clients like this:
<div class="client">
<% #client.each do |c| %>
<%= c.name %>
<% end %>
</div>
But the error is undefined method each' for #<Client:0x007f8e4cdecfb0>
I thought it would be pretty simple because the :id that's returned from /deal/:id is shared by both, but I'm stumped.

find_by will return only one object, each is a method defined for arrays.
So if you want clients for a particular deal, you can do
#deal = Deal.find_by_id(params[:id])
#clients = #deal.clients
or
#deal = Deal.find_by_id(params[:id])
#clients = Client.where(deal_id: params[:id])
and in view
<% #clients.each do |c| %>
<%= c.name %>
<% end %>

Related

how to retrieve data from the database using the has_many association

I have
message table
and
message_pictures
table(It belongs to the message table) and what I want to do is retrieve each messsge pictures (if there is any) from the
message_controller
.
How can I do it. I have surfed the web but found little scanty explanations on it.
This is the message class(model)
class Message < ApplicationRecord
belongs_to :user
has_many :message_pictures, dependent: :destroy
accepts_nested_attributes_for :message_pictures
end
This is the message_pictures class(model)
class MessagePicture < ApplicationRecord
belongs_to :message, optional: true
mount_uploader :message_pictures, PictureUploader
end
and this is the index method of the message_controller class
def index
#user = User.find(current_user.id)#414, 449, 494
#messages = #user.messages.paginate(page: params[:page])
##messages = #messages.message_pictures.paginate(page: params[:page])
end
You can see the line 4 of the index method to see the way I did mine but its not working
I believe what you need is has_many ... :through
app/models/user.rb
class User < ApplicationRecord
# ...
has_many :messages, dependent: :destroy
has_many :message_pictures, through: :messages
end
app/controllers/messages_controller.rb
class MessagesController < ApplicationController
# ...
def index
#user = User.find(current_user.id)
#messages = #user.messages.paginate(page: params[:page])
#message_pictures = #user.message_pictures
end
end
has_many ... :through simplifies the retrieving of "nested" children records via "SQL JOINS", of which normally you would have done it in a longer (more explicit way) like the following (which also works):
class MessagesController < ApplicationController
# ...
def index
#user = User.find(current_user.id)
#messages = #user.messages.paginate(page: params[:page])
#message_pictures = MessagePicture.joins(message: :user).where(
messages: { # <-- this needs to be the table name, and not the association name, and is why it is in plural form
users: { # <-- this needs to be the table name, and not the association name, and is why it is in plural form
id: #user.id
}
}
)
end
end
Update: Alternative Solution
Looking back at your question, I have a feeling you'd only want #message_pictures that corresponds to #messages and not to all#user.messages, because I noticed you have pagination for the messages. I'll do it like this instead:
app/controllers/messages_controller.rb
class MessagesController < ApplicationController
# ...
def index
#user = User.find(current_user.id)
# the `includes` here prevents N+1 SQL queries, because we are gonna loop
# through each `message_picture` in each `message` record (see index.html.erb below)
#messages = #user.messages.includes(:message_pictures).paginate(page: params[:page])
end
end
app/views/messages/index.html.erb (example)
<h1>Messages:</h1>
<% #messages.each do |message| %>
<section>
<h2>Message:</h2>
<p><%= message.content %></p>
<h3>Message Pictures:<h3>
<div>
<% message.message_pictures.each do |message_picture| %>
<% message_picture.message_pictures.each do |message_picture_attachment| %>
<%= image_tag message_picture_attachment.url.to_s %>
<% end %>
<br>
<% end %>
</div>
</section>
<% end %>
^ Above assumes MessagePicture is using carrierwave. P.S. It looks to me there's something wrong with how you defined your models, because your message has many message_pictures, while each of the message_picture also has many attached message_picture carrierwave attachments (assuming you used the "multiple-file" upload set up for carrierwave because you used mount_uploader :message_pictures, PictureUploader instead of mount_uploader :message_picture, PictureUploader. The model problem I think is because it's like this: message < message_pictures < message_pictures attachments, but (depending on your use-case), it should probably be just like message < message_pictures - message_picture attachment, or just simply message < message_pictures attachments

Add Record to Existing Collection in a Rails App

I am attempting to develop a model in which a user can add the recipe they are viewing to an existing menu of recipes they have created, similar to adding a song to a custom playlist. I believe I have the models set up correctly (using a many to many through relationship) however I am unsure how to go about the adding of the actual records to a selected collection. Any guidance would be helpful. My code is as below.
Menus Controller
class MenusController < ApplicationController
before_action :set_search
def show
#menu = Menu.find(params[:id])
end
def new
#menu = Menu.new
end
def edit
#menu = Menu.find(params[:id])
end
def create
#menu = current_user.menus.new(menu_params)
if #menu.save
redirect_to #menu
else
render 'new'
end
end
def update
#menu = Menu.find(params[:id])
if #menu.update(menu_params)
redirect_to #menu
else
render 'edit'
end
end
def destroy
#menu = Menu.find(params[:id])
#menu.destroy
redirect_to recipes_path
end
private
def menu_params
params.require(:menu).permit(:title)
end
end
Menu Model
class Menu < ApplicationRecord
belongs_to :user
has_many :menu_recipes
has_many :recipes, through: :menu_recipes
end
menu_recipe Model
class MenuRecipe < ApplicationRecord
belongs_to :menu
belongs_to :recipe
end
Recipe Model
class Recipe < ApplicationRecord
belongs_to :user
has_one_attached :cover
has_many :menu_recipes
has_many :menus, through: :menu_recipes
end
User Model
class User < ApplicationRecord
has_secure_password
has_one_attached :profile_image
has_many :recipes
has_many :menus
end
You can do something like :
def add_recipe_to_menu
menu = current_user.menus.find params[:id]
recipe = current_user.recipes.find params[:recipe_id]
menu.recipes << recipe
end
It will add a viewing recipe to existing menu of recipes.
First make sure you build the new record off the user:
class MenusController < ApplicationController
# make sure you authenticate the user first
before_action :authenticate_user!, except: [:show, :index]
def new
#menu = current_user.menus.new
end
def create
#menu = current_user.menus.new(menu_attributes)
# ...
end
end
Then we can just add a select to the form where the user can select from his recipes:
# use form_with in Rails 5.1+
<%= form_for(#menu) do |f| %>
... other fields
<div class="field">
<%= f.label :recipe_ids %>
<%= f.collection_select :recipe_ids, f.object.user.recipies, :id, :name, multiple: true %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
f.object accesses the model instance wrapped by the form builder.
recipe_ids is a special setter/getter created by ActiveRecord for has_many associations. As you may have guesses it returns an array of ids and lets the association be set with an array of ids - automatically inserting/deleting rows in the join table in the process.
You then just need to whitelist the recipe_ids param:
def menu_attributes
params.require(:menu)
.permit(:foo, :bar, recipe_ids: [])
end
recipe_ids: [] whitelists an array of permitted scalar types. Since this is a hash option it must be listed after any positional arguments to be syntactically valid.
rb(main):003:0> params.require(:menu).permit(:foo, recipe_ids: [], :bar)
SyntaxError: (irb):3: syntax error, unexpected ')', expecting =>

set up helper_method :current_patient Rails 4

How can I set a helper method in my app that when I call it, recognizes a current_patient from a certain medic (can be multiple medics, and multiple patients for a medic) and access to patient :id, this would help me to associate:
Medic with a Patient with a Consultations for current_patient
I need to access on a patient and set id (patient) on a Consultation table foreign key :patient_id
in my create action in the controller I have:
def create
#consultation = Consultation.new(consultation_params.merge({:patient_id => current_patient}))
respond_to ...
end
Is a helper method a good way to do this?
How can I do this?
my models:
class Medic < ActiveRecord::Base
has_many :patients
end
class Patient < ActiveRecord::Base
belongs_to :medic, :foreign_key => :medic_id
has_many :consultations
accepts_nested_attributes_for :consultations
end
class Consultation < ActiveRecord::Base
belongs_to :patient, :foreign_key => :patient_id
end
Thanks for help
Lets start with the routes:
resources :patients do
resources :consultations
end
This will give us the routes for consultations nested under the patient:
Prefix Verb URI Pattern Controller#Action
patient_consultations GET /patients/:patient_id/consultations(.:format) consultations#index
POST /patients/:patient_id/consultations(.:format) consultations#create
new_patient_consultation GET /patients/:patient_id/consultations/new(.:format) consultations#new
edit_patient_consultation GET /patients/:patient_id/consultations/:id/edit(.:format) consultations#edit
patient_consultation GET /patients/:patient_id/consultations/:id(.:format) consultations#show
PATCH /patients/:patient_id/consultations/:id(.:format) consultations#update
PUT /patients/:patient_id/consultations/:id(.:format) consultations#update
DELETE /patients/:patient_id/consultations/:id(.:format) consultations#destroy
So lets say we have a PatientsController#index method which shows all the patients. And in our view we have something like this:
<% #patients.each do |patient| %>
<li>
<p><%= patient.name %></p>
</li>
<% end %>
So lets add a link to create the consultation:
<% #patients.each do |patient| %>
<p><%= patient.name %></p>
<ul class="actions">
<li>
<%= link_to "New consultation",
new_patient_consultation_path(patient) %>
</li>
</ul>
<% end %>
Clicking the link would take us to /patients/6/consultations/new.
So in our ConsultationsController we can access the patient id from the params:
class ConsultationsController < ApplicationController
# We use a callback so that we don't need to do
# #patient = Patient.find(params[:id]) in every action
before_action :set_patient
# GET /patients/:patient_id/consultations/new
def new
#consultation = #patient.consultations.new
end
# POST /patients/:patient_id/consultations
def create
#consultation = #patient.consultations.new(consultation_params)
# ...
end
# ...
private
def set_patient
#patient = Patient.find(params[:patient_id])
end
# ...
end
set_patient is just a private method belonging to the controller. This is not really a case where you would use a helper method.
Its often done when dealing with dealing with authentication since you are getting current_user from the session - independently from the params. If you are creating a helper method it should work everywhere.
There is one final thing you need to to get this to work, the form needs to point to /patients/:patient_id/consultations.
# app/views/consultations/_form.html.erb
<% form_for [#patient, #consultation] do |f| %>
# .. rails does all the magic figuring out the url.
<% end %>
http://guides.rubyonrails.org/routing.html#nested-resources

Pass associated model id to another form

I have the following association setup
class Donation < ActiveRecord::Base
belongs_to :campaign
end
class Campaign < ActiveRecord::Base
has_many :donations
end
in my donation controller i have campaign_id as a strong parameter. Now when creating a donation i would like to have the campaign_id available to save in that form, but better off in the controller somewhere im guessing as less open to being edited. To get from the campaign to the donation form i click a button
<%= link_to 'Donate', new_donation_path, class: 'big-button green' %>
as we are already using
def show
#campaign = Campaign.find(params[:id])
end
How can i get that id into my donations form?
Thanks
It sounds like you're looking for Nested Resources.
Basically, your routes.rb might look something like:
resources :campaigns do
resources :donations
end
Your controller might look something like:
class DonationsController < ApplicationController
before_action :find_campaign
def new
#donation = #campaign.donations.build
end
def create
#donation = #campaign.donations.create(params[:donation])
# ...
end
protected
def find_campaign
#campaign ||= Campaign.find(params[:campaign_id])
end
end
And your example link, something like...
<%= link_to 'Donate', new_campaign_donation_path(#campaign), class: 'big-button green' %>

In Rails, how do I use RESTful actions for a resource that is the join in a many to many relationship?

I have the following models:
class User < ActiveRecord::Base
has_many :subscriptions
end
class Subscription < ActiveRecord::Base
belongs_to :user
belongs_to :queue
end
class Queue < ActiveRecord::Base
has_many :subscriptions
end
I want to have some meta-data in the Subscription class and allow users to maintain the details of each of their subscriptions with each subscriptions meta-data. Queues produce messages, and these will be sent to users who have Subscriptions to the Queue.
As I see it the resource I want to have is a list of subscriptions, ie the user will fill in a form that has all the Queues they can subscribe to and set some metadata for each one. How can I create a RESTful Rails resource to achieve this? Have I designed my Subscription class wrong?
I presently have this in my routes.rb:
map.resources :users do |user|
user.resources :subscriptions
end
But this makes each subscription a resource and not the list of subscriptions a single resource.
Thanks.
This can be done quite easily using accepts_nested_attributes_for and fields_for:
First in the User model you do the following:
class User < ActiveRecord::Base
has_many :subscriptions
accepts_nested_attributes_for :subscriptions, :reject_if => proc { |attributes| attributes['queue_id'].to_i.zero? }
# if you hit scaling issues, optimized the following two methods
# at the moment this code is suffering from the N+1 problem
def subscription_for(queue)
subscriptions.find_or_initialize_by_queue_id queue.id
end
def subscribed_to?(queue)
subscriptions.find_by_queue_id queue.id
end
end
That will allow you to create and update child records using the subscriptions_attributes setter. For more details on the possibilities see accepts_nested_attributes_for
Now you need to set up the routes and controller to do the following:
map.resources :users do |user|
user.resource :subscriptions # notice the singular resource
end
class SubscriptionsController < ActionController::Base
def edit
#user = User.find params[:user_id]
end
def update
#user = User.find params[:user_id]
if #user.update_attributes(params[:user])
flash[:notice] = "updated subscriptions"
redirect_to account_path
else
render :action => "edit"
end
end
end
So far this is bog standard, the magic happens in the views and how you set up the params:
app/views/subscriptions/edit.html.erb
<% form_for #user, :url => user_subscription_path(#user), :method => :put do |f| %>
<% for queue in #queues %>
<% f.fields_for "subscriptions[]", #user.subscription_for(queue) do |sf| %>
<div>
<%= sf.check_box :queue_id, :value => queue.id, :checked => #user.subscribed_to?(queue) %>
<%= queue.name %>
<%= sf.text_field :random_other_data %>
</div>
<% end %>
<% end %>
<% end %>
I found this tutorial very useful, as I was trying to relate Users to Users via a Follows join table: http://railstutorial.org/chapters/following-users

Resources