user3224820
user3224820

Reputation: 211

Associating nested attributes to user

I'm trying to build a small expense tracking app using Rails 4.1. Using devise for authorization. Expense and it's nested attribute, comments belong to a user. The associations are set up in the model and expenses are getting associated with the user. Here's the Expense controller:

class ExpensesController < ApplicationController
    def new
        @expense = Expense.new
        @item = @expense.items.build
        #@comment = @expense.comments.build
    end

    def index
        @expenses = Expense.all
        #@items = Item.where(:expense_id => @expense.id)
    end

    def show
        @expense = Expense.find(params[:id])
        @items = Item.where(:expense_id => @expense.id)
    end

    def create
        @expense = current_user.expenses.new(expense_params)
        respond_to do |format|
            if @expense.save
                ExpenseMailer.expense_submission(@expense).deliver
                format.html { redirect_to @expense, notice: 'Expense Report Submitted.' }
                format.json { render :show, status: :created, location: @expense }
            else
                format.html { render :new }
                format.json { render json: @expense.errors, status: :unprocessable_entity }
            end
        end
    end

    def edit
        @expense = Expense.find(params[:id])
    end

    def update
        @expense = Expense.find(params[:id])
        #@comment = @expense.comments.build
        if @expense.update(expense_params)
            #if @comment.save
                #ExpenseMailer.comments_added(@expense).deliver
                flash[:notice] = "Expense Report Updated"
                redirect_to expenses_path
            #else
            #   flash[:notice] = "Expense Report Updated"
                #redirect_to expenses_path
            ##end
        else
            render 'edit'
        end
    end

The form from where the comment attributes are built looks like:

    <%= nested_form_for (@expense) do |f| %>
      <div class="form-group">
        <%= f.label :state %><br />
        <%= f.select :state, Expense.states, :include_blank => false, class: "form-control" %>
       </div>
       <%= f.fields_for :comments, @expense.comments.build do |comment| %>
        <div class="form-group">
          <%= comment.label :comment%>
          <%= comment.text_area :comment, class: "form-control" %>
        </div>
        <%= comment.hidden_field :commenter %>
      <% end %>
        <%= f.submit "Submit", class: "btn btn-primary" %>
    <% end %>
  </div>
</div>

The @comment.commenter = current_user isn't adding the current user id to the database. Should I include it in the expense controller somewhere?

Upvotes: 1

Views: 280

Answers (2)

Richard Peck
Richard Peck

Reputation: 76774

Model

I just tried to create better code for your strong params, but I couldn't work out how to include the param in your nested attributes

I would therefore recommend using the inverse_of: method in your Comment model to get it sorted properly:

#app/models/expense.rb
Class Expense < ActiveRecord::Base
   belongs_to :user
   has_many :comments, inverse_of: :expense
   accepts_nested_attributes_for :comments
end

#app/models/comment.rb
Class Comment < ActiveRecord::Base
   belongs_to :expense, inverse_of: :comments
   before_create :populate_expense, on: :create

   private

   def populate_expense
      self.commenter_id = self.expense.user_id
   end
end

This should work if you're populating the comments from the accepts_nested_attributes_for directive


Comments

I don't understand why you've created two create actions for both your expenses and comments controllers - the controller action is meant to be independent of the Model

What I'm trying to say is that if you think the comments#create controller action will be invoked by your nested attribute creation, you'd be mistaken - it is only invoked when you send a request to it through the Rails router :)

If you're creating Comments and Expenses separately, you'll be able to use these two different actions; but they won't be invoked by each other. Only Model methods can be invoked by the controller (you shouldn't be calling other controller methods)

If you wanted to create a Comment from the expenses#show page, here's how you'd set it up:

#config/routes.rb
resources :expenses do
   resources :comments #-> domain.com/expenses/:expense_id/comments/new
end

#app/controllers/expenses_controller.rb
Class CommentsController < ApplicationController
   def new
      @expense = Expense.find params[:expense_id]
      @comment = @expense.comments.new
   end

   def create
      @expense = Expense.find params[:expense_id]

      @comment = @expense.comments.new(comment_params)
      @comment.save
   end

   private

   def comment_params
       params.require(:comment).permit(:comment, :params).merge(commenter_id: current_user.id)
   end
end

This will work if you wanted to create a comment from the expenses#show page. If you do this, you need to ensure you are calling the comments#new / comments#create actions, rather than those of the expenses controller

Upvotes: 0

Mischa
Mischa

Reputation: 43298

You have to add:

@comment.commenter = current_user

below that if statement. Like this:

def create
  @article = Expense.find(params[:expense_id])

  if @comment = @expense.comments.create(comment_params)
    @comment.commenter = current_user
    @comment.save

    ExpenseMailer.comments_added(@expense).deliver
    redirect_to expenses_path
  end
end

And then save the comment again. In your current code you're overwriting the @comment object with the newly created object by doing:

@comment = @expense.comments.create(comment_params)

but you haven't set the commenter on that new object anywhere yet.

Upvotes: 2

Related Questions