2

I am building a review application for schools. A user can only review a school if he is logged in.

What you can do right now:

You can write a review and add a rating between 1 - 5. The application saves the review.

What I want it do:

I would like to save the the logged user_id. So a review belongs to a user and a school.

But I am kinda confused on how to do that. Any ideas and guidelines are welcome =)

Review model:

class Review < ActiveRecord::Base
  belongs_to :school
  belongs_to :user
  attr_accessible :content, :rating
end

School model:

class School < ActiveRecord::Base
  has_many :reviews
  ..more code..
end

User model:

class User < ActiveRecord::Base  
  has_many :reviews
  ...more code..
end

The review controller:

class ReviewsController < ApplicationController
    def create
        @school = School.find(params[:school_id])
        @review = @school.reviews.create!(params[:review])
        redirect_to @review.school, notice: "Review has been created."
    end
end

The review form

<%= form_for [@school, Review.new] do |f| %>
        <h3>Write a review</h3>
        <div class="reviews_comments_container">
            <div class="review_box">
                <ol class="radio-rater">
                    <%= f.radio_button(:rating, 1) %>
                    <%= f.radio_button(:rating, 2) %>
                    <%= f.radio_button(:rating, 3) %>
                    <%= f.radio_button(:rating, 4) %>
                    <%= f.radio_button(:rating, 5) %>
                </ol>
            </div>
            <div class="review_box" id="review_textarea">
                <%= f.text_area :content, rows: 4, cols: 70 %>
            </div>
            <div class="review_box">
                <%= f.submit 'Save your review', :class => "btn" %>
            </div>
        </div>
    <% end %>

The migration file:

class CreateReviews < ActiveRecord::Migration
  def change
    create_table :reviews do |t|
      t.text :content
      t.integer :rating
      t.belongs_to :school
      t.belongs_to :user
      t.timestamps
    end
    add_index :reviews, [:school_id, :user_id]
  end
end
  • I guess you mean you'd like to save it in reviews table by including it in create action of ReviewsController ? Then you should include in the create action: @user_id = current_user (in case you have user_id column in reviews table) . – R Milushev Nov 30 '12 at 23:22
  • Yes, thats the basic concept. But how do I write it in rails?? –  Nov 30 '12 at 23:26

2 Answers2

0

If you are using Devise to authenticate users , then the current_user is the object , returned after authentication . In your ReviewsController :

class ReviewsController < ApplicationController
  def create
    @school = School.find(params[:school_id])

    @review = @school.reviews.build(params[:review])
    @review.user_id = current_user

      if @review.save
    redirect_to @review.school, notice: "Review has been created."
       end
  end
end

If you have to include a new column , named user_id in your table 'reviews' , then you should create a new migration and then 'rake db:migrate'. (it seems you have this column according to your migration ).

EDIT : Routes for nested resources :

resources :schools do
  resources :reviews do
end
R Milushev
  • 4,295
  • 3
  • 27
  • 35
  • `@user_id = current_user` won't work. That just creates an instance variable called user_id. There's no way for rails to know it's meant for `@review` that way. – GorrillaMcD Nov 30 '12 at 23:52
  • You are right , it should be @review.user_id = current_user . – R Milushev Nov 30 '12 at 23:56
0

There are a couple different ways to do this. You've got your associations setup properly, so it comes down to your controller. Your :school_id is already set because of the way to did your create call:

@review = @school.reviews.create!(params[:review])

You can set the :user_id manually:

params[:review][:user_id] = current_user.id
@review = @school.reviews.create!(params[:review])

That's assuming you have a current_user method from your login system, which is fairly standard. Your create action could be improved a bit. Try something like this:

def create
    @school = School.find(params[:school_id])
    @review = @school.reviews.new(params[:review])

    #Assuming your authentication provides the current_user method
    @review.user_id = current_user.id

    if @review.save
        redirect_to @review.school, notice: "Review has been created."
    else
        render :new
    end
end

That has the added benefit of better control over error handling if validation fails or the record can't be saved to the database for some reason. Also, you should have a new action in your reviews_controller, which will make it look like this:

class ReviewsController < ApplicationController
    def new
        @school = School.find_by_id(params[:school_id])
        @review = Review.new
    end

    def create
        @school = School.find(params[:school_id])
        @review = @school.reviews.new(params[:review])

        #Assuming your authentication provides the current_user method
        @review.user_id = current_user.id

        if @review.save
            redirect_to @review.school, notice: "Review has been created."
        else
            render :new
        end
    end
end

That way, in your form_for call, you can change the Review.new call to @review, which is way better.

Update:

Sorry, my mistake. As this SO answer shows, it is acceptable to put Review.new in the form_for tag. It seemed kind of hacky to me, but I confirmed it in several places.

Community
  • 1
  • 1
GorrillaMcD
  • 1,884
  • 1
  • 14
  • 22
  • Excellent explanation, I changed the form to this: <%= form_for @review do |f| %> And I get this error: undefined method `model_name' for NilClass:Class –  Dec 01 '12 at 00:01
  • It should be `<%= form_for [@school, @review] do |f| %>` – GorrillaMcD Dec 01 '12 at 00:06
  • Did you add the `new` method to your Reviews Controller? I'll edit the answer to be more specific. – GorrillaMcD Dec 01 '12 at 00:11
  • Yes I added the methods new and create that you provide in the ReviewsController. –  Dec 01 '12 at 00:14
  • Could you post your routes file ? If you are using nested resources , you should make some changes in you routes too. – R Milushev Dec 01 '12 at 00:16
  • @QumaraotBurgas here is a gist over the error: https://gist.github.com/4179704 , And here is my routes: https://gist.github.com/4179710 –  Dec 01 '12 at 00:19
  • I see , your routes are OK . Try <%= form_for [@review.school ,@review] do |f| %> – R Milushev Dec 01 '12 at 00:25
  • Get a different error now: undefined method `school' for nil:NilClass –  Dec 01 '12 at 00:27
  • Well, that's a good thing ;). From that error, you can see that it's not recognizing the model name for `@review`. I'm going to update my answer real quick with some ideas. – GorrillaMcD Dec 01 '12 at 00:32
  • @GorrillaMcD So I changed the form back to: <%= form_for [@school, Review.new] do |f| %> And it work, but is this going effect the application some how? Whats the purpose with new method in reviewscontroller? –  Dec 01 '12 at 00:32
  • `Review.new` creates a new, blank review object. You're generally not supposed to create/modify objects like that in the view. That's what the controller is for. It might not affect anything to leave it like that, but by doing it the right way, you can minimize otential bugs in the future. – GorrillaMcD Dec 01 '12 at 00:35
  • I can see in your create action , that it is used @school.reviews.new(params[:review]) in case there is a separate action 'new' . Try using @school.reviews.build(params[:review]) .Here is [a nice discussion](http://stackoverflow.com/questions/4954313/build-vs-new-in-rails-3) on the topic – R Milushev Dec 01 '12 at 01:05
  • So I fixed my answer @SHUMAcupcake. It turns out I was wrong and it is acceptable to do `<%= form_for [@school, Review.new]...` – GorrillaMcD Dec 01 '12 at 01:08