Skip to content

Instantly share code, notes, and snippets.

@jennli
Last active February 11, 2016 23:38
Show Gist options
  • Save jennli/f1b0a4a71e3848536363 to your computer and use it in GitHub Desktop.
Save jennli/f1b0a4a71e3848536363 to your computer and use it in GitHub Desktop.

Resources

  • generate model and controller in one command
bin/rails g resource support_request name email message:text department done:boolean
  • quickly reset data
bin/rake db:drop db:create db:migrate db:seed

Difference between find and find_by_id

  • find will throw an exception if not found
  • find_by_id returns nil if not found

Association

  • one to one
  • one to many
  • many to many
  • Foreign key
  • indexing

Migration

  • Defines database table / model structures
  • use the following command to create a migration file when try to add in associations
bin/rails g migration AddProjectToTasks project:references
  • the migration will look like this
add_reference :tasks, :project, index: true, foreign_key: true

Nested resources

   # # the PATH for this URL (index action) is the same as the PATH for the
  # # create action (with POST) so we can just use the one we defined for the
  # # create which is `questions_path`
  # get "/questions" => "questions#index"
  resources :questions do
    get   :search,    on: :collection
    patch :mark_done, on: :member
    post  :approve

    # By defining `resources :answers` nested inside `resources :questions`
    # Rails will defined all the answers routes prepended with
    # `/questions/:question_id`. This enables us to have the question_id handy
    # so we can create the answer associated with a question with `question_id`
    
    resources :answers, only:[:create, :destroy]

  end
  resources :answers
+----+-------+-------------+------------+------------+------------+
| id | body  | question_id | created_at | updated_at |      xx..  |
+----+-------+-------------+------------+------------+------------+
|    |       |             |            |            |            |
+----+-------+-------------+------------+------------+------------+
  • question_id: foreign key constraint, index

  • create rails models with association

> bin/rails g model answer title:text question:references
  • migration will contain the following code
class CreateAnswers < ActiveRecord::Migration
  def change
    create_table :answers do |t|
      t.text :body
      # t.references :question wil generate an integer fild that is called 
      # question_id (rails convention)
      # index: true willatomatically create an index on the 'question_id'
      # field
      # foreign_key: true will automatically create a foreign key constraint
      # for the 'question_id' field
      t.references :question, index: true, foreign_key: true

      t.timestamps null: false
    end
  end
end
  • execute the migrate
bin/rake db:migrate
  • the database will generate a constraint and an index for the foreign key: question_id
  • the answer model now looks like this:
class Answer < ActiveRecord::Base
  belongs_to :question
end
  • question model:
class Question < ActiveRecord::Base
  # This will establish a `has_many` association with answers. This assumes
   # that your answer model has a `question_id` integer field that references
   # the question. with has_many `answers` must be plural (Rails convention).
   # We must pass a `dependent` option to maintain data integrity. The possible
   # values you can give it are: :destroy or :nullify
   # With :destroy: if you delete a question it will delete all associated answers
   # With :nullify: if you delete a question it will update the `question_id` NULL
   # =>             for all associated records (they won't get deleted)
  
  has_many :answers, dependent: :destroy
  ...
  • now we can create records and specify the association like this:
Answer.create(body: "Hey there!", question: q)
  • dependent: :destroy will allow the child record to be deleted before parent record is about to be deleted
2.2.3 :004 >   q = Question.last
  Question Load (0.9ms)  SELECT  "questions".* FROM "questions"  ORDER BY "questions"."id" DESC LIMIT 1
