Ruby on Rails interview questions and answers for 2025

hero image

Ruby on Rails Interview Questions for Freshers and Intermediate Levels:

1.

What are the key features of Ruby on Rails, and how do they contribute to efficient web application development?

Answer

Ruby on Rails promotes efficient web application development through several key features:

  • MVC Architecture: Rails uses the Model-View-Controller pattern to separate concerns, keeping the data (Model), user interface (View), and application logic (Controller) distinct. This structure simplifies maintenance and collaborative development.
  • Convention over Configuration (CoC): By adhering to predefined conventions, Rails minimizes the need for extensive configuration. Developers can focus on building features rather than setting up the framework, which speeds up the development process.
  • Don’t Repeat Yourself (DRY): Rails encourages writing reusable and modular code. This approach reduces redundancy and makes the codebase easier to update and debug.
  • Built-in Tools and Libraries: With features like scaffolding and migrations, Rails automates common tasks (such as generating boilerplate code and managing database schema changes). Additionally, a rich ecosystem of gems allows developers to integrate extra functionalities quickly.
  • Integrated Testing Support: Rails comes with a robust testing framework that promotes test-driven development (TDD). This ensures that applications remain reliable and maintainable as they grow.

 

Together, these features help developers build robust, scalable web applications more efficiently, making Rails a popular choice for both startups and established enterprises.

2.

How do the roles of Ruby as a programming language and Ruby on Rails as a web framework differ in web application development, and how do they work together to facilitate efficient and scalable solutions?

Answer

Ruby is a versatile, general-purpose programming language that offers fundamental language constructs and flexibility. In contrast, Ruby on Rails is a web framework built on Ruby that provides a structured environment specifically for web development. Rails leverages Ruby’s capabilities by adding:

  • Convention over Configuration: Simplifies setup by following standard practices, reducing the need for repetitive configuration.
  • MVC Architecture: Organizes code into Models, Views, and Controllers, which improves maintainability and scalability.
  • Built-in Tools: Offers features like ActiveRecord for database interactions, routing, and migrations to streamline common development tasks.

 

Together, Ruby’s expressive language features and Rails’ structured, opinionated approach facilitate rapid development of efficient, scalable web applications.

3.

How does Ruby on Rails’ philosophy differ from frameworks like Django, Laravel, or Express.js?

Answer

Ruby on Rails follows the “Convention over Configuration” (CoC) and “Don’t Repeat Yourself” (DRY) philosophies. This means it assumes sensible defaults and minimizes the need for configuration. In contrast:

  • Django (Python) emphasizes explicit configurations and uses the MTV (Model-Template-View) pattern.
  • Laravel (PHP) is built on a similar MVC architecture and has extensive built-in functionality.
  • Express.js (JavaScript) is minimalist and flexible, providing more control to the developer, but less structure than Rails.

4.

Explain the Model-View-Controller (MVC) architecture in Rails. What is it, and how does it work?

Answer

The MVC architecture divides an application into three components:

  • Model: Represents the application’s data and logic (typically mapped to database tables using ActiveRecord).
  • View: Displays the data to the user (HTML, CSS, JavaScript).
  • Controller: Handles user input, processes it (with the help of models), and returns an appropriate response (typically a view).

In Rails, the controller receives requests, interacts with models, and renders views.

5.

What are the common types of databases used with Rails?

Answer

Rails supports a variety of databases, but the most commonly used ones are:

  • SQLite: Used for development and testing (default for new Rails apps).
  • PostgreSQL: A popular relational database known for its stability and performance, widely used in production.
  • MySQL: Another relational database often used in production.
  • SQL Server: Supported for enterprise applications.
  • Others: MongoDB and other NoSQL databases can also be used with Rails through specific gems.
6.

What is ActiveRecord in Ruby on Rails?

Answer

ActiveRecord is the Object-Relational Mapping (ORM) layer in Rails. It provides a way to interact with databases by representing database tables as Ruby classes (models). ActiveRecord automatically maps table rows to model instances and supports database operations like querying, creating, updating, and deleting records.

7.

How does ActiveRecord simplify database interactions in Rails?

Answer

ActiveRecord abstracts away complex SQL queries and provides an easy-to-use Ruby interface for interacting with the database. It enables developers to create, update, and query database records using Ruby syntax rather than writing raw SQL. This makes the code cleaner and reduces the potential for errors.

8.

What is the purpose of the Gemfile in a Rails project?

Answer

The Gemfile is a file used to specify which Ruby gems (libraries) are required for the application. It defines the dependencies and their versions. When you run bundle install, Bundler reads this file and installs the necessary gems.

9.

What is a Rails migration?

Answer

A Rails migration is a script that allows you to make changes to the database schema over time. Migrations are used to create, modify, or delete tables and columns, and they provide version control for database changes. Rails generates migration files that can be run using the rails db:migrate

10.

How are Rails migrations used to manage database schema changes?

Answer

Migrations track changes to the database schema and can be applied or rolled back using commands like rails db:migrate and rails db:rollback. This helps manage changes in a version-controlled and structured manner. Each migration file contains methods like create_table, add_column, or remove_column to define schema changes.

11.

How does Rails handle validations in models?

Answer

Rails provides built-in validation methods to ensure that the data entered into models is valid before it is saved to the database. Common validations include validates_presence_of, validates_uniqueness_of, and validates_numericality_of. Validations are typically added in the model file.

You can also use a custom validation method by using validate :name_of_method.

12.

What is Rake in Rails?

Answer

Rake is a task management tool written in Ruby. It allows you to define and execute tasks, which can automate processes such as database migrations, test runs, and deployments. Rake tasks are defined in .rake files, and they can be executed via the rake command in the terminal.

13.

How is Rake used to manage tasks in Rails?

Answer

In Rails, Rake is used to automate tasks like database setup, migrations, test execution, and asset precompilation. For example, you can use rake db:migrate to apply database migrations, or rake test to run the test suite.

In more recent versions of rails you can simply replace rake for rails and do it like this: rails db:migrate

14.

What is an ORM in Rails?

Answer

ORM stands for Object-Relational Mapping, which is a technique for converting data between incompatible type systems (such as between an object-oriented language like Ruby and a relational database). In Rails, ActiveRecord serves as the ORM, mapping database tables to Ruby objects and providing methods for querying and manipulating data.

15.

Could you explain scaffolding in Rails?

Answer

ORM stands for Object-Relational Mapping, which is a technique for converting data between incompatible type systems (such as between an object-oriented language like Ruby and a relational database). In Rails, ActiveRecord serves as the ORM, mapping database tables to Ruby objects and providing methods for querying and manipulating data.

16.

Could you explain scaffolding in Rails?

Answer

Scaffolding in Rails is a quick way to generate boilerplate code for a resource (such as a model, view, and controller). Using the rails generate scaffold command, Rails creates all the necessary files to create, read, update, and delete records for a specific resource. It’s a great starting point for building CRUD interfaces.

17.

What is the difference between load and require in Ruby?

Answer

Both load and requireare used to include external files in Ruby, but they differ in behavior:

  • require: Ensures that a file is only loaded once during the execution of a program.
  • load: Loads the file every time it is called, even if it was previously loaded.
18.

What are the limitations of Ruby on Rails compared to other web frameworks?

Answer

Rails is a full-stack framework with many built-in features, which can be both an advantage and a disadvantage. Some limitations include:

  • Performance: Rails is known for being less performant than more lightweight frameworks like Express.js.
  • Scalability: While Rails can be scaled, it may require more effort and careful planning compared to other frameworks.
  • Flexibility: Rails is opinionated, which can be limiting when you need to diverge from the prescribed way of doing things.
19.

What is ActiveRecord in Ruby on Rails, and what is its role?

Answer

ActiveRecord is the ORM layer in Rails. It maps database tables to Ruby classes and provides methods for interacting with the database. It simplifies data manipulation by allowing developers to perform CRUD operations without writing SQL queries.

20.

What are Rails view helpers, and how are they used?

Answer

Rails view helpers are modules used to encapsulate reusable code that can be used in views. Helpers are commonly used for formatting data or generating HTML elements (like links or forms). For example, link_to is a helper method that generates an HTML link.

21.

How do you implement caching in a Rails application?

Answer

Caching in Rails can be implemented in several ways:

  • Fragment Caching: Cache parts of views to avoid re-rendering the same data, which is particularly useful for complex views with repetitive data. You can use cache in views:

<% cache @post do %> <%= render @post %> <% end %>

  • Action Caching: Cache the output of controller actions (including HTTP response headers). Use caches_action in the controller:

class PostsController < ApplicationController caches_action :index end

  • Page Caching: Cache entire pages (e.g., a static home page) to speed up access to frequently requested URLs. Page caching is handled by placing the page in a public directory, though it is less commonly used with modern Rails.
  • Russian Doll Caching: A nested caching strategy where fragments are cached within other fragments, reducing the need for recalculation when minor changes occur.
  • Low-Level Caching: Rails supports caching of arbitrary objects via the Rails.cache API, which allows caching data at a granular level.
22.

What is the difference between fragment, page, and action caching in Rails?

Answer
  • Fragment Caching: Caches individual parts of a view (e.g., a sidebar, or a partial) using cache blocks. Useful for caching reusable components of a page.
  • Page Caching: Caches entire responses to requests. Ideal for pages that don’t change often and don’t require authentication. This is more commonly used in static websites.
  • Action Caching: Similar to page caching but more selective, as it can cache the entire controller action, including headers, and still allow for dynamic content to be injected into the page (e.g., different headers for different users).
23.

What is the difference between include and extend in Ruby?

Answer
  • include: Used to add methods from a module to an instance of a class. It adds methods to the class’s instances, allowing objects to call them.
    module Greeting
    def hello
    "Hello!"
    end
    end
    `class Person
    include Greeting
    end`
    
    `person = Person.new
    person.hello # => "Hello!"`
  • extend: Used to add methods from a module to a class itself (i.e., class-level methods).
    module Greeting
    def hello
    "Hello!"
    end
    end
    `class Person
    extend Greeting
    end`
    
    `Person.hello # => "Hello!"`
24.

How does find differ from where in ActiveRecord queries?

Answer
  • find: Used to retrieve a record by its primary key (ID). It raises an ActiveRecord::RecordNotFound exception if no record is found.
  • where: Returns an ActiveRecord::Relation object, allowing you to chain additional query methods. It does not raise an exception if no records are found.

Post.where(title: "My Post") # Returns all posts with the given title

25.

What is the difference between symbol and string in Ruby, and when should you use each?

Answer
  • String: A mutable object, meaning its value can be changed. It is often used for general-purpose text that may change over time.

name = "John" name << " Doe" # Modifies the string

  • Symbol: An immutable, unique identifier, typically used for identifiers, keys in hashes, or as method names. Symbols are more efficient in terms of memory and performance when used repeatedly. status = :active

:active will always refer to the same object in memory

When to use: Use strings when working with dynamic content, and symbols when the content is used as a key, identifier, or constant that won’t change.

26.

What is the difference between before_action and after_action callbacks in Rails controllers?

Answer
  • before_action: Runs before a controller action is executed. It is commonly used for tasks such as authentication or setting instance variables.

before_action :authenticate_user

  • after_action: Runs after a controller action is executed, often used for tasks like logging, auditing, or modifying the response.

after_action :log_request

27.

How does render differ from redirect_to in Rails?

Answer
  • render: Renders a view or partial directly in the current request cycle, returning HTML to the user without initiating a new HTTP request.

render :show # Renders the 'show' view

  • redirect_to: Sends an HTTP redirect response to the browser, which triggers a new request to the URL provided. It is often used to navigate to another action or controller.

redirect_to posts_path # Redirects to the index action of posts controller

28.

How does assert differ from refute in Rails testing frameworks?

Answer
  • assert: Used to check that a condition is true. If the condition is false, the test will fail.

assert user.valid? # Passes if user is valid

  • refute: Used to check that a condition is false. If the condition is true, the test will fail.

refute user.invalid? # Passes if user is not invalid

29.

How do you create a model in Rails with fields like title, content, and published_at?

Answer

To create a model with those fields, you would run a migration and use rails generate to create the model rails generate model Post title:string content:text published_at:datetime This will generate a migration file with the appropriate field types and the model file app/models/post.rb. You can then run the migration with:

rails db:migrate

Ruby on Rails Interview Questions for Experienced Levels:

1.

How does Rails manage security concerns like CSRF, SQL injection, and XSS?

Answer

CSRF (Cross-Site Request Forgery) Protection

  • What it is: An attack where a malicious site triggers actions on behalf of an authenticated user.
  • How Rails protects:
    • CSRF Token: Rails includes a CSRF token in forms to verify requests come from the legitimate user.
    • Automatic Protection: Enabled by default with protect_from_forgery with: :exception in ApplicationController.

SQL Injection Protection

  • What it is: An attack where malicious input is used to manipulate SQL queries.
  • How Rails protects:
    • ActiveRecord: Rails uses parameterized queries to sanitize inputs.
    • Example:
      # This is safe
      User.where(email: params[:email])
      
      # This is not
      User.where("email = #{params[:email]}")

XSS (Cross-Site Scripting) Protection

  • What it is: An attack where malicious scripts are injected into web pages.
  • How Rails protects:
      • HTML Escaping: Automatically escapes user-generated content in views to prevent script execution.
      • Sanitization: Use sanitize or strip_tags to remove harmful HTML.
      • Example:
        <%= @user.name %> <!– Escaped content –>

 

Rails provides these protections by default and encourages safe practices like using ActiveRecord queries and HTML escaping in views.

2.

What is the difference between includes, joins, and eager_load in ActiveRecord?

Answer

Difference Between includes, joins, and eager_load in ActiveRecord:

  1. includes
  • Purpose: Prevents N+1 query problems by eager loading associated records.
  • When to use: When you need to access associated records and want to avoid extra queries.
  • How it works: Rails loads the associated records in separate queries.

 

Example:

This will load all posts and their associated comments in two queries.

@posts = Post.includes(:comments).all
  1. joins
  • Purpose: Performs an SQL INNER JOIN between two or more tables to retrieve records.
  • When to use: When you need to filter or order based on attributes of an associated table.
  • How it works: Joins tables and filters records in a single query.

 

Example:

This will fetch posts that have approved comments.

@posts = Post.joins(:comments).where(comments: { status: 'approved' })
  1. eager_load
  • Purpose: Eager loads associations using LEFT OUTER JOIN to fetch associated records in a single query.
  • When to use: When you want to avoid N+1 queries but need to load associations in a single query.
  • How it works: Combines the behavior of includes and joins, but uses a LEFT OUTER JOIN.

 

Example:

This loads posts with comments in one query, even when filtering by the comments’ status.

@posts = Post.eager_load(:comments).where(comments: { status: 'approved' })

 

Summary:

includes: Prevents N+1 queries with multiple queries and when compared with eager_load, is more flexible and determines the best strategy (preloading or eager loading) based on your query.

  • joins: Performs an inner join for filtering based on associated data.
  • eager_load: Eager loads associations with a single query using a LEFT OUTER JOIN. It always uses a SQL join, which may fetch more data than needed and can be slower for large datasets.
3.

Explain the concept of polymorphic associations in Rails with an example.

Answer

Polymorphic Associations in Rails

Concept:

Polymorphic associations allow a model to belong to more than one other model using a single association. This enables a model to reference different models dynamically without needing to create multiple foreign keys.

How it works:

  • A polymorphic association involves two main components:
    • Polymorphic belongs_to association in the child model.
    • has_many or has_one association in the parent models.
  • The child model stores the type of the associated model (via a string column) and the ID (via an integer column). These columns are typically named <model_name>_type and <model_name>_id.

Example:

Consider a Comment model that can belong to either a Post or a Photo.

Migration for the Comment model:

class CreateComments < ActiveRecord::Migration[6.1]
def change
create_table :comments do |t|
t.text :content
t.references :commentable, polymorphic: true, null: false
t.timestamps
end
end
end

Models:

  • Comment model:
    class Comment < ApplicationRecord
    belongs_to :commentable, polymorphic: true
    end
  • Post model:
    class Post < ApplicationRecord
    has_many :comments, as: :commentable
    end
  • Photo model:
    class Photo < ApplicationRecord
    has_many :comments, as: :commentable
    end

Usage Example:

  • A Post can have many comments:

    post = Post.find(1)
    post.comments.create(content: "Great post!")

     

  • A Photo can also have many comments:

    photo = Photo.find(1)
    photo.comments.create(content: "Nice photo!")
  • Each comment will store a commentable_type (either “Post” or “Photo”) and commentable_id to reference the correct parent model.

 

Summary:

  • Polymorphic associations allow a model to be associated with multiple other models.
  • They use a single pair of columns (<model_name>_type and <model_name>_id) to dynamically link to the associated models.
  • This approach is useful when a model (like Comment) can belong to several different types of objects (like Post or Photo).
4.

What are Rails concerns, and how do they promote code reusability?

Answer

Rails Concerns

Concept:

Rails concerns are a way to extract and reuse common functionality across multiple models, controllers, or other classes. They help promote code reusability by keeping related methods in separate, modular files, which can be included in various parts of an application.

How They Promote Reusability:

  • Separation of concerns: Concerns help separate the responsibilities of different components, making the codebase cleaner and easier to maintain.
  • Modularity: Concerns allow developers to group related methods, so they can be reused in multiple places without duplication.
  • Avoid code repetition: By extracting common functionality into concerns, the need to duplicate methods across different classes is minimized.

Usage:

  1. Create a concern:
    • Concerns are typically stored in the app/models/concerns or app/controllers/concerns directory.
    • To create a concern, use ActiveSupport::Concern, which provides extra functionality like included and extended blocks.

    Example: Model Concern (app/models/concerns/trackable.rb)

    module Trackable
    extend ActiveSupport::Concern
    
    included do
    before_create :track_creation
    end
    
    def track_creation
    # custom tracking logic
    puts "Record created at #{Time.now}"
    end
    end

     

  2. Include a concern in a model:
    • Once defined, a concern is included in a model to provide shared behavior.

    Example:

    class User < ApplicationRecord
    include Trackable
    end


    Now, the User model has the track_creation method without directly defining it inside the model.

  3. Use in Controllers: Concerns can also be used in controllers to handle shared logic like authentication or authorization.Example: Controller Concern (app/controllers/concerns/authenticable.rb)
    module Authenticable
    extend ActiveSupport::Concern
    
    def authenticate_user
    # authentication logic
    end
    end

    Example:

    class ApplicationController < ActionController::Base
    include Authenticable
    end

    Benefits:

    • Code DRYness: Concerns prevent duplication of common code in models and controllers, making it easier to maintain and extend.
    • Organized code: They help keep models and controllers focused on their primary responsibilities while moving shared logic to a separate module.
    • Flexibility: Concerns can be used in multiple classes, promoting flexible and reusable code.

    Summary:

    • Concerns allow you to define reusable, modular pieces of logic that can be shared across multiple models, controllers, or other classes.
    • They promote code reusability by reducing duplication and improving maintainability, making the application easier to scale and manage.
5.

How do you optimize a Rails application for performance?

Answer

Optimizing a Rails Application for Performance

To ensure your Rails application performs efficiently, various strategies can be implemented across the codebase, database, caching, and infrastructure. Below are key techniques to optimize a Rails app:

  1. Database Optimization:
  • Indexes: Add appropriate indexes to frequently queried columns to speed up search operations.
    • Example: add_index :posts, :user_id
  • Avoid N+1 Queries: Use eager loading (includes) to load associated records in a single query.
    • Example: Post.includes(:comments).all
  • Database Queries: Optimize queries with select to load only the needed columns.
    • Example: Post.select(:id, :title).where(published: true)
  • Query Caching: Rails caches the result of queries by default. Utilize it where applicable, but be mindful of stale data.
  1. Caching:
  • Fragment Caching: Cache specific parts of views to avoid recomputing expensive operations (e.g., HTML fragments).
    • Example: cache @post
  • Page Caching: Cache entire pages for fast retrieval of static content.
    • Example: Use caches_page in the controller.
  • Action Caching: Cache the result of controller actions.
  • Low-Level Caching: Use Rails’ Rails.cache for manual caching of computed data.
  1. Background Jobs:
  • Offload long-running tasks (e.g., email sending, image processing) to background workers using tools like Sidekiq, Resque, or DelayedJob. This prevents blocking the main request-response cycle.
  1. Asset Pipeline & Precompiling:
  • Precompile Assets: Precompile JavaScript and CSS files to reduce the size of assets sent to the browser.
    • Example: RAILS_ENV=production bin/rails assets:precompile
  • Use Webpacker for modern JS bundling (Webpack), optimizing asset delivery.
  1. Optimize Views:
  • Reduce View Complexity: Minimize complex logic in views; keep views focused on rendering data.
  • Use Partial Caching: Cache partials or expensive view components.
  • Limit DOM Manipulation: Use tools like StimulusJS or Hotwire to reduce client-side rendering load.
  1. Code and Memory Optimization:
  • Profiling: Use tools like Bullet, New Relic, or Rails profiler to detect bottlenecks, such as unnecessary queries or memory-heavy actions.
  • Memory Management: Monitor and reduce memory usage with tools like derailed_benchmarks to identify areas with excessive memory consumption.
  1. Use Content Delivery Networks (CDNs):
  • Offload static assets (e.g., images, JavaScript, CSS) to a CDN to reduce server load and improve load times for users across the globe.
  1. Concurrency and Threading:
  • Optimize for Concurrency: In production, use multi-threading to handle multiple requests concurrently. Configure your application server (e.g., Puma) to handle multiple threads per process.
  • Connection Pooling: Use connection pooling to efficiently handle database connections, especially in multi-threaded applications.
  1. Optimize Garbage Collection:
  • Configure Ruby’s garbage collector to improve performance by reducing GC pauses in production. Use tuning options like RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR.
  1. Use a Content Management System (CMS):
  • For large applications, consider leveraging a CMS like Spina or RefineryCMS to streamline and optimize static content management.
  1. Horizontal and Vertical Scaling:
  • Horizontal Scaling: Add more application servers to distribute traffic load, especially for high-traffic apps.
  • Vertical Scaling: Scale up resources like CPU and RAM on your servers when needed.

Summary:

Optimizing a Rails app involves efficient database queries, effective caching strategies, offloading work to background jobs, optimizing assets, and improving overall code and memory usage. Combine these strategies with scaling solutions to ensure your application remains fast and scalable as user demand grows.

6.

Is Rails scalable?

Answer

Yes, Rails is scalable, but like any framework, the way you scale your application depends on how it’s architected and the tools you use alongside it. Rails provides a solid foundation for building scalable applications, but optimizing performance and ensuring scalability often requires thoughtful strategies.

Key Factors for Scaling Rails Applications:

  1. Vertical Scaling (Scaling Up):
  • Upgrade Hardware: Increase the resources (CPU, memory) of your current server. This can help handle more traffic, but there are limits to this approach. It’s useful for smaller to medium-sized applications.
  1. Horizontal Scaling (Scaling Out):
  • Multiple Servers: Rails can scale horizontally by distributing traffic across multiple application servers. Using a load balancer to distribute traffic across different servers helps in handling more users and requests.
  • Clustering: You can run multiple application instances and split the load between them to scale efficiently.
  1. Database Scalability:
  • Read-Write Splitting: Split read and write operations across different databases (master-slave or primary-replica), where the write operations go to the master database, and read queries are handled by replicas.
  • Sharding: In some cases, it’s necessary to shard your database, splitting data across multiple databases based on some partitioning key (e.g., user ID).
  • Database Connection Pooling: Properly configure connection pooling to handle a large number of simultaneous database connections.
  1. Caching:
  • Caching Layers: Implement caching strategies like fragment caching, action caching, and low-level caching to reduce database load and speed up response times.
  • Distributed Caching: Use distributed caching systems like Memcached or Redis to store cached data and improve read performance.
  1. Background Jobs:
  • Offload time-consuming tasks like sending emails, processing images, or data imports to background job queues (e.g., Sidekiq, Resque, DelayedJob). This prevents blocking the main request-response cycle and keeps your application responsive.
  1. Asynchronous Processing:
  • Use asynchronous techniques for tasks that don’t need to be executed in real-time. This can help handle large-scale traffic and improve user experience.
  • Rails can handle asynchronous requests with tools like Action Cable (for WebSockets) and Active Job.
  1. Content Delivery Network (CDN):
  • Offload Static Assets: Use a CDN to serve static assets (images, JavaScript, CSS) globally, reducing server load and speeding up delivery to users worldwide.
  1. Service-Oriented Architecture (SOA) and Microservices:
  • For large applications, breaking down the system into smaller, independent services can help improve scalability. This means separating concerns (e.g., user authentication, payments, notifications) into different services, possibly with different databases or infrastructures.
  1. Concurrency and Multi-threading:
  • In production environments, use an application server like Puma that supports multi-threading and concurrency. This helps process more requests with fewer resources.
  1. Efficient Queries:
  • Optimize ActiveRecord queries by using techniques like eager loading (includes) to avoid N+1 query problems and ensuring database queries are as efficient as possible.
  1. Microservices Architecture:
  • For very large-scale applications, separating the system into independent services with well-defined APIs (using tools like gRPC, GraphQL, or REST) allows you to scale specific components as needed.

Challenges to Consider:

  • Memory Consumption: Rails applications can be memory-intensive, and scaling vertically may require significant resources.
  • Concurrency: Ruby’s global interpreter lock (GIL) limits true multi-threading within a single process. To handle this, Rails can rely on multiple processes or use threads cautiously.
  • State Management: Handling session and user data across multiple instances can be tricky and may require centralized solutions like Redis for session storage.

Conclusion:

Rails can scale effectively, but to truly handle high traffic, you need to combine horizontal scaling, caching, background processing, and database optimization strategies. With the right architecture, tools, and practices, Rails can be used to build applications that scale well with user demand.

7.

What is a Rails engine?

Answer

A Rails engine is a mini-application that can be embedded within a larger Rails application, allowing you to modularize functionality and share reusable code across multiple projects. It’s essentially a self-contained component that behaves like a mini Rails application, with its own models, views, controllers, and routes.

Key Points:

  • Modularization: Rails engines help break down an application into smaller, reusable components (e.g., authentication, admin panels, payment processing).
  • Reusable: They can be packaged as gems and shared between applications.
  • Encapsulation: Engines encapsulate logic and resources, avoiding conflicts in the main application.

Example:

For example, a gem like Spree Commerce is a Rails engine that adds e-commerce functionality to a Rails app. You can install and mount the engine, and it seamlessly integrates with the host application.

Mounting an Engine:

# In the host application's routes.rb
mount Spree::Core::Engine, at: "/shop"

In summary, a Rails engine is a powerful tool for reusing and modularizing code, making it easier to maintain and scale applications.

8.

How would you handle N+1 query issues in a complex Rails application?

Answer

To handle N+1 query issues in a complex Rails application, you need to minimize the number of database queries triggered when iterating over a collection of objects. The problem arises when Rails performs an additional query for each associated record, resulting in inefficient database access.

Solutions:

  1. Eager Loading (includes):
    • Use includes to load associated records in one query to prevent the N+1 query problem.
    • Example:
      # Without eager loading (N+1 problem)
      posts = Post.all
      posts.each { |post| puts post.comments.count }
      
      # With eager loading
      posts = Post.includes(:comments).all
      posts.each { |post| puts post.comments.count }
  2. Select Specific Columns (select) “This doesn’t directly address N + 1 problem but it can indirectly improve performance by reducing the amount of data fetched from the database”:
    • To avoid fetching unnecessary data, use select to load only the required columns.
    • Example:
      posts = Post.includes(:comments).select(:id, :title)
  3. Avoid N+1 on Nested Queries:
    • Use joins or preload to handle nested associations more efficiently.
    • Example:
      # Using preload for multiple associations
      posts = Post.preload(:comments, :tags).all
      
  4. Batch Queries (pagination) “Same as 2”:
    • For large data sets, use pagination (e.g., kaminari or will_paginate) to limit the number of records loaded at once.
    • Example:
      posts = Post.includes(:comments).paginate(page: 1, per_page: 20)
      

Conclusion:

To fix N+1 queries, use eager loading (includes), carefully select required columns, and consider joins or preload for more complex associations. This reduces the number of queries, improving the performance of your application.

9.

Can you explain the differences between has_one, belongs_to, has_and_belongs_to_many and has_many through associations in Rails?

Answer

In Rails, has_one, belongs_to, and has_and_belongs_to_many are different types of associations used to define relationships between models. Here’s a structured explanation of each:

  1. has_one Association
  • Definition: Defines a one-to-one relationship where the current model “owns” the associated model.
  • Usage: The model with the has_one association holds the foreign key in its associated model.

Example:

class User < ApplicationRecord
has_one :profile
end

class Profile < ApplicationRecord
belongs_to :user
end
  • Key Points:
    • The User model has one Profile.
    • The Profile model contains a foreign key user_id.
  1. belongs_to Association
  • Definition: Defines a one-to-one relationship where the current model is the “child” or “dependent” of the other model.
  • Usage: The model with the belongs_to association holds the foreign key pointing to the other model.
  • Example:
    class Profile < ApplicationRecord
    belongs_to :user
    end
    
    class User < ApplicationRecord
    has_one :profile
    end
  • Key Points:
    • The Profile model belongs to a User.
    • The Profile model contains a foreign key user_id.
    • You generally define belongs_to on the child model and has_one on the parent.
  1. has_and_belongs_to_many (HABTM) Association
  • Definition: Defines a many-to-many relationship between two models, where each model can have many of the other.
  • Usage: A join table (without any model) is required to store the relationships between the two models.
  • Example:
    class Article < ApplicationRecord
    has_and_belongs_to_many :tags
    end
    
    class Tag < ApplicationRecord
    has_and_belongs_to_many :articles
    end
  • Key Points:
    • The Article and Tag models have a many-to-many relationship.
    • A join table (articles_tags) is automatically created with article_id and tag_id columns.
  1. has_many through Association
  • Definition: It also defines a many-to-many relationship between two models, with the main difference being that there will be a third model that represents said relationship. That model can be used to store other pieces of information that don’t belong to either of the original models.
  • Usage: A join table (with a model and possibly extra columns other than the ids) is required to store the relationships between the two models.
  • Example:
    class Person < ApplicationRecord
      has_many :magazines, through: :subscriptions
    end
    
    
    class Magazine < ApplicationRecord
      has_many :persons, through: :subcriptions
    end
    
    
    class Subscriptions < ApplicationRecord
    belongs_to :person
    belongs_to :magazine
    end

 

  • Key Points:
    • The Person and Magazine models have a many-to-many relationship through Subscription.
    • A join table (subscriptions) is automatically created with person_id and magazine_id columns and can have another ones (e.g.: is_active )

Key Differences:

  • has_one: One-to-one relationship where the “owner” is on the “one” side.
  • belongs_to: One-to-one relationship where the “owned” side holds the foreign key.
  • has_and_belongs_to_many: Many-to-many relationship using a join table with no intermediate model.
  • has_many through: Many-to-many relationship using a join table with intermediate model.

Summary:

  • has_one: Parent owns the association.
  • belongs_to: Child holds the foreign key.
  • has_and_belongs_to_many: Both models have many of the other, with a join table.
  • has_many through: Both models have many of the other, with a join table that is represented by a model.
10.

What are the advantages and potential pitfalls of using service objects in a Rails application?

Answer

Advantages of Using Service Objects in Rails:

  1. Separation of Concerns:
    • Service objects help keep models and controllers clean by separating business logic from them.
    • They encapsulate complex operations or workflows, making your codebase more organized.
  2. Improved Testability:
    • Service objects are easier to test in isolation because they typically focus on a single responsibility.
    • Writing unit tests for service objects becomes straightforward, promoting better test coverage.
  3. Reusability:
    • Service objects can be reused across different parts of the application, reducing code duplication and enhancing maintainability.
  4. Simplifies Controllers:
    • By moving complex logic out of controllers, service objects keep them slim and focused only on request handling and response formatting.
  5. Single Responsibility:
    • Service objects usually encapsulate a single action or business logic, adhering to the Single Responsibility Principle (SRP).

Potential Pitfalls of Using Service Objects in Rails:

  1. Overuse:
    • Introducing service objects for every small action can lead to unnecessary complexity. Overuse can make your application harder to navigate.
  2. Verbosity:
    • For simple operations, creating a service object can add unnecessary layers of abstraction, making the code more verbose than necessary.
  3. Too Many Services:
    • Having too many service objects can create confusion and bloat in the project, making it harder to maintain.
  4. Tight Coupling:
    • If service objects are tightly coupled to specific models or controllers, changes in one part of the application could require changes in many service objects.
  5. Increased Indirection:
    • Service objects add another layer of abstraction, which can make tracing bugs more difficult since the logic is split across multiple classes.

Summary:

Service objects are useful for organizing complex business logic, improving testability, and maintaining a clean architecture. However, overuse or improper design can lead to unnecessary complexity and maintenance challenges.

11.

What is the purpose of background jobs in Rails, and how would you integrate a library like Sidekiq or Delayed::Job into a Rails project?

Answer

Purpose of Background Jobs in Rails:

  1. Asynchronous Processing:
    • Background jobs allow you to handle time-consuming tasks (e.g., email sending, file uploads, data processing) outside of the request-response cycle.
    • This ensures that the user experience is not delayed by tasks that take too long to complete.
  2. Improved Performance:
    • By offloading tasks to background workers, you can free up resources for faster handling of requests, improving overall system performance.
  3. Reliability:
    • Background jobs can be retried on failure, which increases the reliability of tasks that depend on external services or require retries.
  4. Scalability:
    • Asynchronous jobs allow your app to scale better by distributing the workload across multiple workers.

Integrating Sidekiq or Delayed::Job into a Rails Project:

  1. Install the Gem:
  • For Sidekiq, add it to the Gemfile:

    gem 'sidekiq'
    
  • For Delayed::Job, add:

    gem 'delayed_job_active_record'
    

Then run bundle install.

  1. Set Up the Queue (For Sidekiq):
  • For Sidekiq, create a worker class to define the background job:

    class HardWorker
    include Sidekiq::Worker
    def perform(*args)
    # background task
    end
    end

     

  • For Delayed::Job, you don’t need to include a special module; just use ActiveRecord methods:

    class Post < ApplicationRecord
    def send_email
    # code to send email
    end
    end
    
  1. Enqueue Jobs:
  • For Sidekiq:
    HardWorker.perform_async(arg1, arg2)
    
  • For Delayed::Job:
    post.delay.send_email
  1. Start the Worker:
  • For Sidekiq, run the following command to start the background workers:

    bundle exec sidekiq
    
  • For Delayed::Job, start the worker with:

    rake jobs:work
    
  1. Configure the Queue (Optional):
  • You can configure the number of retries, priority, and queue names for Sidekiq in an initializer or for Delayed::Job using config.active_job.queue_adapter = :sidekiq in config/application.rb.

 

Summary:

Background jobs are used in Rails for handling long-running tasks asynchronously, improving user experience and system performance. To integrate libraries like Sidekiq or Delayed::Job, you install the respective gems, define worker classes, and enqueue tasks to be processed in the background, thereby offloading heavy tasks from the request cycle.

12.

When would you choose a symbol over a string in Ruby on Rails, particularly with respect to performance and memory usage in large-scale applications?

Answer

Choosing a Symbol Over a String in Ruby on Rails:

  1. Performance:
  • Symbols are more memory efficient and faster than strings, especially in large-scale applications.
  • A symbol is a single object in memory, while strings are mutable, meaning every new string creation results in a new object.
  • Symbols are immutable and reused, so Ruby does not create a new object every time the symbol is referenced.
  • In cases where a value is repeated multiple times (e.g., keys in a hash), symbols will reduce memory usage significantly.

Example:

# Symbol
:username
# String
"username"

 

  1. Memory Usage:
  • Symbols are stored in a symbol table and reused, leading to lower memory consumption when the same value is used repeatedly.
  • Strings, on the other hand, consume more memory because each occurrence of the same string can take up a separate chunk of memory.

In large-scale applications, if you need to refer to the same identifier (e.g., database column names or constant values) many times, symbols are a better choice to optimize memory usage.

  1. Use Cases for Symbols:
  • Keys in Hashes: Symbols are typically used as keys in hashes because they are more memory-efficient and faster for comparison.
  • Method Names: When passing method names (e.g., as arguments to send or define_method), symbols are preferred due to their immutability and memory efficiency.
  • Identifiers: In scenarios like database column names or other identifiers that don’t change frequently, symbols are ideal.
  1. When to Use Strings:
  • Dynamic Content: Use strings when the value may change, need to be manipulated, or is not used repeatedly.
  • User Input: Strings are used for user input, as the content is often unpredictable.

 

Conclusion:

In Ruby on Rails, choose symbols over strings for performance and memory efficiency when dealing with fixed identifiers or values that are reused frequently, such as hash keys, method names, or database field names. Strings should be used for dynamic or user-provided content. This distinction helps in optimizing both memory usage and performance in large-scale applications.

13.

When would you prefer using && and || over and and or in a Rails application, especially with respect to precedence and readability?

Answer

Choosing Between &&/|| and and/or in Ruby on Rails:

  1. Precedence:
  • && and || have higher precedence than and and or. This means that &&/|| are evaluated before other expressions, making them more predictable in complex expressions.
  • and and or have lower precedence, meaning they bind more loosely, which can lead to unexpected behavior if not used carefully. These have even lower precedence than =.

Example:

# Using && (higher precedence)
result = true && false || true
# result will be true (evaluated as (true && false) || true)

# Using and (lower precedence)
result = true and false or true
# result will be true because of the low precedence of "and" and "or", so the "=" will run before the logic operation (evaluated as true and (false or true))
  1. Readability:
  • && and || are the standard logical operators in most programming languages and are widely understood. They are preferred in conditions where readability and clarity are crucial, especially for complex conditions.
  • and and or are more readable when used in simple, low-precedence flow control situations like controlling the flow of execution. They are often used in control flow statements (e.g., user.suspended? and return :denied).

Example:

# Using && (standard operator for conditions)
if user && user.active?
# Do something
end

# Using and (BAD)
if user and user.active?
# Do something
end

  1. Best Practices:
  • Use && and || when dealing with logical expressions that involve multiple conditions, especially when precedence matters.
  • Use and and or for flow control (e.g., assignments or statements), as they make the code more natural and readable.
  1. Examples in Rails Context:
  • Complex Conditions:
    # Prefer &&/|| in Rails for clarity and precedence
    if user.active? && user.admin? || user.premium?
    # More readable and predictable
    end
  • Flow Control:

    # Prefer and/or for flow control
    x = extract_arguments or raise ArgumentError, "Not enough arguments!"

 

Conclusion:

In a Rails application, prefer && and || when dealing with complex logical expressions where precedence is important. Use and and or in simpler, flow-control contexts where readability is prioritized. The key is to maintain clarity while ensuring the correct evaluation order for logical operations.

14.

How would you use Procs in Rails, particularly in the context of callbacks?

Answer

Using Procs in Rails for Callbacks and Custom Logic Optimization:

Procs in Ruby are blocks of code that can be stored in variables and executed when needed. In Rails, they are particularly useful for creating reusable, dynamic behavior, especially when it comes to callbacks or custom logic.

  1. Using Procs in Callbacks:

Rails callbacks (e.g., before_save, after_create) allow you to run custom code at specific points in the lifecycle of an ActiveRecord object. You can pass a Proc to a callback, which makes it easier to reuse or delay execution of the logic.

Example:

class User < ApplicationRecord
# Define a Proc that can be reused in callbacks
set_callback :create, :before, Proc.new { |user| user.name = user.name.capitalize }

# Alternatively, use a Proc for custom logic in callbacks
before_save Proc.new { |user| user.updated_at = Time.now }
end

In this example:

  • set_callback: A callback with a Proc can be used to dynamically define behavior during lifecycle events.
  • before_save: The Proc capitalizes the user’s name before saving the object.
  1. Using Procs for Custom Logic (Optimization):

Procs can be useful by separating logic and executing it conditionally without having to write redundant code. For instance, a Proc can hold expensive or complex logic that only needs to run under specific conditions.

Example – Encapsulation and Reusability:

filter_products = Proc.new do |category|
where(category: category).order(price: :asc)
end

def self.filter_by_category(category)
filter_products.call(category)
end

def self.filter_by_category_with_discount(category)
filter_products.call(category).where(discounted: true)
end

In this example:

  • The filter_products Proc defines a reusable query filter.
  • You can call the Proc with .call inside any method to execute the logic, reducing code duplication.
  1. Using Procs for Dynamic Callbacks:

In some cases, you may want to conditionally define or modify callbacks based on specific criteria. Procs allow you to dynamically define logic that can be executed inside Rails callbacks based on the state of the object.

Example:

class User < ApplicationRecord
before_save Proc.new { |user| user.is_active? ? user.activate : user.deactivate }
end

In this example:

  • The Proc is used to check the is_active? state of the user before saving and conditionally triggers the activate or deactivate method.

Benefits of Using Procs:

  1. Code Reusability: Procs allow you to define reusable pieces of logic that can be called from different places in your application.
  2. Cleaner Code: Procs can help keep your callbacks and custom logic clean, modular, and easy to maintain.
  3. Dynamic Behavior: Procs can be used to define dynamic or conditional behaviors in callbacks or methods, based on the state of the object.

Conclusion:

In Rails, Procs provide a flexible and efficient way to handle callbacks and custom logic. They allow for reusable, deferred, and dynamic execution of logic, leading to cleaner code.

15.

How do you manage front-end assets in Rails, and how does the asset pipeline compare to newer tools like Webpacker or jsbundling?

Answer

Managing Front-End Assets in Rails

In Rails, managing front-end assets such as JavaScript, CSS, and images is a critical part of the application development process. Rails provides different tools for handling assets, and understanding the evolution from the Asset Pipeline to Webpacker and jsbundling is key for modern Rails development.

  1. The Asset Pipeline (Sprockets)

Sprockets is the traditional asset management tool in Rails and is responsible for processing and serving assets like JavaScript, CSS, and images.

Key Features of the Asset Pipeline:

  • Concatenation: Sprockets combines multiple JavaScript or CSS files into a single file to reduce the number of requests.
  • Minification: It minifies JavaScript and CSS to reduce file sizes and improve performance.
  • Preprocessing: Sprockets supports preprocessors like SCSS, CoffeeScript, and ERB. This means you can write SCSS instead of plain CSS or use CoffeeScript instead of JavaScript.
  • Digesting: It appends a hash to the filenames of assets for cache busting, ensuring browsers always load the latest version.

Example:

# In app/assets/stylesheets/application.css
/*
*= require_self
*= require_tree .
*/

# In app/assets/javascripts/application.js
//= require rails-ujs
//= require_tree .

While the Asset Pipeline is powerful, it can be limited when it comes to handling modern JavaScript tooling, such as ES6+, React, and TypeScript.

  1. Webpacker (Rails 5.1 – Rails 7)

With the introduction of Webpacker in Rails 5.1, Rails developers gained access to the power of Webpack — a modern JavaScript bundler. Webpacker was designed to handle more complex front-end tools and workflows, especially for applications that use modern JavaScript frameworks like React, Vue, or Angular.

Key Features of Webpacker:

  • ES6 and Beyond: Webpacker allows developers to use modern JavaScript features, such as ES6 modules, directly.
  • Integration with Front-End Frameworks: You can easily integrate with front-end frameworks (React, Vue.js, Angular) by setting up Webpack configurations.
  • Asset Bundling: Webpacker handles asset bundling and minification more effectively for modern JavaScript apps.
  • Hot Module Replacement (HMR): During development, Webpacker supports HMR, allowing you to see changes in real-time without a full page reload.

Example:

// In app/javascript/packs/application.js
import { start } from "rails-ujs";
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';

ReactDOM.render(, document.getElementById('root'));

While Webpacker provided significant improvements for modern JavaScript development, it was somewhat heavy-handed for simpler Rails apps or apps not relying heavily on front-end frameworks.

  1. jsbundling-rails (Rails 7+)

With Rails 7, jsbundling-rails was introduced to offer a more lightweight and flexible approach to JavaScript bundling. It leverages modern bundlers like esbuild, Rollup, or Vite instead of Webpack, aiming to provide faster builds and a simpler configuration for Rails applications.

Key Features of jsbundling-rails:

  • Faster Builds: Tools like esbuild and Vite offer faster bundling compared to Webpack, improving development speed.
  • Simple Configuration: The integration is straightforward and doesn’t require complex Webpack configuration files.
  • Minimalistic: jsbundling-rails focuses solely on JavaScript, leaving CSS and other assets to be handled by separate tools.
  • Modern Tooling: jsbundling allows developers to take advantage of ES Modules, TypeScript, React, Vue.js, and other modern JavaScript tools.

Example:

// In app/javascript/application.js
import { start } from "rails-ujs";
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';

ReactDOM.render(, document.getElementById('root'));

With jsbundling-rails, you can use the bundler of your choice (esbuild, Rollup, or Vite) for faster, optimized builds.

 

Comparison: Asset Pipeline, Webpacker, and jsbundling-rails

Feature Asset Pipeline (Sprockets) Webpacker jsbundling-rails
Main Focus Traditional asset management (CSS, JS, images) JavaScript and modern frameworks (React, Vue, etc.) JavaScript bundling (with modern tools)
Configuration Complexity Simple, integrated into Rails Complex, needs Webpack configuration Minimal, integrates with esbuild, Rollup, Vite
Front-End Framework Support Limited to plain JavaScript/CSS Excellent for React, Vue, Angular Supports React, Vue, TypeScript
Build Speed Slower, especially with many assets Slower than jsbundling-rails Faster build times (especially with esbuild or Vite)
CSS Handling Built-in CSS support via Sprockets Not built-in, requires additional handling Separate tool (e.g., Tailwind via PostCSS)
JavaScript Features Limited to ES5/Classic JS Supports modern JS features (ES6, JSX) Supports modern JS features (ES6, JSX, TypeScript)
Hot Module Replacement No Yes (with Webpack Dev Server) Yes (with Vite or esbuild)

Conclusion

  • Asset Pipeline (Sprockets) is still a good choice for simpler Rails applications where traditional CSS and JavaScript assets are enough.
  • Webpacker was a powerful tool for Rails 5.1-6 for handling complex front-end frameworks but may be overkill for simpler applications due to its complexity.
  • jsbundling-rails (introduced in Rails 7) is the modern solution for faster, more efficient JavaScript bundling. It supports modern tools like esbuild and Vite, offering better performance and simpler configuration for most use cases.

For modern Rails apps with dynamic, complex JavaScript or TypeScript codebases, jsbundling-rails or Webpacker are preferred, with jsbundling-rails being a faster and more streamlined option.

16.

How do you set up and manage multiple databases in Rails?

Answer

Setting Up and Managing Multiple Databases in Rails

Rails supports the use of multiple databases within a single application, making it possible to connect to different databases for different purposes (e.g., separating read and write databases or managing services that need their own database). Below are the steps to set up and manage multiple databases in a Rails application.

  1. Configure the database.yml File

Rails uses the config/database.yml file to define database configurations. To set up multiple databases, you can add different sections for each database connection (e.g., primary, secondary).

 

Example: config/database.yml

default: &default
adapter: postgresql
encoding: unicode
pool: 5
username: <%= ENV['DB_USERNAME'] %>
password: <%= ENV['DB_PASSWORD'] %>

development:
primary:
<<: *default
database: my_app_primary_development
secondary:
<<: *default
database: my_app_secondary_development

production:
primary:
<<: *default
database: my_app_primary_production
secondary:
<<: *default
database: my_app_secondary_production

In this example:

  • The primary database is the main database.
  • The secondary database is an additional database you want to connect to.
  1. Setting Up Models for Multiple Databases

To manage different models with different databases, you can specify which database a model should connect to using establish_connection.

 

Example: Using Multiple Databases for Models

# app/models/user.rb
class User < ApplicationRecord
# This model will use the `primary` database
establish_connection :primary
end

# app/models/order.rb
class Order < ApplicationRecord
# This model will use the `secondary` database
establish_connection :secondary
end

This ensures that the User model uses the primary database and the Order model uses the secondary database.

  1. Using connected_to for Database Switching

Rails 6 introduced the connected_to method, which allows dynamically switching between databases within a single request. This is useful for operations like reading from a replica database while writing to the primary database.

 

Example: Switching Databases for Specific Operations

# Switching to read-only database for queries
User.connected_to(role: :reading) do
# This query will use the secondary (replica) database
User.where(active: true).to_a
end

# This operation will use the primary database
User.create(name: "John Doe")

 

In the above example, the connected_to method allows for temporary switching between the reading (replica) and writing (primary) databases.

  1. Database Migrations with Multiple Databases

When working with multiple databases, Rails will run migrations on the database specified in the database.yml. If you want to run migrations on a specific database, you can use the RAILS_ENV variable and the db:migrate task.

 

Example: Running Migrations on Specific Databases

# Run migrations on the primary database
rails db:migrate RAILS_ENV=primary

# Run migrations on the secondary database
rails db:migrate RAILS_ENV=secondary

To run migrations for all databases, you can use the db:migrate task for each environment.

  1. Handling Schema for Multiple Databases

If you’re working with multiple databases, each one will have its own schema. To ensure your application correctly handles migrations and schema changes across databases, you can use schema.rb or structure.sql.

For multiple databases, you may need to handle schema definitions and updates manually for each database.

  1. Shards in Rails (Advanced)

If you’re managing shards or horizontal partitioning (i.e., splitting data across databases based on certain criteria), Rails supports this via the shard API. You would define a shard for each subset of data and connect models accordingly.

This is an advanced approach used for large-scale applications needing to scale horizontally.

  1. Database Connection Pooling

When using multiple databases, it’s important to ensure you manage the connection pool appropriately. This can be configured in the database.yml for each database connection.

 

Example:

development:
primary:
<<: *default
database: my_app_primary_development
pool: 10
secondary:
<<: *default
database: my_app_secondary_development
pool: 5

By specifying the pool, you ensure the correct number of connections to each database.

Summary

To set up and manage multiple databases in Rails:

  1. Configure database.yml to specify different databases (e.g., primary and secondary).
  2. Use establish_connection within models to associate them with specific databases.
  3. For dynamic switching, use connected_to to read from one database (e.g., replica) while writing to another.
  4. Run migrations on individual databases using the RAILS_ENV variable.
  5. Handle database connections properly with pooling and ensure schema consistency.

This approach provides flexibility in working with multiple databases in a Rails application, allowing you to scale or separate concerns between different databases.

17.

What are the different types of ActiveRecord callbacks in Rails?

Answer

ActiveRecord Callbacks in Rails

In Rails, ActiveRecord callbacks are hooks that allow you to run methods at various points in the lifecycle of an ActiveRecord object. These callbacks help manage the behavior of an object before or after it is saved, updated, deleted, etc. Below are the main types of callbacks:

1. Create Callbacks

  • before_create: Runs before a new object is created and saved.
  • after_create: Runs after a new object is created and saved.

Example:

class User < ApplicationRecord
before_create :set_default_role
after_create :send_welcome_email

private

def set_default_role
self.role ||= 'user'
end

def send_welcome_email
UserMailer.welcome(self).deliver_later
end
end

2. Update Callbacks

  • before_update: Runs before an existing object is updated and saved.
  • after_update: Runs after an object is updated and saved.

Example:

class User < ApplicationRecord
before_update :check_role_change
after_update :log_role_change

private

def check_role_change
# Do something before updating the role
end

def log_role_change
# Log the role change after it happens
end
end

3. Save Callbacks

  • before_save: Runs before either create or update is performed on the object.
  • after_save: Runs after either create or update is performed.

Example:

class User < ApplicationRecord
before_save :normalize_email
after_save :clear_cache

private

def normalize_email
self.email = email.downcase
end

def clear_cache
Rails.cache.delete("user_#{id}")
end
end

4. Validation Callbacks

  • before_validation: Runs before validations are run.
  • after_validation: Runs after validations are run.

Example:

class User < ApplicationRecord
before_validation :normalize_name
after_validation :log_validation_error

private

def normalize_name
self.name = name.strip.capitalize
end

def log_validation_error
# Log validation errors, if any
end
end

5. Destroy Callbacks

  • before_destroy: Runs before an object is destroyed.
  • after_destroy: Runs after an object is destroyed.

Example:

class User < ApplicationRecord
before_destroy :notify_admin
after_destroy :log_deletion

private

def notify_admin
# Notify admin before destruction
end

def log_deletion
# Log user deletion after it's destroyed
end
end

6. Transaction Callbacks

  • before_commit: Runs before a database transaction is committed.
  • after_commit: Runs after a transaction has been committed.
  • before_rollback: Runs before a transaction is rolled back.
  • after_rollback: Runs after a transaction has been rolled back.

Example:

class User < ApplicationRecord
before_commit :audit_changes
after_commit :send_notification
after_rollback :log_failure

private

def audit_changes
# Log changes before commit
end

def send_notification
# Send notifications after commit
end

def log_failure
# Log the failure after rollback
end
end

Summary of ActiveRecord Callbacks

  • Before Callbacks: These are executed before specific actions, such as saving, creating, updating, or destroying.
  • After Callbacks: These are executed after specific actions, such as saving, creating, updating, or destroying.
  • Validation Callbacks: Used for running methods before or after validations.
  • Transaction Callbacks: Used to manage operations in the context of a database transaction.
  • Destroy Callbacks: Triggered before or after an object is destroyed.

Best Practices

  • Use callbacks sparingly to keep your code maintainable and clear.
  • Avoid complex business logic in callbacks. Instead, consider using service objects or other design patterns to handle complex tasks.
  • Callbacks like after_create, before_save, etc., should be kept as simple and lightweight as possible to avoid performance hits.

 

18.

How does the flash work in Rails, and how can you customize its behavior?

Answer

Flash in Rails: Overview and Customization

In Rails, flash is a mechanism used to store temporary messages that persist for the duration of a single request-response cycle. It is commonly used for displaying notifications, errors, or success messages to users, typically after a form submission or action.

How Flash Works in Rails

  • Storage: The flash is a hash that stores temporary key-value pairs, where the key is usually a symbol (e.g., :notice, :alert), and the value is a message to display to the user.
  • Persistence: Flash messages persist across redirects, which is why they’re useful for showing feedback after a user performs an action (e.g., submitting a form and getting redirected).Flash messages are cleared out after the next request cycle (usually a redirect). This makes them temporary and ensures they don’t persist beyond the next request.

 

Example:

class UsersController < ApplicationController
def create
@user = User.new(user_params)
if @user.save
flash[:notice] = "User successfully created."
redirect_to user_path(@user)
else
flash[:alert] = "There was an error creating the user."
render :new
end
end
end

In the above example, when a user is created successfully, the :notice flash key is set, and the user is redirected. If there is an error, an :alert message is set, and the new user form is re-rendered.

 

Types of Flash Messages

Rails provides two main flash categories:

  • flash[:notice]: Typically used for success or informational messages.
  • flash[:alert]: Used for error or warning messages.

 

You can also define custom flash keys for specific purposes:

flash[:success] = “Operation was successful!”
flash[:error] = “Something went wrong!”

 

Displaying Flash Messages in Views

Flash messages are often displayed in views (usually in the layout or as part of a specific view) using the following syntax:

 

<% if flash[:notice] %>
<div class=”notice”><%= flash[:notice] %></div>
<% end %>

<% if flash[:alert] %>
<div class=”alert”><%= flash[:alert] %></div>
<% end %>

 

This checks for any messages stored in the flash[:notice] and flash[:alert] keys and displays them in the view if they exist.

Customizing Flash Behavior

  1. Custom Flash Types

You can customize the flash by adding additional keys and adjusting how they are displayed in your views.

flash[:success] = "Your action was successful."
flash[:warning] = "Be cautious next time."

In the view, you can then display these custom messages:

 

<% if flash[:success] %>
<div class=”success”><%= flash[:success] %></div>
<% end %>

<% if flash[:warning] %>
<div class=”warning”><%= flash[:warning] %></div>
<% end %>

  1. Flash.now

Sometimes you need to display flash messages without redirecting. In that case, you can use flash.now. This will only display the flash message for the current request (not persisted across redirects).

flash.now[:alert] = "This is an error that doesn't persist."
render :new
  1. Customizing Flash Message Duration

Rails doesn’t have built-in support for automatically hiding flash messages after a certain period. However, you can use JavaScript to automatically remove flash messages after a few seconds.

$(document).ready(function() {
setTimeout(function() {
$(".flash").fadeOut("slow");
}, 3000); // 3 seconds
});

Flash Hash Persistence

By default, flash messages are cleared after the next request (i.e., after a redirect). However, if you want the flash message to persist for additional requests (for instance, across two pages), you can use flash.keep or flash.keep(:key):

flash[:notice] = "This is a persistent message."
flash.keep(:notice)

This will keep the flash message around for the next request, allowing you to display it across multiple redirects.

 

Summary

  • Flash is used to store temporary messages between redirects and is cleared after the next request cycle.
  • Common flash keys are :notice and :alert, but custom keys can be used for various purposes.
  • Flash messages can be displayed in views and customized with CSS or JavaScript for better user experience.
  • You can use flash.now for messages that should only appear without a redirect, and flash.keep to persist flash messages across multiple requests.

 

19.

How and when would you use content_for vs yield in Rails layouts to achieve flexible rendering strategies?

Answer

content_for vs yield in Rails Layouts:

In Rails, both content_for and yield are used for managing and rendering content in layouts. They allow you to inject content dynamically into specific parts of a layout, making it flexible and reusable. However, they serve different purposes and are used in different scenarios.

yield:

  • Purpose: The yield method is used to render content from the view that is passed to the layout. It’s where the main body of the page will be inserted when rendering the layout.
  • Usage: yield is typically used in the layout to specify where the content of the view will go.

 

Example:

<!– app/views/layouts/application.html.erb –>
<html>
<head>
<title>My App</title>
</head>
<body>
<header>
<h1>Welcome to My App</h1>
</header>

<div class=”main-content”>
<%= yield %> <!– Main content of the page goes here –>
</div>

<footer>
<p>Footer content</p>
</footer>
</body>
</html>

 

In the above example, yield is used in the layout to place the main content of the page where the yield tag appears.

content_for:

  • Purpose: The content_for method allows you to define a block of content in a specific view, which can be rendered in a particular part of the layout. This allows for injecting content into predefined sections of the layout without modifying the layout itself.
  • Usage: content_for is often used for injecting content into specific areas, such as the <head> section for JavaScript or CSS, or for injecting other dynamic content into predefined blocks.

 

Example:

<!– app/views/layouts/application.html.erb –>
<html>
<head>
<title>My App</title>
<%= yield :head %> <!– Render custom content in the head section –>
</head>
<body>
<header>
<h1>Welcome to My App</h1>
</header>

<div class=”main-content”>
<%= yield %> <!– Main content of the page –>
</div>

<footer>
<p>Footer content</p>
</footer>
</body>
</html>

 

<!– app/views/posts/show.html.erb –>
<% content_for :head do %>
<link rel=”stylesheet” href=”special_styles.css”>
<% end %>

<h2>Post Title</h2>
<p>Post content…</p>

 

In this case, content_for :head is used to add custom content to the <head> section of the layout. The content defined in the show.html.erb view will be inserted where <%= yield :head %> appears in the layout.

Key Differences:

  1. Purpose:
    • yield is used for the main content area of the layout.
    • content_for is used to inject content into specific predefined sections of the layout.
  2. Flexibility:
    • yield is used when the content for the page is directly passed from the view.
    • content_for is used when you need to inject additional content into specific layout sections (like a <head> tag or other custom regions).
  3. Default vs Custom Sections:
    • yield is the default place for rendering content.
    • content_for is for creating custom sections in your layout that are rendered when specifically requested with yield :section_name.

When to Use Each:

  • Use yield:
    • For the main content of a page that comes from the view.
    • When you want a simple placeholder to render dynamic content (i.e., the body of the page).
  • Use content_for:
    • When you need to inject specific content into different parts of your layout, such as adding extra JavaScript or CSS in the <head> section, or when you have specific blocks of content that should only appear in certain pages.
    • To separate concerns in your views, such as not mixing JavaScript or CSS inside the body of the page.

Example Use Case:

Imagine you have a layout with a <head> section, and on some pages, you want to include specific JavaScript files. Instead of putting the JavaScript directly in your views, you can use content_for to inject this into the layout dynamically.

 

<!– app/views/layouts/application.html.erb –>
<html>
<head>
<title>My App</title>
<%= yield :head %> <!– Inject dynamic content for head here –>
</head>
<body>
<%= yield %> <!– Main content –>
</body>
</html>

 

<!– app/views/products/show.html.erb –>
<% content_for :head do %>
<script src=”products.js”></script>
<% end %>

<h1>Product Details</h1>
<p>Details of the product…</p>

 

Here, the products.js file will only be loaded on the show.html.erb page and not globally across all pages.

Conclusion:

  • yield is used to render the main content of a page, acting as a placeholder in the layout.
  • content_for is used to inject content into specific sections of the layout, providing flexibility in customizing parts of the page without altering the main layout structure.
20.

How does Rails naming convention impact the organization of models, controllers, and views?

Answer

Rails Naming Conventions and Their Impact on Organization of Models, Controllers, and Views:

Rails uses naming conventions as a key principle for maintaining a clean, structured, and predictable application architecture. By adhering to these conventions, Rails automatically knows how to link models, controllers, and views, significantly reducing the amount of manual configuration needed.

Here’s how Rails naming conventions impact the organization of models, controllers, and views:

1. Models:

  • Naming Convention: Models are named in singular form and PascalCase and its file using snake_case.
    • Example: A model representing articles is named Article (singular and capitalized).
    • Impact:
      • The model class should be named in a singular form (Article).
      • By default, Rails assumes the model corresponds to a database table named in plural form (articles), following the “pluralization” convention.
  • Example:
    class Article < ApplicationRecord
    end

    Rails will automatically look for a corresponding table called articles in the database.

2. Controllers:

  • Naming Convention: Controllers are named in plural form and PascalCase and its file using snake_case.
    • Example: A controller for managing articles would be named ArticlesController.
    • Impact:
      • The controller class should be named in plural form (ArticlesController).
      • Rails assumes the controller will handle requests related to the model Article, and that it will have actions like index, show, new, create, edit, update, and destroy.
  • Example:
    class ArticlesController < ApplicationController
    def index
    @articles = Article.all
    end
    end

    Rails automatically maps the controller to the appropriate routes (like articles#index and articles#show) and expects certain actions based on standard RESTful conventions.

3. Views:

  • Naming Convention: Views are stored in subdirectories corresponding to their controllers, and each view file is named after the corresponding action in snake_case with .html.erb (or other formats like .html.haml or .html.slim).
    • Example: A view for the index action in the ArticlesController will be placed in the app/views/articles/ directory and named index.html.erb.
    • Impact:
      • The view is automatically associated with the correct controller and action based on its location and name.
      • Views should be placed in folders matching the controller’s name (views/articles/ for the ArticlesController).
      • If the controller is managing a resource, Rails will expect views for actions like index, show, new, and so on, and these views should be named accordingly.
  • Example:
    The view file for the index action:app/views/articles/index.html.erb4. Routes:

    • Naming Convention: Rails uses RESTful conventions for routing by default. It will automatically generate routes based on the resource name and the standard controller actions.
      • Example:
        • For a ArticlesController, Rails will generate routes like:
          • GET /articlesArticlesController#index
          • GET /articles/:idArticlesController#show
          • GET /articles/newArticlesController#new
          • POST /articlesArticlesController#create
          • GET /articles/:id/editArticlesController#edit
          • PATCH/PUT /articles/:idArticlesController#update
          • DELETE /articles/:idArticlesController#destroy
    • Impact:
      • The naming conventions reduce the need for custom route definitions, allowing Rails to automatically generate standard routes based on the controller and action names.

    5. Associations:

    • Naming Convention: When defining relationships between models (e.g., has_many, belongs_to), Rails expects certain naming patterns.
      • Example: If an Article belongs to an Author, Rails will automatically expect an author_id foreign key in the articles table.
      • Impact:
        • Proper use of naming conventions in models (e.g., has_many, belongs_to) helps Rails automatically understand relationships and perform actions like querying related records.

     

    Summary of the Impact:

    1. Reduced Boilerplate: By following naming conventions, you avoid needing to manually configure many aspects of your application. Rails automatically makes the right connections between models, controllers, views, and routes based on names.
    2. Consistency: Naming conventions help maintain consistency across the application, making it easier for developers to navigate and understand the structure of the app.
    3. Automatic Route Generation: The naming conventions help Rails generate routes automatically, which aligns with RESTful principles and simplifies the routing configuration.
    4. Standardization: Rails’ conventions ensure that other developers can easily understand your code, as the organization of files and code follows a predictable pattern.

    Example: Full Structure

    • Model: Article (singular, camel-case)
    • Controller: ArticlesController (plural, snake-case)
    • View: index.html.erb (snake-case, corresponds to index action in the controller)
    • Route: resources :articles (automatically generates standard routes for CRUD actions)

    By adhering to these conventions, you ensure that your Rails application is clean, maintainable, and follows best practices, reducing the amount of configuration required and increasing productivity.

21.

What are some techniques for caching in Rails, and how do you decide which caching strategy to use?

Answer

Caching Techniques in Rails

  1. Fragment Caching:
    • Caches a portion of a view (e.g., specific elements like lists or images).
    • Usage: Ideal for parts of the page that don’t change often.
    • Example:<% cache @post do %>
      <%= render @post %>
      <% end %>
  2. Page Caching:
    • Caches entire pages and serves them directly (static HTML).
    • Usage: Best for content that doesn’t change frequently (e.g., public pages).
    • Example:
      class PostsController < ApplicationController
      caches_page :show
      end
  3. Action Caching:
    • Caches the output of an entire action but runs filters (like authentication).
    • Usage: Good for actions that can be cached but need some dynamic behavior (e.g., logged-in users).
    • Example:
      class PostsController < ApplicationController
      caches_action :index
      end
  4. Low-Level Caching (Cache Store):
    • Stores arbitrary data (e.g., database queries, API responses) in a cache store like Memcached or Redis.
    • Usage: For frequently used data or results that require fast access.
    • Example:
      Rails.cache.write("some_key", @data)
      @data = Rails.cache.read("some_key")
  5. Russian Doll Caching:
    • A strategy where nested fragments are cached individually, improving cache efficiency.
    • Usage: Useful for pages with complex nested elements.
    • Example:<% cache @post do %>
      <%= render @post %>
      <% cache @comment do %>
      <%= render @comment %>
      <% end %>
      <% end %>

Choosing the Right Caching Strategy

  • Page Caching: Use for static, rarely updated pages (e.g., home, about).
  • Action Caching: Use when the action is dynamic but can be cached for specific users or conditions.
  • Fragment Caching: Use for sections of views that don’t change often but are part of dynamic pages.
  • Low-Level Caching: Use for expensive database queries or external API calls.
  • Russian Doll Caching: Use for complex pages with nested dynamic content.

Decision Factors

  • Content Freshness: Use cache expiry or versioning for frequently updated content.
  • User Interaction: Cache for anonymous users or specific roles (e.g., admins) only.
  • Performance: Choose low-level caching or fragment caching to reduce database load.
22.

Explain the concept of lazy loading in Rails and its impact on performance.

Answer

Lazy Loading in Rails

Lazy loading refers to the practice of loading associations in ActiveRecord only when they are accessed, rather than loading them immediately when the parent object is fetched.

How it works:

  • When you retrieve an object from the database (e.g., Post.find(1)), its associated records (e.g., comments or authors) are not loaded until explicitly called (e.g., post.comments).
  • This is the default behavior in Rails to reduce unnecessary database queries.

Impact on Performance:

  • Advantages:
    • Reduced Initial Query Load: Initially, fewer queries are run, improving page load times.
    • Efficient Resource Usage: Associations are loaded only when necessary, saving database resources.
  • Potential Pitfalls:
    • N+1 Query Problem: If you access multiple records and their associations without eager loading (using .includes), it can result in multiple additional queries (one per record), leading to performance degradation.
    • Inefficiency with Multiple Accesses: Repeatedly accessing associations can trigger multiple queries, which can be costly for large datasets.

When to Use:

  • Lazy loading is useful when you don’t need to access all associations immediately, improving initial page load times.
  • To avoid N+1 issues, use eager loading (includes, preload, or eager_load) when accessing associated data across multiple records.
23.

How would you handle large file uploads in Rails?

Answer

Handling Large File Uploads in Rails

To handle large file uploads in Rails, you should focus on optimizing for both user experience and server performance. Here’s a structured approach:

1. Use a Gem for File Uploads:

  • CarrierWave or ActiveStorage are popular gems in Rails for handling file uploads.
    • CarrierWave: Customizable and supports storing files locally or on cloud services.
    • ActiveStorage: Built-in Rails solution that supports file attachment to models and integrates easily with cloud storage like Amazon S3.

2. Store Files in the Cloud (e.g., AWS S3, Google Cloud Storage):

  • Store large files in external cloud storage rather than directly on your server to prevent performance bottlenecks and reduce storage strain.
  • Example with ActiveStorage:

    has_one_attached :file

3. Background Processing for Uploads:

  • For large files, process uploads asynchronously to avoid timeouts and slow response times.
  • Use a background job library like Sidekiq or Delayed::Job to handle file uploads or processing after the file is uploaded.

4. Use Direct-to-Cloud Uploads:

  • For very large files, use direct-to-cloud uploads, where the file is uploaded directly from the client to cloud storage (e.g., using a pre-signed URL with AWS S3). This offloads the file transfer from your server.

5. Chunked Uploads (Multipart Uploads):

  • For extremely large files, implement chunked or multipart uploads. This approach uploads the file in smaller pieces, reducing the chance of failure.
  • Amazon S3 offers multipart uploads that allow files to be split into parts, uploaded in parallel, and then reassembled.

6. Set File Size Limits:

  • Set a maximum file size limit in your Rails model or controller to prevent users from uploading excessively large files that could strain your server.
    validates :file, size: { less_than: 100.megabytes }

7. Use Streaming for Large Files (Optional):

  • For large video or audio files, stream the file in chunks to avoid memory overload and improve user experience.

8. Handle Upload Failures Gracefully:

  • Ensure that file uploads handle potential failures, like timeouts or interruptions. Provide users with helpful error messages and allow them to retry uploads.

Summary:

  • Use gems like CarrierWave or ActiveStorage for seamless file handling.
  • Store files in the cloud to optimize performance.
  • Leverage background jobs for processing large files asynchronously.
  • Implement chunked uploads for very large files to minimize failures.

 

24.

What are the differences between find and find_by in ActiveRecord?

Answer

Differences Between find and find_by in ActiveRecord

  1. Primary Use:
    • find:
      • Retrieves a record by its primary key (usually id).
      • Raises an ActiveRecord::RecordNotFound exception if the record doesn’t exist.
        user = User.find(1) # Finds user with id = 1
        
    • find_by:
      • Retrieves the first record matching any condition (e.g., column values), not limited to primary key.
      • Returns nil if no record matches the criteria, no exception is raised.
        user = User.find_by(email: 'test@example.com') # Finds user with the specified email
  2. Error Handling:
    • find: Will throw an error if no matching record is found.
    • find_by: Will return nil if no match is found, making it safer for use in situations where no result is acceptable.
  3. Use Case:
      • find: When you are sure the record exists and you’re searching by primary key.
      • find_by: When you’re searching for a record by non-primary key columns, or when a missing record is expected and should be handled gracefully.

Summary:

  • Use find when retrieving a record by its primary key and expecting it to exist.
  • Use find_by for conditions with other columns and when you can handle the possibility of no result.
25.

How do you handle background tasks in Rails, and what libraries do you prefer for job processing?

Answer

Handling Background Tasks in Rails

In Rails, background tasks are typically used for time-consuming operations such as sending emails, processing images, or data syncing. These tasks run asynchronously outside the web request/response cycle, improving performance.

Libraries for Job Processing:

  1. Sidekiq:
    • A popular choice for background job processing in Rails, using Redis as the backend.
    • Supports multithreading, making it fast and efficient.
    • Provides advanced features like retries, scheduling, and monitoring via the Sidekiq Web UI.
    class MyJob
      include Sidekiq::Worker
    
      def perform
        # your task here
      end
    end
    
  2. Delayed::Job:
    • A simpler, database-backed option for background job processing.
    • It’s easy to set up and integrates directly with ActiveRecord.
    class MyJob < ApplicationJob
      def perform
        # your task here
      end
    end
    
  3. Resque:
    • Another Redis-backed job queue system, known for reliable job processing.
    • Provides real-time monitoring and supports job retries.
  4. ActiveJob:
    • Rails’ built-in abstraction layer for background jobs. It allows you to use multiple backends (like Sidekiq, Delayed::Job, Resque, etc.) interchangeably.

Recommendation:

  • Sidekiq is often the preferred choice for high-volume or complex tasks due to its speed, scalability, and rich features.
  • Use Delayed::Job if simplicity and database-backed jobs are sufficient for your needs.

Conclusion:

Choose the library based on your application’s scale and complexity, but Sidekiq is generally the go-to for large-scale, high-performance applications.

26.

Explain how you would test a Rails controller action that performs a database query.

Answer

To test a Rails controller action that performs a database query, you typically use RSpec or Minitest along with fixtures or factories (like FactoryBot). The goal is to ensure that the controller action correctly interacts with the database and returns the expected results.

Steps:

  1. Set up test data:
    • Use fixtures or factories to create sample data in the database for the test.
  2. Write the controller test:
    • Test the controller action to ensure it performs the database query correctly, typically checking the response and the data returned.

Example with RSpec:

RSpec.describe PostsController, type: :controller do
  let!(:post) { create(:post, title: "Test Post", content: "Test Content") }

  describe 'GET #show' do
    it 'retrieves the correct post from the database' do
      get :show, params: { id: post.id }

      expect(assigns(:post)).to eq(post)         # Check if the correct post is assigned
      expect(response).to have_http_status(:success)  # Check if the response is successful
    end
  end
end

Explanation:

  1. Test Setup: We use create(:post) to insert a Post into the database before the test runs.
  2. Action Call: We call get :show, params: { id: post.id } to make a GET request to the show action, passing the post.id as a parameter.
  3. Assertions:
    • assigns(:post) checks if the instance variable @post was assigned correctly from the query.
    • expect(response).to have_http_status(:success) ensures the response is successful.

Conclusion:

This test ensures that the controller correctly retrieves the post from the database and handles it as expected.

27.

What is the difference between scope and class methods in Rails models?

Answer

In Rails models, both scopes and class methods are used to define reusable queries, but they differ in how they’re defined, used, and their intended purpose.

Scopes:

  • Definition: Scopes are a way to define commonly used queries in a more readable and chainable manner.
  • Purpose: They are intended for simple query encapsulation, enabling easy reuse and chaining.
  • Usage: Scopes return an ActiveRecord::Relation, allowing for method chaining.
  • Syntax: scope :name, -> { query }

Example:

class Post < ApplicationRecord
scope :published, -> { where(status: 'published') }
end

Post.published # Returns an ActiveRecord relation with posts that are published

 

Class Methods:

  • Definition: Class methods are custom methods defined within a model that can include more complex logic beyond just queries.
  • Purpose: They provide flexibility for adding complex business logic or performing operations that don’t return an ActiveRecord::Relation.
  • Usage: Class methods return objects or data that may not necessarily be ActiveRecord::Relation, and they’re not limited to query chaining.
  • Syntax: def self.method_name; end

Example:

class Post < ApplicationRecord
def self.published_count
where(status: 'published').count
end
end

Post.published_count # Returns the count of published posts

Key Differences:

  1. Return Type:
    • Scope: Always returns an ActiveRecord::Relation, enabling chaining.
    • Class Methods: Can return any object (e.g., count, array, boolean).
  2. Chaining:
    • Scope: Designed to be chainable (Post.published.order(:created_at)).
    • Class Methods: Not inherently chainable, unless you return an ActiveRecord::Relation.
  3. Purpose:
    • Scope: Intended for simple query logic.
    • Class Methods: Suitable for more complex logic that may not directly relate to queries.

When to Use:

  • Use a scope when you need a reusable, chainable query.
  • Use a class method when you need more flexibility or need to perform non-query operations.
28.

How do you implement and manage database transactions in Rails?

Answer

In Rails, database transactions ensure that a set of operations are executed atomically, meaning either all succeed or none are applied (rolled back in case of failure).

Implementing Transactions:

  • You can use ActiveRecord::Base.transaction to wrap multiple database operations within a transaction block.

Syntax:

ActiveRecord::Base.transaction do
  # Operations that need to be executed atomically
  user = User.create!(name: 'John Doe')
  order = Order.create!(user_id: user.id, total: 100)

  # If an exception occurs, the transaction will be rolled back
end

Key Points:

  • create! and other “bang” methods raise exceptions if something goes wrong, which will cause the transaction to roll back.
  • If no exceptions occur, the transaction commits the changes to the database.

Managing Nested Transactions:

  • Rails supports nested transactions, but only the outermost transaction will control the commit/rollback behavior.
ActiveRecord::Base.transaction do
  # Outer transaction
  ActiveRecord::Base.transaction do
    # Inner transaction
  end
  # Commit happens if no error occurs
end

Using save! and destroy!:

  • Using the save! and destroy! methods (with a !), Rails will raise an exception if the save or destroy fails, ensuring the transaction is rolled back.

Rollback:

  • You can manually trigger a rollback within the transaction by using raise ActiveRecord::Rollback.
ActiveRecord::Base.transaction do
  # some operations
  raise ActiveRecord::Rollback # Forces a rollback
end

When to Use:

  • Use transactions when you need to ensure multiple database operations are either fully applied or fully discarded (e.g., creating an order and processing payment).
29.

How do you handle database indexing and query optimization in large-scale Rails applications?

Answer

Handling database indexing and query optimization in large-scale Rails applications is crucial for improving performance, especially with large datasets. Here’s a concise approach:

1. Database Indexing:

  • Indexing improves query speed by allowing faster lookups on frequently queried columns.
  • Add indexes on columns that are often searched, filtered, or used in JOINs. For example:
    add_index :users, :email
    add_index :orders, :user_id
    
  • Composite indexes: Use multi-column indexes for queries that filter or sort by multiple columns:
    add_index :orders, [:user_id, :status]
    
  • Unique indexes: Ensure uniqueness by adding indexes with the unique option:
    add_index :users, :email, unique: true
    

2. Query Optimization:

  • Avoid N+1 queries: Use eager loading (includes, joins, preload) to load related records in a single query:
    # Eager load related posts for users
    User.includes(:posts).each { |user| user.posts }
    
  • Use select to limit columns: Only retrieve the columns you need to reduce database load:
    users = User.select(:id, :email)
    
  • Optimize where clauses: Ensure efficient conditions in queries. For example, use integer columns for filtering rather than strings for better performance.
  • Database-level optimization: Monitor slow queries using tools like EXPLAIN ANALYZE to see execution plans and identify performance bottlenecks.

3. Query Caching:

  • Cache frequent queries with Rails caching mechanisms like fragment caching or query caching to avoid repeated database hits.

4. Use Database Views and Materialized Views:

  • In complex applications, views or materialized views can precompute expensive joins or aggregations for faster query performance.

5. Background Processing:

  • Offload heavy database operations to background jobs to improve user experience, for example, using Sidekiq for asynchronous task processing.

By combining indexing, query optimization, caching, and background processing, you can handle database performance efficiently in large-scale Rails applications.

Ruby on Rails Coding Interview Questions

1.

Write a Rails migration to create a posts table with columns for title, body, author_id (foreign key), and published_at.

Answer

Here’s a Rails migration to create a posts table with the specified columns (title, body, author_id, and published_at):

class CreatePosts < ActiveRecord::Migration[6.0]
  def change
    create_table :posts do |t|
      t.string :title
      t.text :body
      t.references :author, foreign_key: { to_table: :users }
      t.datetime :published_at

      t.timestamps
    end
  end
end

Explanation:

  • t.string :title: Creates a column title of type string.
  • t.text :body: Creates a column body of type text for storing the content.
  • t.references :author: Creates a column author_id as a foreign key referencing the users table (assuming users is where the authors are stored).
  • t.datetime :published_at: Creates a column for storing the publication date and time.
  • t.timestamps: Adds created_at and updated_at columns automatically.

You can run this migration using:

rails db:migrate
2.

Write a Rails controller action to fetch all posts by a specific author, where author_id is provided in the request. Include error handling for invalid author IDs.

Answer

Here’s an example of a Rails controller action to fetch all posts by a specific author, including error handling for invalid author_id:

class PostsController < ApplicationController
  def index
    author = User.find_by(id: params[:author_id])

    if author.blank?
      render json: { error: 'Author not found' }, status: :not_found
    else
      posts = author.posts
      render json: posts, status: :ok
    end
  end
end

Explanation:

  • params[:author_id]: Retrieves the author_id from the request parameters.
  • User.find_by(id: params[:author_id]): Attempts to find the User (author) by the provided author_id. If the author doesn’t exist, it returns nil.
  • Error handling: If the author is nil, meaning the author_id is invalid or not found, a JSON response with an error message is returned with a 404 Not Found status.
  • Fetching posts: If the author is found, their associated posts are retrieved using author.posts.
  • render json: posts, status: :ok: Returns the posts as JSON with a 200 OK status.

This controller action can be used to fetch all posts by a specific author by making a request like:

GET /posts?author_id=1

If the author_id is invalid, the response will be:

{
"error": "Author not found"
}
3.

Write a Rails model validation to ensure that the title of a post is unique and its body is at least 100 characters long

Answer

Here’s an example of a Rails model validation to ensure that the title of a post is unique and its body is at least 100 characters long:

class Post < ApplicationRecord
  # Validations
  validates :title, presence: true, uniqueness: true
  validates :body, presence: true, length: { minimum: 100 }
end

Explanation:

  • validates :title, presence: true, uniqueness: true: This ensures that the title is present and unique across all Post records.
    • presence: true: Ensures the title is not empty.
    • uniqueness: true: Ensures no other post has the same title.
  • validates :body, presence: true, length: { minimum: 100 }: This ensures that the body is present and contains at least 100 characters.
    • presence: true: Ensures the body is not empty.
    • length: { minimum: 100 }: Ensures the body has at least 100 characters.

This validation ensures that:

  1. A post cannot be saved without a title or a body.
  2. The title must be unique across all posts.
  3. The body must be at least 100 characters long.
  4. Finally, if were being 100% correct, this should also be done on the database
4.

Implement an ActiveRecord query to find all posts that were published within the last 30 days. Return them sorted by published_at in descending order.

Answer

Here’s how you can write an ActiveRecord query to find all posts that were published within the last 30 days and return them sorted by published_at in descending order:

Post.where('published_at >= ?', 30.days.ago)
    .order(published_at: :desc)

Explanation:

  • Post.where('published_at >= ?', 30.days.ago): This filters the Post records where published_at is greater than or equal to the date 30 days ago.
    • 30.days.ago: This is a Rails helper that returns a Time object representing the time 30 days before the current time.
  • .order(published_at: :desc): This orders the resulting posts by published_at in descending order, ensuring that the most recent posts are listed first.
  • If this filter is used in multiple places, it would be better to create it as a ActiveRecord scope.
5.

Write a Rails method in a model that calculates the total number of comments for a given post. Assume you have a comments table with post_id and body.

Answer

Here’s how you can write a Rails method in the Post model to calculate the total number of comments for a given post:

class Post < ApplicationRecord
  has_many :comments

  # Method to calculate total number of comments for the post
  def total_comments
    comments.count
  end
end

Explanation:

  • has_many :comments: This establishes the association between Post and Comment models, assuming each post has many comments.
  • comments.count: This method returns the count of comments associated with the post. It uses ActiveRecord’s count method, which efficiently counts the number of related Comment records for the current post.

You can now call post.total_comments on a Post object to get the total number of comments for that post.

6.

Write a method that returns the most recent comments for a given post.

Answer

Here’s how you can write a method in the Post model that returns the most recent comments for a given post:

class Post < ApplicationRecord
  has_many :comments

  # Method to return the most recent comments for the post
  def recent_comments(limit = 5)
    comments.order(created_at: :desc).limit(limit)
  end
end

Explanation:

  • comments.order(created_at: :desc): This sorts the associated comments by their created_at timestamp in descending order, so the most recent comments come first.
  • .limit(limit): This limits the number of comments returned, with the default set to 5. You can pass a different number as an argument to get more or fewer recent comments.

You can now call post.recent_comments on a Post object to get the most recent comments, with the option to specify how many to retrieve (e.g., post.recent_comments(10) for the 10 most recent).

7.

Create a custom validation method for a user model to ensure that the username is not a reserved word (e.g., “admin”, “root”).

Answer

Here’s how you can create a custom validation method in the User model to ensure that the username is not a reserved word (e.g., “admin”, “root”):

class User < ApplicationRecord
  validate :username_not_reserved

  private

  # Custom validation method to check if the username is a reserved word
  def username_not_reserved
    reserved_words = ['admin', 'root', 'superuser']
    if reserved_words.include?(username.downcase)
      errors.add(:username, "is reserved and cannot be used")
    end
  end
end

Explanation:

  • validate :username_not_reserved: This line tells Rails to run the username_not_reserved method as a custom validation for the User model.
  • reserved_words = ['admin', 'root', 'superuser']: This array contains the reserved words that cannot be used as a username.
  • username.downcase: Ensures case-insensitivity by converting the username to lowercase before checking it against the reserved words.
  • errors.add(:username, "is reserved and cannot be used"): If the username is found in the list of reserved words, this line adds an error to the username attribute, which will prevent the user from saving the model.

Now, when trying to create or update a User with a reserved username (like “admin” or “root”), the validation will trigger and prevent the save operation.

8.

Write a query to retrieve all users who have created more than 5 posts in the last month.

Answer

To retrieve all users who have created more than 5 posts in the last month, assuming you have a users table and a posts table where each post has a user_id and a created_at timestamp, you can write the following ActiveRecord query:

User.joins(:posts)
    .where(posts: { created_at: 1.month.ago..Time.current })
    .group(:id)
    .having('COUNT(posts.id) > ?', 5)

Explanation:

  1. User.joins(:posts): This performs an SQL JOIN between the users and posts tables based on the relationship defined in the models (i.e., each Post belongs to a User).
  2. .where(posts: { created_at: 1.month.ago..Time.current }): Filters posts that have been created in the last month using 1.month.ago..Time.current, which is an interval of time.
  3. .group(:id): Groups the result by users.id, which is necessary for aggregate functions like COUNT.
  4. .having('COUNT(posts.id) > 5'): Filters the grouped results to only include users who have more than 5 posts in the last month.

This query retrieves all users who have created more than 5 posts within the last month.

9.

Implement a Rails model method to calculate the total price of an order, considering both the price and quantity of line items.

Answer

To calculate the total price of an order, considering both the price and quantity of each line item, you would typically have an Order model and an associated LineItem model. The LineItem model would contain price and quantity fields, while the Order model would be associated with multiple LineItems.

Here’s how you can implement the total_price method in the Order model:

  • Order model method:
class Order < ApplicationRecord
  has_many :line_items
  
  def total_price
    line_items.total_price
  end
end
  • LineItem model method:
class LineItem < ApplicationRecord
  belongs_to :line_items

  # Scope to calculate the total price for a set of line items
  scope :total_price, -> { sum('price * quantity') }
end

Explanation:

  1. has_many :line_items: This establishes the relationship between the Order and LineItem models, where each order can have many line items.
  2. total_price scope:
    • sum('price * quantity') calculates the total price by summing up the result of multiplying the price and quantity of each line item. This uses ActiveRecord’s sum method with a raw SQL expression to calculate the total.

Example Usage:

Assuming you have an order with line items, you can now calculate the total price as follows:

order = Order.find(1)
total = order.total_price
puts "Total Price: $#{total}"

This will output the total price of all the line items in the given order, considering both price and quantity.

10.

Write a migration to add a foreign key from the comments table to the posts table.

Answer

To add a foreign key from the comments table to the posts table, you can create a migration that adds the post_id column to the comments table and enforces the foreign key constraint.

Here’s how you can write the migration:

Migration to add post_id foreign key to comments table:

class AddPostRefToComments < ActiveRecord::Migration[6.0]
  def change
    add_reference :comments, :post, null: false, foreign_key: true
  end
end

Explanation:

  1. add_reference :comments, :post: This adds a post_id column to the comments table, which will store the reference to the posts table.
  2. null: false: This ensures that every comment must have an associated post (the post_id cannot be null).
  3. foreign_key: true: This enforces the foreign key constraint, ensuring referential integrity between comments and posts.

Running the Migration:

Once the migration is defined, run the migration to apply the changes to your database:

rails db:migrate

This will add the post_id column to the comments table and create a foreign key constraint linking it to the posts table.

11.

Write a scope to fetch all posts created in the last 7 days.

Answer

You can create a scope in the Post model to fetch all posts created in the last 7 days using ActiveRecord’s query methods.

Here is how you can write the scope:

Scope in the Post model:

class Post < ApplicationRecord
  scope :created_in_last_7_days, -> { where(created_at: 7.days.ago) }
end

Explanation:

  • scope :created_in_last_7_days: Defines a class method named created_in_last_7_days that can be used to query posts.
  • where(created_at: 7.days.ago): This filters posts where the created_at date is greater than or equal to 7 days ago.
    • 7.days.ago is a Rails method that returns a timestamp 7 days before the current time.

Usage:

To fetch all posts created in the last 7 days, you can simply call the scope like this:

Post.created_in_last_7_days

This will return all posts where the created_at is within the last 7 days.

12.

Write a Rails model method that returns all authors who have never written a book in a given genre.

Answer

To implement a Rails model method that returns all authors who have never written a book in a given genre, we’ll assume the following relationships:

  • An Author model has many Books.
  • A Book belongs to an Author and belongs to a Genre.
  • A Genre has many Books.

Method in the Author model:

class Author < ApplicationRecord
  has_many :books

  # Method to find authors who have never written a book in a given genre
  def self.without_books_in_genre(genre_name)
    # Find all authors whose books do not belong to the given genre
    Author.left_outer_joins(:books)
          .where.not(books: { genre_id: Genre.find_by(name: genre_name)&.id })
          .distinct
  end
end

Explanation:

  • self.without_books_in_genre(genre_name): This defines a class method on the Author model that accepts a genre_name parameter.
  • left_outer_joins(:books): This performs a left outer join between authors and books. It ensures that authors who don’t have any books are still included in the results.
  • where.not(books: { genre_id: Genre.find_by(name: genre_name)&.id }): This filters out authors who have written books in the specified genre. Genre.find_by(name: genre_name)&.id finds the ID of the genre with the given name. The &. operator is used to handle cases where the genre may not exist.
  • distinct: Ensures that each author is returned only once in the final result set, even if they have multiple books in other genres.

Usage:

To find authors who have never written a book in a given genre, you can call the method like this:

authors = Author.without_books_in_genre('Science Fiction')

This will return all authors who do not have any books in the “Science Fiction” genre.

13.

Create a method to fetch all orders placed by a user with a status of “completed” or “pending”.

Answer

To create a method that fetches all orders placed by a user with a status of “completed” or “pending”, we will assume the following relationships:

  • An Order model belongs to a User.
  • The Order model has a status attribute.

Method in the User model:

class User < ApplicationRecord
  has_many :orders

  # Method to fetch all orders with "completed" or "pending" status
  def completed_or_pending_statuses
    orders.where(status: %w(completed pending))
  end
end

Explanation:

  • completed_or_pending_statuses: This defines an instance method on the User model that returns all orders for the user with a status of “completed” or “pending”.
  • orders.where(status: ['completed', 'pending']): This uses ActiveRecord’s where method to filter orders by the status attribute, selecting only those with a status of either “completed” or “pending”. The %w(completed pending) array is used to specify multiple possible statuses.

Usage:

To fetch all orders placed by a user with a status of “completed” or “pending”, you can call the method like this:

user = User.find(1)  # Replace with the actual user ID
orders = user.completed_or_pending_statuses

This will return all orders placed by the specified user that have a status of either “completed” or “pending”.

14.

Write a controller action to handle user login with session management.

Answer

To handle user login with session management in Rails, you would typically create a SessionsController with a create action to handle the login logic. Here’s an implementation for that:

Steps:

  1. Session Management: You’ll store the user ID in the session to track their login state.
  2. Authentication: Verify the user’s credentials (usually email and password).

Example of SessionsController:

class SessionsController < ApplicationController
  # Action to handle user login
  def create
    # Find the user by email
    @user = User.find_by(email: params[:email])

    # Check if the user exists and the password is correct
    if @user && @user.authenticate(params[:password])
      # Log the user in by storing their user ID in the session
      session[:user_id] = @user.id

      # Redirect to the user's dashboard or home page after successful login
      redirect_to dashboard_path, notice: 'Successfully logged in.'
    else
      # If authentication fails, show an error message
      flash.now[:alert] = 'Invalid email or password.'
      render :new
    end
  end

  # Action to log out the user
  def destroy
    # Clear the session to log out
    session[:user_id] = nil

    # Redirect to the login page after logout
    redirect_to login_path, notice: 'Successfully logged out.'
  end
end

Explanation:

  • create action:
    • First, we search for the user by email (User.find_by(email: params[:email])).
    • If the user is found and the password is correct (@user.authenticate(params[:password])), we store their id in the session (session[:user_id] = @user.id) to log them in.
    • If the authentication is successful, the user is redirected to a dashboard or home page.
    • If the credentials are invalid, we show an error message (flash.now[:alert] = 'Invalid email or password.') and render the login form again.
  • destroy action:
    • To log the user out, we clear the session by setting session[:user_id] to nil.
    • After logout, the user is redirected to the login page.

Usage in Routes:

You would need to set up routes for the login and logout actions. Here’s how you can do that in config/routes.rb:

Rails.application.routes.draw do
  # Route for logging in
  get 'login', to: 'sessions#new'
  post 'login', to: 'sessions#create'

  # Route for logging out
  delete 'logout', to: 'sessions#destroy'

  # Example of a dashboard route that requires authentication
  get 'dashboard', to: 'users#dashboard'
end

new.html.erb for Login Form (optional):

 

To render the login form, you would typically have a view for the login action (app/views/sessions/new.html.erb):

<h1>Login</h1>

<%= form_with url: login_path, method: :post do |form| %>
<div>
<%= form.label :email %>
<%= form.text_field :email %>
</div>

<div>
<%= form.label :password %>
<%= form.password_field :password %>
</div>

<%= form.submit ‘Log in’ %>
<% end %>

<%= link_to ‘Forgot Password?’, new_password_reset_path %>

Security Note:

  • Password Authentication: This assumes the User model has has_secure_password (from the bcrypt gem) for password encryption. If it’s not set up, make sure to include it in your User model:
class User < ApplicationRecord
  has_secure_password  # This adds password validation and encryption
end

With this setup, the user can log in by providing their email and password, and Rails will manage their session state through the session ID.

Usually when talking about a production ready product you’ll probably just use something like Devise which is a lot more secure and was tested by a whole community instead of only you and your team.

15.

Write a model method that calculates the average rating for a product based on reviews.

Answer

To calculate the average rating for a product based on reviews, you can define a method in the Product model that computes the average of the rating attribute from the associated reviews. Assuming the reviews table has a rating field and is associated with the Product model, here’s how you could implement it:

 

Example:

1. Product Model (app/models/product.rb):

class Product < ApplicationRecord
  # Assuming the product has many reviews
  has_many :reviews

  # Method to calculate the average rating for a product
  def average_rating
    # If there are no reviews, return nil or 0 depending on the requirement
    return nil if reviews.empty?

    # Calculate the average of the ratings of all reviews
    reviews.average(:rating).to_f
  end
end

 

Explanation:

  • has_many :reviews: This sets up the association where a product has many reviews.
  • average_rating method:
    • It checks if there are no reviews (reviews.empty?). If there are no reviews, it returns nil (or you can choose to return 0 if that makes more sense for your application).
    • reviews.average(:rating) calculates the average of the rating column from all reviews associated with the product.
    • .to_f ensures the result is a float, as average might return a BigDecimal.

2. Review Model (app/models/review.rb):

class Review < ApplicationRecord
  belongs_to :product

  # Assuming each review has a rating attribute
  validates :rating, presence: true, inclusion: { in: 1..5 }
end

 

Usage:

To call the average_rating method in your application, you can do something like this in your controller or view:

@product = Product.find(params[:id])
@average_rating = @product.average_rating

 

If you display it in a view (e.g., show.html.erb), it could look like this:

 

<p>Average Rating: <%= @product.average_rating %></p>

 

This method will return the average rating of a product based on its reviews, ensuring it’s easily accessible and calculated dynamically.

This could be replaced by a scope , which is arguably a better way to do this but this is just another way of doing it.

16.

Create a method that marks all posts in the draft state as published after a given date.

Answer

To create a method that marks all posts in the “draft” state as “published” after a given date, you can define a class method in the Post model. This method will find all posts with a “draft” status and a created_at date greater than the given date, then update their status to “published.”

Assuming you have a status field in your posts table and the possible statuses are “draft” and “published,” here’s how you can implement it:

 

Example:

 

1. Post Model (app/models/post.rb):

class Post < ApplicationRecord
  # Assuming the Post model has a status field (e.g., 'draft', 'published')

  # Class method to mark all posts as published after a given date
  def self.publish_drafts_after(date)
    # Find all posts with status 'draft' and created_at after the given date
    where(status: 'draft').where('created_at > ?', date).update_all(status: 'published')
  end
end

 

Explanation:

  • self.publish_drafts_after(date): This defines a class method that can be called directly on the Post class.
  • where(status: 'draft'): This filters the posts to only include those with the status “draft.”
  • where('created_at > ?', date): This further filters to include only those posts where the created_at date is after the provided date.
  • update_all(status: 'published'): This updates the status field of the selected posts to “published”. update_all is used because it performs the update directly in the database, which is more efficient than iterating over each post.

 

Usage:

To call this method, you would pass the date after which you want to mark the drafts as published:

# Example: Mark all drafts created after January 1st, 2024 as published
Post.publish_drafts_after('2024-01-01')

 

This will update the status of all posts that are in the “draft” state and were created after the specified date, changing their status to “published.”

 

Notes:

  • update_all does not trigger ActiveRecord callbacks or validations, so it will directly modify the database.
  • If you need to trigger any additional logic (like callbacks or notifications), you might want to iterate over the posts and update them one by one with each instead of using update_all. However, update_all is more efficient when handling bulk updates.
17.

Write a migration to add a published_at column to the articles table and default it to nil.

Answer

To add a published_at column to the articles table and set its default value to nil, you can create a migration like this:

 

Migration Code:

class AddPublishedAtToArticles < ActiveRecord::Migration[6.0]
  def change
    add_column :articles, :published_at, :datetime, default: nil
  end
end

 

Explanation:

  • add_column :articles, :published_at, :datetime: This adds a new column called published_at to the articles table with the type datetime.
  • default: nil: This explicitly sets the default value of the published_at column to nil.

 

Steps to Apply the Migration:

  1. Run the migration with the following command:
    rails db:migrate
    

 

This will add the published_at column to the articles table with a nil default value, meaning it will be nil unless explicitly set to a value.

18.

Write a query that finds all customers who have not placed an order in the past 30 days.

Answer

To find all customers who have not placed an order in the past 30 days, you can write an ActiveRecord query assuming you have a Customer model and an Order model, where the orders table has a customer_id and an order_date (or equivalent) column.

Here’s how you can write the query:

 

ActiveRecord Query:

Customer.left_joins(:orders)
        .where('orders.created_at < ?', 30.days.ago)
        .or(Customer.where(orders: { id: nil }))

 

Explanation:

  1. left_joins(:orders): This creates a left join between the customers table and the orders table, ensuring we include customers who may not have any orders.
  2. where('orders.created_at < ?', 30.days.ago): This filters customers whose orders are older than 30 days.
  3. or(Customer.where(orders: { id: nil })): This handles customers who have no orders at all (i.e., orders.id is nil).

 

SQL Equivalent:

SELECT customers.*
FROM customers
LEFT JOIN orders ON orders.customer_id = customers.id
WHERE orders.created_at < NOW() - INTERVAL 30 DAY OR orders.id IS NULL;

 

This query retrieves all customers who either haven’t placed any orders or whose last order was placed more than 30 days ago.

19.

Write a helper method to format a timestamp into a readable format (e.g., “January 1, 2024”).

Answer

To create a helper method that formats a timestamp into a readable format (e.g., “January 1, 2024”), you can add the following method in your Rails ApplicationHelper or any specific helper file you prefer.

 

Helper Method:

module ApplicationHelper
  def format_timestamp(timestamp)
    timestamp.strftime("%B %d, %Y") if timestamp.present?
  end
end

 

Explanation:

  • timestamp.strftime("%B %d, %Y"): This formats the timestamp to a human-readable string:
    • %B: Full month name (e.g., “January”)
    • %d: Day of the month with leading zeros (e.g., “01”)
    • %Y: Year with century (e.g., “2024”)
  • timestamp.present?: Checks if the timestamp is not nil or empty to avoid errors.

 

Usage:

In your views, you can use this helper method like so:

 

<%= format_timestamp(@post.created_at) %>

 

This would display the timestamp as something like “January 1, 2024”.

20.

Write a method that updates a product’s inventory based on a transaction of purchased quantities.

Answer

To create a method that updates a product’s inventory based on a transaction of purchased quantities, you would need to define this method in the Product model. The method would subtract the purchased quantity from the current inventory and ensure that the inventory does not become negative.

 

Here’s how you could implement this:

Model Method:

class Product < ApplicationRecord
  # Assuming Product has 'inventory' attribute to track stock

  # Method to update inventory after a purchase
  def update_inventory(purchased_quantity)
    if purchased_quantity <= 0
      errors.add(:base, "Purchased quantity must greater than zero")
      return false
    end

    if inventory >= purchased_quantity
      self.inventory -= purchased_quantity
      save
    else
      errors.add(:inventory, "Not enough stock available")
      return false
    end
  end
end

 

Explanation:

  • purchased_quantity: The quantity of items purchased in a transaction.
  • inventory >= purchased_quantity: Checks if there is enough stock available to complete the transaction.
  • self.inventory -= purchased_quantity: Decreases the product’s inventory based on the purchased quantity.
  • save: Persists the changes to the database.
  • Error Handling:
    • If the purchased_quantity is less than or equal to zero, it adds an error.
    • If there’s not enough stock available, it adds an error indicating that.

 

Usage:

You can call this method after processing a purchase:

product = Product.find(product_id)
if product.update_inventory(purchased_quantity)
  # Handle successful inventory update
else
  # Handle failure (e.g., not enough stock or invalid quantity)
  flash[:error] = product.errors.full_messages.to_sentence
end

 

Notes:

  • This method assumes that your products table has an inventory column to track the current stock of the product.
  • You can add further validation (like ensuring the inventory doesn’t go negative) based on your application’s needs.
21.

Write a scope to fetch all users with a confirmed email address who have logged in within the last week.

Answer

To write a scope that fetches all users with a confirmed email address who have logged in within the last week, you would need to define this scope in the User model. The scope would check if the user’s email is confirmed and if they have logged in within the last 7 days.

 

Here’s how you can implement this scope:

 

Model Scope:

class User < ApplicationRecord
# Assuming the User model has 'confirmed' attribute for email confirmation
# and 'last_login_at' attribute for tracking the last login time

# Scope to fetch users with confirmed email and who logged in within the last week
scope :with_confirmed_email_and_logged_in_last_week, -> {
      where(confirmed: true, last_login_at: 7.days.ago..)
  }
end

 

Explanation:

  • confirmed: true: Filters users who have confirmed their email address (assuming there is a confirmed boolean column in the users table).
  • last_login_at >= ?: Filters users who logged in within the last 7 days (assuming last_login_at is a datetime column).
  • 7.days.ago: Rails’ time helper to return the date/time exactly 7 days ago from the current time.

 

Usage:

You can call this scope to fetch the users:

users = User.with_confirmed_email_and_logged_in_last_week

 

This will return all users who have a confirmed email address and have logged in within the last week.

22.

Write a Rails method that sends a welcome email to new users after their account is created.

Answer

To send a welcome email to new users after their account is created in Rails, you can use Active Job with a mailer to send the email asynchronously. Here’s how you can implement it:

  1. Generate the Mailer

First, create a mailer to handle sending the welcome email. You can generate it with the following command:

rails generate mailer UserMailer

 

This will create a user_mailer.rb file in app/mailers and a corresponding view file for the email templates.

 

  1. Define the Mailer Method

Inside app/mailers/user_mailer.rb, define the method to send the welcome email.

class UserMailer < ApplicationMailer
default from: 'no-reply@yourapp.com'

def welcome_email(user)
@user = user
@url = 'http://yourapp.com/login' # Replace with your login URL or relevant link
mail(to: @user.email, subject: 'Welcome to Our Platform!')
end
end
  1. Create the Email Template

Create the email template to render the welcome message. In app/views/user_mailer/, create a file called welcome_email.html.erb and add the content of the email.

 

<!– app/views/user_mailer/welcome_email.html.erb –>
<h1>Welcome to Our Platform, <%= @user.name %>!</h1>

<p>Thank you for signing up. We are excited to have you on board!</p>

<p>To get started, please <a href=”<%= @url %>”>login here</a>.</p>

<p>Best Regards,</p>
<p>Your Company Team</p>

 

You can also create a plain-text version welcome_email.text.erb for email clients that do not support HTML.

 

<!– app/views/user_mailer/welcome_email.text.erb –>
Welcome to Our Platform, <%= @user.name %>!

Thank you for signing up. We are excited to have you on board!

To get started, please login here: <%= @url %>

Best Regards,
Your Company Team

 

  1. Trigger the Email After User Creation

To send the email after a new user is created, use the after_create callback in the User model.

In app/models/user.rb, add the following code:

class User < ApplicationRecord
# Assuming you have a user model with necessary validations

after_create :send_welcome_email

private

def send_welcome_email
UserMailer.welcome_email(self).deliver_later # This sends the email asynchronously
end
end

 

Explanation:

  • after_create: This callback triggers after the user is saved in the database, ensuring that the email is sent only when the user is successfully created.
  • deliver_later: This uses Active Job to send the email asynchronously, improving performance and ensuring that the email doesn’t delay the user creation process.
  1. Background Job Configuration (Optional)

Make sure that you have a background job processor set up (e.g., Sidekiq, Delayed Job, or ActiveJob’s default Async adapter) to handle the email delivery in the background. If you’re using the default Rails async adapter, no further setup is required, but for production environments, you’ll need a more robust background job system like Sidekiq.

Conclusion:

With the above steps, every time a new user is created, a welcome email will be sent asynchronously.

23.

Write a Rails method that checks if a user is authorized to edit a post (only the owner or admin should be able to edit).

Answer

To implement a method that checks if a user is authorized to edit a post (only the owner or an admin should be able to edit), you can create a method in the Post model or within a controller depending on how you’d like to structure it.

Here’s a method that checks whether the user is either the owner of the post or an admin:

  1. Method in the Post Model

In the Post model (app/models/post.rb), add the following method to check the authorization:

class Post < ApplicationRecord
  belongs_to :user  # Assuming a post belongs to a user

  # Method to check if a user can edit the post
  def editable_by?(user)
    user == self.user || user.admin?  # Checks if the user is the owner or an admin
  end
end
  1. Controller Action Example

You can use the editable_by? method inside your controller action to check authorization before allowing the edit action.

In the PostsController (app/controllers/posts_controller.rb), modify the edit and update actions to check if the current user can edit the post:

class PostsController < ApplicationController
  before_action :set_post, only: [:edit, :update]
  before_action :check_authorization, only: [:edit, :update]

  def edit
    # If authorized, render the edit form
  end

  def update
    if @post.update(post_params)
      redirect_to @post, notice: 'Post was successfully updated.'
    else
      render :edit
    end
  end

  private

  def set_post
    @post = Post.find(params[:id])
  end

  def check_authorization
    unless @post.editable_by?(current_user)
      redirect_to @post, alert: 'You are not authorized to edit this post.'
    end
  end

  def post_params
    params.require(:post).permit(:title, :body)
  end
end
  1. Explanation:
  • editable_by? Method: This method checks if the user is the owner of the post (user == self.user) or if the user is an admin (user.admin?). You may need to adjust the admin? method depending on how you define admin users in your application.
  • Controller Action:
    • The check_authorization method is used as a before_action to verify if the user is allowed to edit the post before rendering the edit page or processing the update action.
    • If the user is not authorized, they are redirected back to the post with an alert message.
  1. Assumptions:
  • The User model has an admin? method or boolean field (admin).
  • current_user is available (through Devise or any other authentication system).
  • Post belongs to User via a user_id field.

With this method, only the owner of the post or an admin can edit it, and the system will prevent unauthorized access to the edit functionality.

24.

Write a query that finds all products in a certain category and orders them by price in descending order.

Answer

To write a query that finds all products in a certain category and orders them by price in descending order, you can use ActiveRecord query methods in Rails. Assuming you have a Product model and a Category model, and the products table has a category_id column to establish the relationship between products and categories:

Query using ActiveRecord:

Product.where(category_id: category_id).order(price: :desc)

 

Explanation:

  1. where(category_id: category_id): This filters the products by the category_id that you provide.
  2. order(price: :desc): This orders the products by their price column in descending order.

Example with category name:

If you have a Category model and want to find all products in a category by name, you could do something like this:

category = Category.find_by(name: 'Electronics')
products = category.products.order(price: :desc)


Explanation:

  • Category.find_by(name: 'Electronics'): Finds the category with the name ‘Electronics’.
  • category.products: Fetches all products associated with that category.
  • order(price: :desc): Orders the products by price in descending order.

 

This will return all products in the ‘Electronics’ category, sorted by price from highest to lowest.

25.

Write a method to fetch all users who have a birthday in the current month.

Answer

To write a method that fetches all users who have a birthday in the current month, assuming the User model has a birthday column (of type date), you can use the following ActiveRecord query:

 

Method in User Model:

class User < ApplicationRecord
  def self.birthday_this_month
    where('extract(month from birthday) = ?', Date.current.month)
  end
end

 

Explanation:

  • extract(month from birthday): This is a SQL function that extracts the month part from the birthday column.
  • Date.current.month: This returns the current month as an integer.
  • where: This filters users whose birthday month matches the current month.

 

Usage Example:

users_with_birthday_this_month = User.birthday_this_month

 

This will return all users whose birthday falls in the current month.

26.

Create a method to check if a product has been reviewed more than 5 times.

Answer

To create a method that checks if a product has been reviewed more than 5 times, assuming you have a Review model where each review belongs to a product, you can use the following method in the Product model.

 

Method in Product Model:

class Product < ApplicationRecord
  has_many :reviews

  def reviewed_more_than_5_times?
    reviews.count > 5
  end
end

 

Explanation:

  • has_many :reviews: This establishes the relationship between the Product and Review models, where a product can have many reviews.
  • reviews.count > 5: This checks if the product has more than 5 reviews. reviews.count will return the number of reviews associated with the product.

 

Usage Example:

product = Product.find(1)

if product.reviewed_more_than_5_times?
  puts "This product has been reviewed more than 5 times."
else
  puts "This product has 5 or fewer reviews."
end

 

This method checks if a given product has been reviewed more than 5 times and returns true or false.

27.

Write a method that retrieves the last 10 posts from each user.

Answer

To retrieve the last 10 posts from each user, assuming you have a User model and a Post model where each post belongs to a user, you can define the following method in the User model.

 

Method in User Model:

class User < ApplicationRecord
  has_many :posts

  def last_10_posts
    posts.order(created_at: :desc).limit(10)
  end
end

 

Explanation:

  • has_many :posts: This establishes a one-to-many relationship between User and Post, where a user can have many posts.
  • posts.order(created_at: :desc).limit(10): This queries the posts for the user, ordering them by the created_at field in descending order to get the most recent posts, and then limits the result to the last 10 posts.

 

Usage Example:

user = User.find(1)
last_10_posts = user.last_10_posts

last_10_posts.each do |post|
  puts post.title
end

 

This method will return the last 10 posts for a given user, sorted by created_at in descending order (most recent posts first).

28.

Write a Rails helper method that generates a link to the user profile page, showing the user’s name as the link text.

Answer

To create a Rails helper method that generates a link to a user profile page, displaying the user’s name as the link text, follow the steps below:

 

Helper Method:

You can define the helper method in your application_helper.rb file or in a specific helper file related to users (e.g., users_helper.rb).

module ApplicationHelper
  def user_profile_link(user)
    link_to user.name, user_path(user)
  end
end

 

Explanation:

  • link_to: This Rails helper generates an HTML link. It takes two arguments: the text to display (in this case, the user’s name) and the path (here, we use user_path(user) to generate the URL for the user’s profile page).
  • user.name: This refers to the name attribute of the user object, which will be used as the text of the link.
  • user_path(user): This generates the URL for the user’s profile page using the user_path route helper (you need to have a show action for users in your UsersController for this to work).

 

Usage Example:

<%= user_profile_link(@user) %>

 

In this example, @user is an instance of the User model. This will generate an HTML link that displays the user’s name as the link text, which directs the user to their profile page.

29.

Write a method that sends a reminder email to all users who have pending tasks.

Answer

To write a method that sends a reminder email to all users who have pending tasks, you will need a few things:

  1. A User model with a method to retrieve users with pending tasks.
  2. A Task model with a status attribute to indicate whether the task is pending or not.
  3. An email reminder system, which requires creating a mailer for the email.

 

Here’s how to implement it:

Step 1: Task Model (Task)

Make sure the Task model has a status attribute (e.g., "pending", "completed") and belongs to a User.

class Task < ApplicationRecord
  belongs_to :user
  # status can be "pending", "completed", etc.
  enum status: { pending: 0, completed: 1 }
end

 

Step 2: User Model (User)

In the User model, define a method that returns all users with pending tasks.

class User < ApplicationRecord
  has_many :tasks

  # Method to get users with pending tasks
  def self.with_pending_tasks
    joins(:tasks).where(tasks: { status: :pending }).distinct
  end
end

 

Step 3: ReminderMailer (ReminderMailer)

Create a mailer that will send the reminder email to users.

Run the following command to generate the mailer:

rails generate mailer ReminderMailer

 

In the ReminderMailer class:

class ReminderMailer < ApplicationMailer
  default from: 'no-reply@example.com'

  def reminder_email(user)
    @user = user
    @tasks = @user.tasks.pending
    mail(to: @user.email, subject: 'Reminder: You have pending tasks!')
  end
end

 

Step 4: Sending Reminder Emails (send_reminders Method)

Now, you need a method to send the reminder emails to all users with pending tasks. This method can be added to a service class or an action in your controller, depending on where you want to trigger the emails.

 

Here’s an example method that would be placed in a service object or background job:

class TaskReminderService
  def self.send_reminders
    # Fetch all users with pending tasks
    users = User.with_pending_tasks

    # Send reminder email to each user
    users.each do |user|
      ReminderMailer.reminder_email(user).deliver_later
    end
  end
end

 

Step 5: Scheduling the Reminder (Optional)

You might want to schedule this task to run periodically. You can do this using a background job like Sidekiq, Delayed::Job, or Rails’ built-in Active Job with a scheduler like whenever or cron.

 

Here’s an example using Active Job with a background queue:

# In your background job
class SendTaskRemindersJob < ApplicationJob
  queue_as :default

  def perform
    TaskReminderService.send_reminders
  end
end

 

Then, you can schedule the job to run at a specific time:

# For example, use 'whenever' gem to schedule a cron job to run daily
every :day, at: '9:00 am' do
  runner "SendTaskRemindersJob.perform_later"
end

 

Step 6: Triggering the Reminder Manually (Optional)

If you want to manually send reminders, you can call the method:

TaskReminderService.send_reminders

 

Summary of Flow:

  • The Task model tracks pending tasks.
  • The User model has a method to get users with pending tasks.
  • The ReminderMailer sends the email to the user.
  • The TaskReminderService triggers the email sending process.
  • You can schedule or manually trigger the task reminder.

 

This approach ensures that all users with pending tasks receive a reminder email.

30.

Write a migration to create a comments table with columns for post_id, user_id, content, and timestamps.

Answer

Here’s how you can write a migration to create a comments table with the required columns (post_id, user_id, content, and timestamps):

Run the following command to generate the migration:

rails generate migration CreateComments post_id:integer user_id:integer content:text

 

Then, in the generated migration file (typically located in db/migrate/), modify it as follows:

class CreateComments < ActiveRecord::Migration[6.0]
  def change
    create_table :comments do |t|
      t.references :post, null: false, foreign_key: true
      t.references :user, null: false, foreign_key: true
      t.text :content, null: false

      t.timestamps
    end
  end
end

 

Explanation:

  1. Columns:
    • post_id: This is :post on the migration. The ID of the post being commented on. It references the posts table.
    • user_id: This is :user on the migration. The ID of the user making the comment. It references the users table.
    • content: The content of the comment (stored as text).
    • timestamps: Rails adds created_at and updated_at columns automatically.
  2. Foreign Keys:
    • The foreign_key: true ensure referential integrity, meaning the post_id column must match an existing record in the posts table, and user_id must match an existing record in the users table.

 

Running the Migration:

After creating the migration, run the following command to apply the migration and create the comments table:

rails db:migrate

This will create the comments table in your database with the specified columns and foreign key constraints.

Ruby on Rails Developer hiring resources
Hire Ruby on Rails Developers
Hire fast and on budget—place a request, interview 1-3 curated developers, and get the best one onboarded by next Friday. Full-time or part-time, with optimal overlap.
Hire now
Q&A about hiring Ruby on Rails Developers
Want to know more about hiring Ruby on Rails Developers? Lemon.io got you covered
Read Q&A
Ruby on Rails Developer Job Description Template
Attract top Ruby on Rails developers with a clear, compelling job description. Use our expert template to save time and get high-quality applicants fast.
Check the Job Description

Hire remote Ruby on Rails developers

Developers who got their wings at:
Testimonials
star star star star star
Gotta drop in here for some Kudos. I’m 2 weeks into working with a super legit dev on a critical project, and he’s meeting every expectation so far 👏
avatar
Francis Harrington
Founder at ProCloud Consulting, US
star star star star star
I recommend Lemon to anyone looking for top-quality engineering talent. We previously worked with TopTal and many others, but Lemon gives us consistently incredible candidates.
avatar
Allie Fleder
Co-Founder & COO at SimplyWise, US
star star star star star
I've worked with some incredible devs in my career, but the experience I am having with my dev through Lemon.io is so 🔥. I feel invincible as a founder. So thankful to you and the team!
avatar
Michele Serro
Founder of Doorsteps.co.uk, UK

Simplify your hiring process with remote Ruby on Rails developers

Popular Ruby on Rails Development questions

What are the key differences between Ruby on Rails and Node.js?

Following are some of the main differences between Ruby on Rails and Node.js: Ruby on Rails is an out-and-out stack web application framework following the Convention over Configuration philosophy, hence ideal for rapid development. While Node.js is a JavaScript runtime environment that makes the platform suitable for event-driven applications assumed to scale considerably, especially those having real-time capability needs like chat apps. While the Rails address the demand for productivity and simplicity, Node.js provides high performance, flexibility in particular for I/O-bound applications.

What are the key advantages of using Ruby on Rails for startups?

Ruby on Rails work with startups, because they can be enabled to bring speed in development and hence an edge to get products out into the market in the quickest time. The philosophy of convention over configuration reduces extensive setup, and a wide ecosystem of gems is able to develop functions from existing code by not having to reinvent the wheel. Rails are also at the top of popularity: good community support means that a startup will find enough resources to see them through in the form of tutorials, plugins, and expert advice.

How do Ruby on Rails Developers handle API integrations?

Ruby on Rails Developers handle API integrations through RESTful architecture by nature. It makes creating and consuming APIs pretty straightforward. Rails provide facilities such as ActiveResource, which is good to go for RESTful APIs, and HTTParty for making requests via HTTP. Of course, they could use gems such as Devise and OAuth2 for handling authentication when integrating third-party APIs. Further, Rails also supports JSON and XML, which makes exchanging data with other services pretty easy.

What are the best practices for scaling a Ruby on Rails application?

The Ruby on Rails application should be optimized for scaling by taking on more users than usual for performance. Keeping pace with the best practices, lighten the load off the database by caching strategies, including fragment caching and Redis. Enhancement of load balancing across multiple servers should also be done, including background job processing handled by tools like Sidekiq, which handle responsiveness. That performance remains optimized on scaling of the application, regarding database queries and indexing, while making use of content delivery networks for assets.

What is Ruby on Rails mainly used for?

Ruby on Rails is a framework for developing mostly web applications. It focuses on convention over configuration and will be good for rapid development, keeping in consideration the cleanliness of the code and being maintainable. Rails is very famous for startups and small- to mid-sized web applications, thanks to its capabilities for quick prototyping and handling most of the general web development tasks such as database management, routing, and authentication. It finds high usage for e-commerce platforms, content management systems, and social networking sites.

image

Ready-to-interview vetted Ruby on Rails developers are waiting for your request