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
Related
I used this guide as a starting point for creating a messaging system from scratch. The idea is to have conversations between User and AdminUser. I want the AdminUser respond to questions sent it by User. AdminUser can have many conversations with the same User (there can be several topics of conversation, each topic is a separate conversation).
The Admin should be able to see the previous conversations with a reply box.
Like in the article I've created below db structure:
class User < ApplicationRecord
has_many :conversations, as: :sendable
has_many :conversations, as: :recipientable
end
class AdminUser < ApplicationRecord
has_many :conversations, as: :sendable
has_many :conversations, as: :recipientable
end
class Conversation < ApplicationRecord
belongs_to :sendable, polymorphic: true
belongs_to :recipientable, polymorphic: true
has_many :messages, dependent: :destroy
validates :sendable_id, uniqueness: { scope: :recipientable_id }
end
class Message < ApplicationRecord
belongs_to :conversation
belongs_to :messageable, polymorphic: true
validates_presence_of :body
end
Inside the article provided earlier I read that:
We also added “messageable” as polymorphic. This way, both Admin and User can send messages to conversation with their respective references.
What "messageable" is responsible for? because to create new message I need to provide this messageble_id and I don't now what it is.
# app/views/messages/index.html.erb
<% #messages.each do |message| %>
<%= message.body %>
<% end %>
<%= form_for [#conversation, #message] do |f| %>
<%= f.text_area :body %>
<%= f.submit "Send Reply" %>
<% end %>
controller:
class MessagesController < ApplicationController
before_action :fetch_conversation
def index
#messages = #conversation.messages
#message = #conversation.messages.new
end
def new
#message = #conversation.messages.new
end
def create
#message = #conversation.messages.new(message_params)
redirect_to conversation_messages_path(#conversation) if #message.save
end
private
def message_params
params.require(:message).permit(:body, :messageable_id)
end
def fetch_conversation
#conversation = Conversation.find(params[:conversation_id])
end
end
With above code I've got an error:
ActiveRecord::RecordInvalid: Validation failed: Messageable must exist
Which means I need to provide messageable_id and probably messageable_type but what are these values?
I have a Store object that has an email_address attribute. Using the logic from and How To Build A Form and Handling Inbound Email Parsing with Rails, I'm trying to figure out how to structure a Conversation where a visitor can email the Store, and the Store can reply through email - their replies would post a Message to the Conversation.
When a visitor inquires to the store (via form), I create a Reservation record with their name and email, and start a Conversation like this:
#conversation = Conversation.create(sender_id: self.id, recipient_id: self.store_id)
I wanted to model the notifications similar to this, where everyone but the sender receives an email, but I'm stumped on how to map the User, since it's two different objects (Reservation and Store):
def send_notifications!
(forum_thread.users.uniq - [user]).each do |user|
UserMailer.new_post(user, self).deliver_now
end
end
The Conversation model looks like this, may be wrong, any guidance on what I could use to make the messages unique and structure the notifications?
class Conversation < ApplicationRecord
belongs_to :sender, :foreign_key => :sender_id, class_name: "Reservation"
belongs_to :recipient, :foreign_key => :recipient_id, class_name: "Store"
belongs_to :reservation
has_many :messages, dependent: :destroy
end
The simplest and most flexible way would be to set this up as a many-to-many association:
class User < ApplicationRecord
has_many :messages
has_many :conversations, through: :messages
end
class Message < ApplicationRecord
belongs_to :user
belongs_to :conversation
end
class Conversation < ApplicationRecord
has_many :messages
has_many :users, through: :messages
end
Here Message actually works as the join table that ties it together. Conversion is the recipient. When sending an initial message to a user you would POST to /users/:user_id/messages:
<%= form_with(model: [#user, #message || Message.new]) do |f| %>
# ...
<% end %>
module Users
class MessagesController < ApplicationController
# POST /users/:user_id/messages
def create
#user = User.find(params[:user_id])
#conversation = Conversation.joins(:users)
.where(users: { id: [current_user, #user]})
.first_or_create
#message = #conversation.messages.new(message_params.merge(user: current_user))
if #message.save
redirect_to #coversation
else
render :new
end
end
end
end
And then you would handle the views and controllers (such as a chat window) for conversations in a separate controller:
<%= form_with(model: [#conversation, #message || #conversation.messages.new]) do |f| %>
# ...
<% end %>
module Conversations
class MessagesController < ApplicationController
# POST /conversations/:conversation_id/messages
def create
#conversation = Conversation.find(params[:conversation_id])
#message = #conversation.messages.new(message_params.merge(user: current_user))
if #message.save
redirect_to #coversation
else
render :new
end
end
end
end
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 =>
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 %>
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
end
class Post < ActiveRecord::Base
belongs_to :calendar
end
We have routed the corresponding resources with Routing Concerns, as follows:
concern :administratable do
resources :administrations
end
resources :users, concerns: :administratable
resources :calendars, concerns: :administratable
To create a new #calendar, we use the following form:
<h2>Create a new calendar</h2>
<%= form_for(#calendar) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.text_field :name, placeholder: "Your new calendar name" %>
</div>
<%= f.submit "Create", class: "btn btn-primary" %>
<% end %>
When we embed this form into the user's show.html.erb (so that a user can create a new calendar from his profile), everything works fine.
However, we would like to have a special page, call dashboard, where a user could see all his calendars and create a new one.
We believe the logical url should be /users/:id/administrations.
So, we embedded the above form in the Administration's index.html.erb.
And as soon as we do that and visit for instance /users/1/administrations, we get the following error:
ArgumentError in AdministrationsController#index
First argument in form cannot contain nil or be empty
Extracted source (around line #432):
else
object = record.is_a?(Array) ? record.last : record
raise ArgumentError, "First argument in form cannot contain nil or be empty" unless object
object_name = options[:as] || model_name_from_record_or_class(object).param_key
apply_form_for_options!(record, object, options)
end
EDIT: here is also our Administrations#Controller:
class AdministrationsController < ApplicationController
def to_s
role
end
def index
#user = current_user
#administrations = #user.administrations
end
def show
#administration = Administration.find(params[:id])
end
end
Any idea of what we are doing wrong?
First argument in form cannot contain nil or be empty
The reason is #calendar is not initialised in your controller action, so obviously #calendar is nil. So is the error.
To get your form to work in administrations/index.html.erb, you should be having the below code in your index action of your administrations_controller.
def index
#user = current_user
#administrations = #user.administrations
#calendar = Calendar.new # this one.
end