Skip to content

Instantly share code, notes, and snippets.

@jballanc
Last active August 29, 2015 14:05
Show Gist options
  • Save jballanc/d4b9672881abdec99fb6 to your computer and use it in GitHub Desktop.
Save jballanc/d4b9672881abdec99fb6 to your computer and use it in GitHub Desktop.

Ruby on Rails Interview Questions

Q1.

What is the difference between Ruby's Hash and ActiveSupport's HashWithIndifferentAccess?

Answer

The Hash class in Ruby's core library retrieves values by doing a standard == comparison on the keys. This means that a value stored for a Symbol key (e.g. :my_value) cannot be retrieved using the equivalent String (e.g. 'my_value'). On the other hand, HashWithIndifferentAccess treats Symbol keys and String keys as equivalent so that the following would work:

h = HashWithIndifferentAccess.new
h[:my_value] = 'foo'
h['my_value'] #=> will return "foo"

Q2.

What's the problem with the following controller code? What would the consequence of leaving this code in a production app? How would you fix it?

class MyController < ApplicationController
  def update_options
    available_option_keys = [:first_option, :second_option, :third_option]
    all_keys = params.keys.map(&:to_sym)
    set_option_keys = all_keys & available_option_keys
    set_option_keys.each do |key|
      options[key] = params[key]
    end
  end
end

Answer

Because Symbol objects in Ruby are not garbage collected, it is dangerous to convert user supplied parameters to symbols. An attacker could send a series of requests with random keys that would be turned into symbols, quickly exhausting your server's available memory and taking down your site.

There are two ways that this could could be fixed. The first would be to use slice to eliminate values from the params hash that are not valid option keys. This would look something like:

params.slice(available_option_keys).each do |key|
  options[key] = params[key]
end

The other, some would argue better, option would to simply be to use String keys for your options. Unless you have an extremely large number of possible option keys, you won't actually save that much memory by using Symbol keys instead.

Q3.

What is the problem with the following controller code? How would you fix it?

class CommentsController < ApplicationController
  def users_comments
    posts = Post.all
    comments = posts.map(&:comments).flatten
    @user_comments = comments.filter do |comment|
      comment.author.username == params[:username]
    end
  end
end

Answer

This is a classic example of an "n+1" bug. The first line will retrieve all of the Post objects from the database, but then the very next line will make an additional request for each Post to retrieve the corresponding Comment objects. To make matters worse, this code is then making even more database requests in order to retrieve the Author of each Comment.

This can all be avoided by changing the first line in the method to:

posts = Post.includes(comments: [:author]).all

This will tell ActiveRecord to perform a database join between the tables for Post, Comment, and Author, reducing the number of database requests to just one.

Q4.

What is CSRF? How does Rails protect against CSRF?

Answer

CSRF stands for Cross-Site Request Forgery. This is a form of an attack where the attacker submits a form on your behalf to a different website, potentially causing damage or revealing sensitive information. Since browsers will automatically include cookies for a domain on a request, if you were recently logged in to the target site, the attacker's request will appear to come from a logged-in user (as your session cookie will be sent with the POST request).

In order to protect against CSRF attacks, you can add protect_from_forgery to your ApplicationController. This will then cause Rails to require that a CSRF token is present before accepting any POST, PUT, or DELETE requests. The CSRF token is included as a hidden field in every form created using Rails' form builders. It is also included as a header in GET requests so that other, non-form-based mechanisms for sending a POST can also use it. Attackers are prevented from stealing the CSRF token by browsers' "same origin" policy.

Q5.

How would you define a Person model so that any Person can be assigned as the parent of another Person (as demonstrated in the Rails console below)? What columns would you need to define in the migration creating the table for Person?

irb(main):001:0> john = Person.create(name: "John")
irb(main):002:0> jim = Person.create(name: "Jim", parent: john)
irb(main):003:0> bob = Person.create(name: "Bob", parent: john)
irb(main):004:0> john.children.map(&:name)
=> ["Jim", "Bob"]

For an advanced challenge: Update the Person model so that you can also get a list of all of a person's grandchildren, as illustrated below. Would you need to make any changes to the corresponding table in the database?

irb(main):001:0> sally = Person.create(name: "Sally")
irb(main):002:0> sue = Person.create(name: "Sue", parent: sally)
irb(main):003:0> kate = Person.create(name: "Kate", parent: sally)
irb(main):004:0> lisa = Person.create(name: "Lisa", parent: sue)
irb(main):005:0> robin = Person.create(name: "Robin", parent: kate)
irb(main):006:0> donna = Person.create(name: "Donna", parent: kate)
irb(main):007:0> sally.grandchildren.map(&:name)
=> ["Lisa", "Robin", "Donna"]

Answer

Normally, the target class of an ActiveRecord association is inferred from the association's name (a perfect example of "convention over configuration"). It is possible to override this default behavior, though, and specify a different target class. Doing so, it is even possible to have relationships between two objects of the same class.

This is how it is possible to set up a parent-child relationship. The model definition would look like:

class Person < ActiveRecord::Base
  belongs_to :parent, class: Person
  has_many :children, class: Person, foreign_key: :parent_id
end

It is necessary to specify the foreign_key option for the has_many relationship because ActiveRecord will attempt to use :person_id by default. In the migration to create the table for this model you would need to define, at minimum, a column for the name attribute as well as an integer column for parent_id.

Self-referential relationships can be extended in all the same ways as normal two-model relationships. This even includes has_many ... :through => ... style relationships. However, because we are circumventing Rails' conventions, we will need to specify the source of the :through in the case of adding a grandchild relationship:

class Person < ActiveRecord::Base
  belongs_to :parent, class: Person
  has_many :children, class: Person, foreign_key: :parent_id
  has_many :grandchildren, class: Person, through: :children, source: :children
end

Consequently, since this is still just using the parent_id defined in the first case, no changes to the table in the database are required.

Q6.

What paths (HTTP verb and URL) will be defined by the following line in config/routes.rb?

resources :posts do
  member do
    get 'comments'
  end
  collection do
    post 'bulk_upload'
  end
end

Answer

Using the resource method to define routes will automatically generate routes for the standard seven restful actions:

  1. GET /posts
  2. POST /posts
  3. GET /posts/new
  4. GET /posts/:id/edit
  5. GET /posts/:id
  6. PATCH/PUT /posts/:id
  7. DELETE /posts/:id

Note that Rails also supports the (relatively) new URL verb PATCH for partial updates to records. (In theory, a PUT request should only be valid if the entire record is included in the request).

The extra routes defined inside of the block passed to resources will generate one route valid for individual posts:

GET /posts/:id/comments

and one defined for the top-level resource:

POST /posts/bulk_upload

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment