ruby on rails
I'm beginner in rails and having trouble finding a proper way out with my problem.
I have three models : Conversation, participant, messages which have the following attributes :
Conversation :
module Messenger
class Conversation <ActiveRecord::Base
has_many :participants, :class_name => 'Messenger::Participant'
def messages
self.participants.messages.order(:created_at)
end
end
end
Participant :
module Messenger
class Participant <ActiveRecord::Base
has_many :messages, :class_name => 'Messenger::Message'
belongs_to :conversation, :class_name => 'Messenger::Conversation'
end
end
Message :
module Messenger
class Message <ActiveRecord::Base
default_scope {order(:created_at)}
default_scope {where(deleted: false)}
belongs_to :participant, :class_name => 'Messenger::Participant'
end
end
My trouble is that I'm trying to make a single form to create a conversation with a first message in it. The form looks like this :
= form_for @conversation, url: messenger.conversations_create_path do |f|
.row
.col-md-12.no-padding
.whitebg.padding15
.form-group.user-info-block.required
= f.label :title, t('trad'), class: 'control-label'
= f.text_field :title, class: 'form-control'
.form-group.user-info-block.required
= f.label :model, t('trad'), class: 'control-label'
= f.text_field :model, class: 'form-control'
.form-group.user-info-block.required
= f.label :model_id, t('trad'), class: 'control-label'
= f.text_field :model_id, class: 'form-control'
= fields_for @message, @conversation.participants.message do |m|
= m.label :content, t('trad'), class: 'control-label'
= m.text_area :content, class:'form-control'
.user-info-block.action-buttons
= f.submit t('trad'), :class => 'btn btn-primary pull-right'
I've tried many ways to make this form simple but I've encountered some problems which I don't know how to fix using rails properly.
I've tried using Field_for
to include a message in my conversation form, but since I have nothing saved in my database yet it seems I can't link a message to an unexisting participant.
So basically I want my first form, once validated, to create a conversation, link the current user to that conversation and link the message to that first user, but I assume there are ways to do it with the framework and I would not like to do it manually.
What is the proper way to follow to achieve that? Am I even on the good track or shoould I change something or add something?
Edit : to make it more understandable, a participant got a user_id and a conversation_id, which means this is a relation table. I can't adapt the attributes of my models to make it easier since I must keep it in that way for security reasons.
First , in order for your form to accept nested attributes using the fields_for
form helper, you need to specify accepts_nested_attributes_for
on your Conversation
model:
module Messenger
class Conversation <ActiveRecord::Base
has_many :participants, :class_name => 'Messenger::Participant'
# Required for form helper
accepts_nested_attributes_for :participants
[...]
Since you want to save a both Participant
as well as its Message
from the same form, you need to add a second accepts_nested_attributes_for
on your Participant
model:
module Messenger
class Participant <ActiveRecord::Base
has_many :messages, :class_name => 'Messenger::Message'
# Required for form helper
accepts_nested_attributes_for :messages
belongs_to :conversation, :class_name => 'Messenger::Conversation'
end
end
Next , in your controller, since this is a new Conversation
that doesn't have any Participant
s at first, you need to build
an associated Participant
(presumably based on the current_user
), and also an associated Message
for this new Participant
:
def new
@conversation.participants.build(user: current_user).messages.build
end
Finally in your view, specify the attribute fields in three nested blocks, form_for @conversation do |f|
, f.fields_for :participants do |p|
, and p.fields_for :messages do |m|
:
= form_for @conversation, url: messenger.conversations_create_path do |f|
[...]
= f.fields_for :participants do |p|
= p.fields_for :messages do |m|
= m.label :content, t('trad'), class: 'control-label'
= m.text_area :content, class:'form-control'
.user-info-block.action-buttons
= f.submit t('trad'), :class => 'btn btn-primary pull-right'
Side note: the (incorrectly implemented) messages
method in Conversation
should be replaced by a simple has_many :through
relation:
has_many :messages, through: :participants
Message needs to belong_to
Conversation directly, since you need to disambiguate when participants have more than one conversation.
So having done that, you can build the conversation's default message in the controller using
@conversation.messages.build(participant: @conversation.participants.first)
That's pretty wordy, so you can add a couple of model methods to reduce the controller call to
@conversation.build_default_message
In this case, you want to create a Conversation, but it needs to create a Message with user input as well. So Conversation needs to accept attributes on behalf of Message. You can do that using accepts_nested_attributes_for
class Conversation
accepts_nested_attributes_for :messages
end
This would allow you to create a Conversation with 1 or more associated Messages using
Conversation.create(
...,
messages_attributes: [
{ participant_id: 1, content: 'question' }
]
)
First of all, I think, you have mistakes in this method:
def messages
self.participants.messages.order(:created_at)
end
since Conversation
has_many :participants
:
self.participants
will return an array, not a single Participant
active record object. So you can't directly call messages
on an array. You need to iterate that array and call messages
on each object.
Use nested form and fields_for
method and accepts_nested_attributes_for
(you can find how to write these from SO or documentation) and post the code code and error what you got. Then someone might be able to help you.
And to use fields_for
:
Since you can't link a message directly to a Conversation
and you need a message field, you need to link a @message
to any participant. you can build a Participant
from current_user
or first user ie User.first
for the @conversation
and then build a @message
for this Participant
.
上一篇: 杰克逊在json中添加了反斜杠
下一篇: 红宝石在轨道上