+-----+-------------+-------------+-------------+--------------+------------+
| id  | title       | body        | created_at  | updated_at   | view_count |
+-----+-------------+-------------+-------------+--------------+------------+
| 207 | Use inpu... | this bod... | 2016-02-... | 2016-02-0... | 0          |
+-----+-------------+-------------+-------------+--------------+------------+
1 row in set
2.2.3 :005 > Answer.create(body: "Hey there!", question: q)
   (0.1ms)  BEGIN
  SQL (0.6ms)  INSERT INTO "answers" ("body", "question_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"  [["body", "Hey there!"], ["question_id", 207], ["created_at", "2016-02-10 20:03:33.520373"], ["updated_at", "2016-02-10 20:03:33.520373"]]
   (1.2ms)  COMMIT
+----+------------+-------------+---------------------+---------------------+
| id | body       | question_id | created_at          | updated_at          |
+----+------------+-------------+---------------------+---------------------+
| 4  | Hey there! | 207         | 2016-02-10 20:03... | 2016-02-10 20:03... |
+----+------------+-------------+---------------------+---------------------+
1 row in set
2.2.3 :006 > q.destroy
   (0.2ms)  BEGIN
  Answer Load (0.2ms)  SELECT "answers".* FROM "answers" WHERE "answers"."question_id" = $1  [["question_id", 207]]
  SQL (0.1ms)  DELETE FROM "answers" WHERE "answers"."id" = $1  [["id", 4]]
  SQL (0.3ms)  DELETE FROM "questions" WHERE "questions"."id" = $1  [["id", 207]]
   (6.3ms)  COMMIT
+-----+-------------+-------------+-------------+--------------+------------+
| id  | title       | body        | created_at  | updated_at   | view_count |
+-----+-------------+-------------+-------------+--------------+------------+
| 207 | Use inpu... | this bod... | 2016-02-... | 2016-02-0... | 0          |
+-----+-------------+-------------+-------------+--------------+------------+
1 row in set
  • if have nullify instead of destroy for the association constraint:
has_many :answers, dependent: :nullify
  • when deleting the parent record:
2.2.3 :001 > a = Answer.new(body: "abc")
+----+------+-------------+------------+------------+
| id | body | question_id | created_at | updated_at |
+----+------+-------------+------------+------------+
|    | abc  |             |            |            |
+----+------+-------------+------------+------------+
1 row in set
2.2.3 :002 > a.question = Question.last
  Question Load (0.6ms)  SELECT  "questions".* FROM "questions"  ORDER BY "questions"."id" DESC LIMIT 1
+-----+-------------+-------------+-------------+--------------+------------+
| id  | title       | body        | created_at  | updated_at   | view_count |
+-----+-------------+-------------+-------------+--------------+------------+
| 208 | Amazing ... | amazing ... | 2016-02-... | 2016-02-0... | 3          |
+-----+-------------+-------------+-------------+--------------+------------+
1 row in set
2.2.3 :003 > a.save
   (0.2ms)  BEGIN
  SQL (0.6ms)  INSERT INTO "answers" ("body", "question_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"  [["body", "abc"], ["question_id", 208], ["created_at", "2016-02-10 19:55:57.242718"], ["updated_at", "2016-02-10 19:55:57.242718"]]
   (1.3ms)  COMMIT
true
2.2.3 :004 > Question.last.destroy
  • the child will be updated before the parent is destroyed
  Question Load (0.5ms)  SELECT  "questions".* FROM "questions"  ORDER BY "questions"."id" DESC LIMIT 1
   (0.1ms)  BEGIN
  SQL (0.2ms)  UPDATE "answers" SET "question_id" = NULL WHERE "answers"."question_id" = $1  [["question_id", 208]]
  SQL (0.3ms)  DELETE FROM "questions" WHERE "questions"."id" = $1  [["id", 208]]
   (6.3ms)  COMMIT
+-----+-------------+-------------+-------------+--------------+------------+
| id  | title       | body        | created_at  | updated_at   | view_count |
+-----+-------------+-------------+-------------+--------------+------------+
| 208 | Amazing ... | amazing ... | 2016-02-... | 2016-02-0... | 3          |
+-----+-------------+-------------+-------------+--------------+------------+
1 row in set
  • create an answer controller
bin/rails g controller answers

create

  • controller
class AnswersController < ApplicationController

  def create
    @question = Question.find params[:question_id]
    answer_params = params.require(:answer).permit(:body)
    @answer   = Answer.new answer_params
    @answer.question = @question
    if @answer.save
      redirect_to question_path(@question), notice: "Answer created!"
    else
      render "/questions/show"
    end
   # render json: params
  end
end
  • in question's show view, add in an answer form
<%= form_for [@question, @answer] do |f| %>
<div>
    <%= f.label :body %>
    <%= f.text_area :body %>
  </div>
  <%= f.submit %>
<% end %>
  • model
class Answer < ActiveRecord::Base
  belongs_to :question
 # unique answer per question
  validates :body, presence: true, uniqueness:{scope: :question_id}

end

destroy

  • view
<% @question.answers.each do |ans| %>
  <%= ans.body %>
  <%= link_to "Delete", question_answer_path(@question, ans),
                        method: :delete,
                        data: {confirm: "Are you sure?"} %>
  <hr>
<% end %>
  • controller
def destroy
    # question = Question.find params[:question_id]
    # answer   = question.find params[:id]
    answer = Answer.find params[:id]
    answer.destroy
    redirect_to question_path(params[:question_id]), notice: "Answer deleted!"
  end

best practise: lower dependencies between components

  • add category to question
  • question model:
 belongs_to :category
...
   def category_name
    category.name if category
  end
...
  • view
  <%= f.collection_select(:category_id, Category.all, :id, :name, prompt: true) %>
  • controller
    question_params = params.require(:question).permit([:title, :body, :category_id])

avoid triple nested resources

  • have comments for each answer which blongs to questions
  # We do this to avoid triple nesting comments under `resources :answers` within
    # the `resources :questions` as it will be very cumbersome to generate routes
    # and use forms. We don't need another set of `answers` routes in here so
    # pass the `only: []` option to it.
    resources :answers, only: [] do
      resources :comments, only: [:create, :destroy]
    end

allow Inner Join

# This enables us to access all comments created for all the question's answers. 
# This generated a single SQL statement with 'INNER JOIN' to accomplish it
has_many :comments, through: :answers
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment