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 .

链接地址: http://www.djcxy.com/p/38168.html

上一篇: 杰克逊在json中添加了反斜杠

下一篇: 红宝石在轨道上