### Case equality operator (===) if the right operand has an IS A relationship with the left operand. As such, the popular name case equality operator is misleading. This SO answer describes it thus: the best way to describe a === b is "if I have a drawer labeled a, does it make sense to put b in it?" In other words, does the set a include the member b? ``` (1..5) === 3 => true (1..5) === 6 => false Integer === 42 => true Integer === 'fourtytwo' # => false /ell/ === 'Hello' /ell/ === 'Foobar' => true => false ``` -------- ### 1. Lambdas: Strict Argument Checking: Lambdas are strict about the number of arguments you pass to them. If you pass the wrong number of arguments, they throw an ArgumentError. ``` lamb = lambda { |x, y| puts "#{x} and #{y}" } lamb.call(1, 2) # => "1 and 2" lamb.call(1) # => ArgumentError: wrong number of arguments (given 1, expected 2) # Using stabby lambda syntax add = ->(a, b) { a + b } puts add.call(2, 3) # Output: 5 # Using lambda keyword multiply = lambda { |a, b| a * b } puts multiply.call(2, 3) # Output: 6 ``` ### 2. Procs: Flexible Argument Checking: Procs are more lenient with the number of arguments you pass to them. If you pass fewer arguments than expected, the missing ones are set to nil. If you pass more, the extras are ignored. ``` pr = Proc.new { |x, y| puts "#{x.inspect} and #{y.inspect}" } pr.call(1, 2) # => "1 and 2" pr.call(1) # => "1 and nil" pr.call(1, 2, 3) # => "1 and 2" # Defining a Proc square = Proc.new { |x| x * x } puts square.call(4) # Output: 16 # Proc doesn't check the number of arguments puts square.call # Output: nil ``` These differences underline the importance of choosing the right tool for the job. If you want strict arity (i.e., strict number of arguments) checking, use a lambda. If you want flexibility with the number of arguments, use a proc. ----- ### 3. destroy vs delete: #### destroy: Calls the model's before_destroy and after_destroy callbacks and any associated callbacks. Objects are instantiated, which can have some overhead. Will also destroy dependent associations. ``` user = User.find(1) user.destroy ``` #### delete: Skips callbacks. Directly deletes the record in the database without instantiating the object. Doesn't trigger dependent destruction. ``` user = User.find(1) user.delete ``` #### Block: A chunk of code that can be passed to a method. ``` [1,2,3].each { |num| puts num } ``` ### Inject Vs Reduce inject and reduce can be used with shorthand operators for common operations like addition, multiplication, etc. This makes the code more concise. #### Inject ``` result = [1, 2, 3, 4].inject(:+) puts result # Output: 10 result = [1, 2, 3, 4].inject(:*) puts result # Output: 24 result = [1, 2, 3, 4].inject(10, :+) puts result # Output: 20 (10 + 1 + 2 + 3 + 4) ``` #### Reduce ``` result = [1, 2, 3, 4].reduce(:+) puts result # Output: 10 result = [1, 2, 3, 4].reduce(:*) puts result # Output: 24 result = [1, 2, 3, 4].reduce(2, :*) puts result # Output: 48 (2 * 1 * 2 * 3 * 4) ``` ### Optimistic vs Pessimistic Locking: #### Optimistic Locking: Assumes conflicts will rarely occur. When saving, checks if the record has been updated by another transaction. If it has, raises a conflict (e.g., ActiveRecord::StaleObjectError in Rails). #### Pessimistic Locking: Locks the record for the duration of the transaction. Other transactions are blocked until the lock is released. Can prevent data inconsistencies but can lead to deadlocks. ### Rails directory structure: **app/**: This is the heart of any Rails application. It's where you'll find models, views, controllers, and other core components of the application's functionality. **app/assets/**: Contains subdirectories for JavaScripts, stylesheets, and images. This is where the asset pipeline will look for assets. **app/controllers/**: This is where your application controllers live. Controllers handle the web requests, manage logic, and hand off to the views to render data. **app/helpers/**: Helper modules are meant to provide methods that assist with rendering views. **app/mailers/**: This is where mailer classes live, responsible for sending emails. **app/models/**: This is where you'll place your ActiveRecord models - classes that represent tables in the database and where you handle related business logic. **app/views/**: Contains the templates that will be used to render the views. They're organized based on the controller they belong to. **app/channels/**: Used for Action Cable setup for real-time features. **app/jobs/**: Place for background jobs, which can be executed asynchronously. **app/config/**: Contains locale files and initializers. **bin/**: Contains Rails script files that help with various tasks (e.g., starting a server or console). **config/**: Contains configuration files: **config/routes.rb**: Defines the routes for your application. **config/application.rb**: General configuration settings. **config/environments/**: Settings specific to development, production, and test environments. **config/initializers/**: Initialization code for various libraries and gems. **config/database.yml**: Database configuration. **db/**: Contains everything related to the database: **db/migrate/**: Contains all the migration files for altering the database schema. **db/schema.rb**: Represents the current state of the database schema. **log/**: Contains application log files. **public/**: Files in this directory are served directly by the web server. For example, error pages like 404.html are found here. **test/ or spec/**: Contains tests for your application. Whether it's "test" or "spec" often depends on the testing framework you're using (e.g., Minitest vs RSpec). **tmp/**: Temporary files (e.g., cache, session, sockets). **vendor/**: A place for third-party code. In a typical Rails application, you might use gems instead, but sometimes you might want to vendor certain libraries. **.gitignore**: A file used by Git to exclude certain files and directories from the repository. **Gemfile and Gemfile.lock**: These files allow you to specify which gems (libraries) your application depends on. The Gemfile.lock is an auto-generated file that ensures all developers on a project are using the exact same gem versions. **Rakefile**: Provides tasks that can be run from the command line using the rake command. Rails comes with many built-in rake tasks for things like database migrations. **README.md**: A markdown file where you should describe your project, how to set it up, and other pertinent information. ### PUT VS PATCH #### PUT: Idempotent: Making the same PUT request multiple times will have the same effect as making it once. This implies that a PUT request is expected to provide a full representation of the resource being updated. Definition: PUT is used to update or create a resource at a particular URI. If the resource exists, it's replaced. If it doesn't exist, it's created (if the server supports doing so). Usage: Given that PUT is idempotent, it typically requires the client to send the entire updated representation of the resource. If you only send a partial update to a PUT endpoint, it might either result in an error or in incomplete data for the resource, depending on the API's design. ``` PUT /users/123 { "id": 123, "name": "John Doe", "email": "johndoe@example.com", "age": 30 } ``` **Even if only the age field changed, you'd typically send the entire user object to the PUT endpoint.** #### PATCH: Not Necessarily Idempotent: While it can be implemented in an idempotent manner, PATCH requests by nature do not guarantee idempotency. This is because PATCH is intended to provide partial updates to a resource. Definition: PATCH is used to apply partial modifications to a resource. Usage: Unlike PUT, which requires the entire resource data, PATCH requires only the changes. This makes PATCH more efficient when you only need to update a small part of a resource. Example: If you're only updating the age of a user: ``` PATCH /users/123 { "age": 31 } ``` **Here, you only send the age attribute with its new value.** ##### Key Takeaways: Use PUT when you're replacing a resource entirely or providing a full representation of the resource. Use PATCH when you're making partial updates to a resource. ### Cron: A cron is a Unix-based job scheduling program that allows users to run scripts, commands, or software at specified times and dates. The name "cron" comes from the Greek word "chronos", which means "time", and it's used to automate tasks like backups, sending emails, or any repetitive job you can think of. #### Crontab: crontab (short for "cron table") is the command used to view, add, remove, or modify cron jobs for a user. The crontab command interfaces with the cron daemon, managing the user's job tables. ``` * * * * * command_to_be_executed - - - - - | | | | | | | | | +----- day of the week (0 - 6) [0 is Sunday, or use names] | | | +------- month (1 - 12) | | +--------- day of the month (1 - 31) | +----------- hour (0 - 23) +------------- min (0 - 59) ``` The **crontab -l** command is used to list all the cron jobs for the currently logged-in user. Let's say you want to schedule a backup script, backup.sh, to run every day at 3:30 AM. ``` 30 3 * * * /path/to/backup.sh ``` If you wish to run a Python script, data_cleanup.py, every Monday at 5:45 PM: ``` 45 17 * * 1 /usr/bin/python3 /path/to/data_cleanup.py ``` ### Use Cases: **Backups**: Automate database or file system backups daily, weekly, or at any desired frequency. **Reports**: Generate and email weekly sales reports, performance metrics, etc. **Housekeeping**: Clear out temporary files or cache directories periodically to free up space. **Notifications**: Send out reminders, e.g., for expiring subscriptions, upcoming appointments, or system metrics. **Updates**: Automatically update system software or databases. **Monitoring**: Check the health status of services or websites at regular intervals and alert if they're down ### Modules: In Ruby, a module is a collection of methods, constants, and class variables. Modules are not classes and can't create instances. They're used for namespacing and mixins (injecting functionality into classes). ``` module Greetable def greet "Hello, #{self.name}" end end class User include Greetable attr_accessor :name def initialize(name) @name = name end end user = User.new("Alice") puts user.greet # Outputs: Hello, Alice ``` ### Concerns: In Rails, a concern is a specialized module that not only allows you to group related model (or controller) methods but also allows you to add class-level methods and scopes. It's Rails' way to make model or controller code more modular and organized. ``` # app/models/concerns/taggable.rb module Taggable extend ActiveSupport::Concern included do has_many :tags end def tag_list tags.map(&:name).join(", ") end class_methods do def most_tagged # Some code to find the most tagged instance end end end # app/models/post.rb class Post < ApplicationRecord include Taggable end ``` ### Use Cases: **Modules**: When you want to share functionality between classes or prevent name clashes by namespacing. **Concerns**: When your Rails models or controllers become bulky, and you want to extract and group related functionalities into separate modules. They also allow for a cleaner way to extend Active Record with both instance and class methods. ### Save an object without callbacks: In Rails, Active Record callbacks allow you to attach code to certain lifecycle events of an object, like before it's saved, after it's created, etc. However, there might be situations where you want to save an object without triggering these callbacks. Method: update_column and update_columns update_column and update_columns methods allow you to update attributes of an object directly in the database, skipping validations and callbacks. ``` class User < ApplicationRecord before_save :set_default_username def set_default_username self.username ||= "default_username" end end user = User.find(1) user.update_column(:username, 'new_username') # or for multiple columns # user.update_columns(username: 'new_username', email: 'new_email@example.com') ``` ### Rake vs Rack #### Rake: It's a build program written in Ruby. Used for tasks like database setup, cleanup, and general application maintenance tasks. Rakefile in a Rails project defines tasks which can be executed. #### Rack: It's an interface between web servers and Ruby frameworks, including Rails. Provides a minimal API to connect web servers that support Ruby. A Rails application is actually a Rack application under-the-hood. ### Use Cases: #### Rake: Running database migrations. Clearing temporary files. Running custom developer-defined tasks. #### Rack: Middleware management. Connecting Rails to servers like Puma, Unicorn, etc. Building micro-frameworks or small web applications. ### Application Server vs Web Server #### Application Server (like Puma, Unicorn): Runs your Rails application. Handles Ruby code execution. Can serve multiple requests simultaneously, depending on the configuration (threaded, multi-process, etc.). Typically slower at serving static files than a web server. #### Web Server (like Nginx, Apache): Can serve static files very efficiently. Can handle SSL/TLS termination, load balancing, and more. In a typical Rails deployment, sits in front of the application server, forwarding dynamic requests to it. ##### Use Cases: ###### Application Server: Running the Ruby on Rails code. Serving dynamic content. ###### Web Server: SSL/TLS termination. Serving static assets like images, stylesheets, and JavaScript files. Load balancing between multiple application server instances. Caching content for faster response times. ### has_and_belongs_to_many Table Architecture The has_and_belongs_to_many association creates a direct many-to-many connection between models without an explicit intermediary model. To implement it, a join table is required, and by convention, the join table's name is the union of the participating model tables in alphabetical order. Suppose we have a students and courses table, and we want to establish a many-to-many relationship between them: ``` rails generate migration CreateJoinTableStudentsCourses students courses class CreateJoinTableStudentsCourses < ActiveRecord::Migration[6.0] def change create_join_table :students, :courses do |t| t.index [:student_id, :course_id] t.index [:course_id, :student_id] end end end # student.rb class Student < ApplicationRecord has_and_belongs_to_many :courses end # course.rb class Course < ApplicationRecord has_and_belongs_to_many :students end ``` Many developers prefer to use has_many :through instead of has_and_belongs_to_many because it offers more flexibility by allowing you to work with the relationship model directly, adding validations, callbacks, or extra attributes to the join model. ### **Flatten Method logic** ``` def custom_flatten(array, result = []) array.each do |element| if element.is_a?(Array) custom_flatten(element, result) else result << element end end result end # Test the custom_flatten method nested_array = [1, [2, [3, 4]], [5, 6]] flattened_array = custom_flatten(nested_array) puts flattened_array.inspect # Output should be [1, 2, 3, 4, 5, 6] ``` ### MetaProgramming Examples This ability to define and alter the structure and behavior of objects and classes during execution is one of Ruby's powerful features. #### Send() Method send() is used to pass message to object. send() is an instance method of the Object class. The first argument in send() is the message that you're sending to the object - that is, the name of a method. It could be string or symbol but symbols are preferred. Then arguments those need to pass in method, those will be the remaining arguments in send(). ``` class Hello def hello(*args) puts 'Hello ' + args.join(' ') end end h = Hello.new h.send :hello, 'gentle', 'readers' #=> "Hello gentle readers" # h.send(:hello, 'gentle', 'readers') #=> Here :hello is method and rest are the arguments to method. ``` ``` class Account attr_accessor :name, :email, :notes, :address def assign_values(values) values.each_key do |k, v| # How send method would look a like # self.name = value[k] self.send("#{k}=", values[k]) end end end user_info = { name: 'Matt', email: 'test@gms.com', address: '132 random st.', notes: "annoying customer" } account = Account.new If attributes gets increase then we would messup the code #--------- Bad way -------------- account.name = user_info[:name] account.address = user_info[:address] account.email = user_info[:email] account.notes = user_info[:notes] # --------- Meta Programing way -------------- account.assign_values(user_info) # With single line we can assign n number of attributes puts account.inspect Note: send() itself is not recommended anymore. Use __send__() which has the power to call private methods, or (recommended) public_send() ``` #### Dynamic Filters: Using metaprogramming to dynamically create methods for filtering photos based on attributes. ``` class Album FILTERS = [:by_date, :by_location, :by_tag] FILTERS.each do |filter| define_method("photos_#{filter}") do |value| # Logic to filter photos by the given criteria puts "Filtering photos #{filter} with value #{value}" end end end album = Album.new album.photos_by_date("2023-08-25") => Filtering photos by_date with value 2023-08-25 album.photos_by_location("Paris") => Filtering photos by_location with value Paris ``` #### Dynamic Event Hooks: Creating hooks for certain events in the lifecycle of a photo (e.g., when a photo is viewed, liked, shared). ``` class Photo EVENTS = [:viewed, :liked, :shared] EVENTS.each do |event| define_method("on_#{event}") do # Logic to handle the event puts "Photo has been #{event}" end end end photo = Photo.new photo.on_viewed photo.on_shared ``` #### Dynamic Tagging System: Allow users to add custom tags to photos and then retrieve photos by those tags. ``` class Photo def add_tag(tag) # Logic to add a tag to a photo self.tags << tag # Dynamically create a method to check for this specific tag self.class.define_method("with_#{tag}") do # Logic to fetch photos with this specific tag puts "Fetching photos with tag: #{tag}" end end end photo = Photo.new photo.add_tag("vacation") Photo.with_vacation # Fetching photos with tag: vacation ``` Imagine you want to categorize your photos into different genres, like 'Portraits', 'Landscapes', 'Wildlife', etc. With metaprogramming, we can dynamically generate methods to categorize and retrieve photos based on these categories: 1. Define a Photo class: ``` class Photo attr_accessor :title, :category @@photos = [] def initialize(title, category = nil) @title = title @category = category @@photos << self end def self.all @@photos end def self.categorize(name) # Dynamically generate method to get photos of this category define_singleton_method("in_#{name.downcase}") do @@photos.select { |photo| photo.category == name } end end def self.categories(*names) names.each { |name| categorize(name) } end end ``` Define categories using the categories method: ``` Photo.categories('Portraits', 'Landscapes', 'Wildlife') ``` ``` Create and categorize photos: photo1 = Photo.new("Portrait of John", "Portraits") photo2 = Photo.new("Sunset at Beach", "Landscapes") photo3 = Photo.new("Eagle in the Sky", "Wildlife") photo4 = Photo.new("Mountain Range", "Landscapes") Use the dynamically generated methods to retrieve photos by category: puts Photo.in_portraits.map(&:title) # ["Portrait of John"] puts Photo.in_landscapes.map(&:title) # ["Sunset at Beach", "Mountain Range"] puts Photo.in_wildlife.map(&:title) # ["Eagle in the Sky"] ``` 1. We've created a simple Photo class to represent photos with titles and categories. 2. The categorize method dynamically generates methods to filter photos by category. 3. The categories method is a utility to define multiple categories at once. Each category passed to this method will create a new method to retrieve photos in that category. 4. We then create and categorize photos. 5. Lastly, we use the dynamically generated methods to retrieve photos by their category. ### INNER JOIN An INNER JOIN returns only the rows that have matching values in both tables. ![](https://hackmd.io/_uploads/H1GPBniT3.png) ``` SELECT Students.name, Scores.score FROM Students INNER JOIN Scores ON Students.id = Scores.student_id; ``` ![](https://hackmd.io/_uploads/S1yiB3iph.png) LEFT OUTER JOIN A LEFT OUTER JOIN returns all the rows from the left table, and the matching rows from the right table. If there's no match, NULL values are returned for columns from the right table. ``` SELECT Students.name, Scores.score FROM Students LEFT OUTER JOIN Scores ON Students.id = Scores.student_id; ``` ![](https://hackmd.io/_uploads/Skjsr2i6h.png) ### Polymorphic associations ``` rails generate model Post title:string rails generate model Video title:string rails generate model Comment body:text commentable:references{polymorphic} ``` ``` class Comment < ApplicationRecord belongs_to :commentable, polymorphic: true end create_table :comments do |t| t.text :body t.integer :commentable_id t.string :commentable_type t.timestamps end class Post < ApplicationRecord has_many :comments, as: :commentable end class Video < ApplicationRecord has_many :comments, as: :commentable end # Creating a comment for a post post = Post.create(title: "Hello World") comment1 = Comment.create(body: "A comment on a post.", commentable: post) # Creating a comment for a video video = Video.create(title: "My Video") comment2 = Comment.create(body: "A comment on a video.", commentable: video) # Fetch comments of a post post_comments = post.comments # Fetch comments of a video video_comments = video.comments # Find the parent model (Post or Video) for a comment parent_for_comment1 = comment1.commentable # This will return the related Post object parent_for_comment2 = comment2.commentable # This will return the related Video object ``` #### Benefits of Polymorphic Associations: DRY code: Without polymorphic associations, you would end up with separate associations and tables for each PostComment, VideoComment, etc. Polymorphic associations eliminate that redundancy. Flexibility: You can easily add more models that can be commented on without having to change the Comment model or table. Query Efficiency: It allows you to retrieve the comments for multiple types of commentable objects in one query, making it easier to manage and optimize queries. ### Single Table Inheritance (STI) It is a feature in Ruby on Rails that allows a single database table to be used to represent multiple classes in an inheritance hierarchy. This is useful when you have models that share common attributes and behavior but also have their own specific attributes and behavior. ##### Rails utilizes a type column in the database table to keep track of the class for a given row. ``` rails generate migration CreateVehicles make:string model:string type:string payload_capacity:integer # app/models/vehicle.rb class Vehicle < ApplicationRecord # common behavior for all vehicles end # app/models/car.rb class Car < Vehicle # behavior specific to cars end # app/models/truck.rb class Truck < Vehicle # behavior specific to trucks end Car.create(make: 'Toyota', model: 'Camry') Truck.create(make: 'Ford', model: 'F-150', payload_capacity: 5000) vehicles = Vehicle.all # This will give you an array of Vehicle, Car, and Truck instances based on the `type` column cars = Car.all # This will only give you the rows where `type` is 'Car' trucks = Truck.all # This will only give you the rows where `type` is 'Truck' class User < ActiveRecord::Base self.inheritance_column = :entity_type # can be string as well end class Admin < User; end ``` By default STI model class name is stored in a column named type. But its name can be changed by overriding inheritance_column value in a base class. #### Advantages and Disadvantages ##### Advantages: 1. DRY: Code is not duplicated across multiple tables. 2. Easy Queries: It's easy to fetch all records of a certain type or parent type. 3. Intuitive Modeling: A natural fit for certain kinds of domain models. ##### Disadvantages: 1. Complex Queries: As more subclasses are added, it can become increasingly complex to query for specific attributes. 2. Table Bloat: The single table will contain columns for all attributes of all classes in the hierarchy, which may lead to many NULL values. 3. Type Coupling: Because the type field is string-based, renaming classes can lead to issues unless handled carefully. Having type column in a Rails model without invoking STI can be achieved by assigning `:_type_disabled` to inheritance_column: ``` class User < ActiveRecord::Base self.inheritance_column = :_type_disabled end ``` ### Monkey Patching Monkey Patching is a way of modifying and extending classes in Ruby. Basically, you can modify already defined classes in Ruby, adding new methods and even modifying previously defined methods. #### Changing an existing ruby method ``` puts "Hello readers".reverse # => "sredaer olleH" class String def reverse "Hell riders" end end puts "Hello readers".reverse # => "Hell riders" ``` ### Mixin In Ruby, a mixin is a way to share code among multiple classes. This is done through modules, which can contain methods that get mixed into classes using the include or extend keywords. Mixins are a form of composition and offer an alternative to inheritance for code reuse. Instance Method Mixin Let's consider a simple example where we have a Document and a Person class, both of which need to output a summary. ``` module Summarizable def summary "#{self.class.name} Summary: #{self.to_s}" end end class Document attr_accessor :text include Summarizable def initialize(text) @text = text end def to_s @text end end class Person attr_accessor :name, :age include Summarizable def initialize(name, age) @name = name @age = age end def to_s "#{@name}, #{@age} years old" end end doc = Document.new("This is a sample document.") puts doc.summary # Output: "Document Summary: This is a sample document." person = Person.new("Alice", 30) puts person.summary # Output: "Person Summary: Alice, 30 years old" ``` ``` module Foo def foo_method puts 'foo_method called!' end end module Bar def bar_method puts 'bar_method called!' end end class Baz include Foo include Bar def baz_method puts 'baz_method called!' end end new_baz = Baz.new new_baz.baz_method #=> 'baz_method called!' new_baz.bar_method #=> 'bar_method called!' new_baz.foo_method #=> 'foo_method called!' ``` ``` module SomeMixin def foo puts "foo!" end end class Bar extend SomeMixin def baz puts "baz!" end end b = Bar.new b.baz # => "baz!" b.foo # NoMethodError, as the method was NOT added to the instance Bar.foo # => "foo!" # works only on the class itself ``` ### Self-Referential Association Self-referential association is used to associate a model with itself. The most frequent example would be, to manage association between a friend and his follower. ``` rails g model friendship user_id:references friend_id:integer # db/migrate/xxxxxx_create_friendships.rb class CreateFriendships < ActiveRecord::Migration[6.0] def change create_table :friendships do |t| t.references :user, null: false, foreign_key: true t.references :friend, null: false, foreign_key: { to_table: :users } t.timestamps end add_index :friendships, [:user_id, :friend_id], unique: true end end now you can associate models like; class User < ActiveRecord::Base has_many :friendships has_many :friends, :through => :friendships has_many :inverse_friendships, :class_name => "Friendship", :foreign_key => "friend_id" has_many :inverse_friends, :through => :inverse_friendships, :source => :user end and the other model will look like; class Friendship < ActiveRecord::Base belongs_to :user belongs_to :friend, :class_name => "User" end # Create users user1 = User.create(name: 'Alice') user2 = User.create(name: 'Bob') # Create friendship user1.friendships.create(friend: user2) # List friends user1.friends # => [user2] user2.inverse_friends # => [user1] ``` #### The has_many :through association ``` class Physician < ApplicationRecord has_many :appointments has_many :patients, through: :appointments end class Appointment < ApplicationRecord belongs_to :physician belongs_to :patient end class Patient < ApplicationRecord has_many :appointments has_many :physicians, through: :appointments end ``` #### The has_one :through association A has_one :through association sets up a one-to-one connection with another model. This association indicates that the declaring model can be matched with one instance of another model by proceeding through a third model. ``` class Supplier < ApplicationRecord has_one :account has_one :account_history, through: :account end class Account < ApplicationRecord belongs_to :supplier has_one :account_history end class AccountHistory < ApplicationRecord belongs_to :account end ``` #### Skipping Validations decrement! decrement_counter increment! increment_counter toggle! touch update_all update_attribute update_column update_columns update_counters #### User.save(validate: false) ### Scopes Scopes act as predefined filters on ActiveRecord models. A scope is defined using the scope class method. ``` class Person < ActiveRecord::Base #attribute :first_name, :string #attribute :last_name, :string #attribute :age, :integer # define a scope to get all people under 17 scope :minors, -> { where(age: 0..17) } # define a scope to search a person by last name scope :with_last_name, ->(name) { where(last_name: name) } end ``` Scopes can be called directly off the model class: ``` minors = Person.minors ``` Scopes can be chained: `peters_children = Person.minors.with_last_name('Peters')` User.first ``` SELECT `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1 ``` #### Includes ActiveRecord with includes ensures that all of the specified associations are loaded using the minimum possible number of queries. So when querying a table for data with an associated table, both tables are loaded into memory. ``` @authors = Author.includes(:books).where(books: { bestseller: true } ) # this will print results without additional db hitting @authors.each do |author| author.books.each do |book| puts book.title end end ``` Author.joins(:books).where(books: { bestseller: true } ) will load only authors with conditions into memory without loading books. Use joins when additional information about nested associations isn't required. ``` @authors = Author.joins(:books).where(books: { bestseller: true } ) # this will print results without additional queries @authors.each { |author| puts author.name } # this will print results with additional db queries @authors.each do |author| author.books.each do |book| puts book.title end end ``` #### Joins User.joins(:posts) will produce the following SQL query: `"SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id""` ``` User.joins(:posts).where(posts: { title: "Hello world" }) ``` Nested joins: ``` User.joins(posts: :images).where(images: { caption: 'First post' }) "SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id" INNER JOIN "images" ON "images"."post_id" = "images"."id"" ``` ##### Mind that find_by doesn't throw any exception by default. If the result is an empty set, it returns nil instead of find. ##### delete_all method. to be called directly on a model, to delete all records in that table, or a collection. Beware though, as .delete_all does not instantiate any object hence does not provide any callback (before_* and after_destroy don't get triggered). #### case insensitive search ``` addresses = Address.arel_table Address.where(addresses[:address].matches("%street%")) ``` ### Docker Docker is a platform for building, shipping, and running applications in containers. A container is a lightweight, standalone, and executable package of software that includes everything needed to run an application, including code, runtime, libraries, system tools, and settings. Docker makes it easy to package an application and all its dependencies into a single container, which can then be deployed to any environment that supports Docker. ``` # Use an official Ruby runtime as a parent image FROM ruby:3.0.3 # Set the working directory WORKDIR /app # Install dependencies RUN apt-get update && \ apt-get install -y \ build-essential \ nodejs \ postgresql-client && \ rm -rf /var/lib/apt/lists/* # Install gems COPY Gemfile Gemfile.lock ./ RUN gem install bundler && \ bundle install --jobs 4 # Copy the application code COPY . . # Expose ports EXPOSE 3000 # Set the entrypoint command CMD ["rails", "server", "-b", "0.0.0.0"] ``` ``` version: "3" services: web: build: . command: bundle exec rails s -p 3000 -b '0.0.0.0' ports: - "3000:3000" depends_on: - db - redis db: image: postgres:12 environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: myapp_development redis: image: redis:6.0 ``` ##### docker-compose up --build ``` version: "3" services: web: image: your-dockerhub-username/myapp:latest environment: RAILS_ENV: production DATABASE_URL: "postgres://postgres:password@db/myapp_production" REDIS_URL: "redis://redis:6379/0" ports: - "80:3000" depends_on: - db - redis restart: always db: image: postgres:12 environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: password POSTGRES_DB: myapp_production volumes: - db_data:/var/lib/postgresql/data restart: always redis: image: redis:6.0 restart: always volumes: db_data: ``` #### Nginx Configuration ``` upstream myapp { server localhost:3000; } server { listen 80; server_name example.com; location / { proxy_pass http://myapp; proxy_set_header Host $host proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } ``` #### Github CI/CD ``` # This is a basic workflow to help you get started with Actions name: CD # Controls when the workflow will run on: # Triggers the workflow on push or pull request events but only for the production branch push: branches: [ uat ] # pull_request: # branches: [ main ] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: # This workflow contains a single job called "build" deploy: # The type of runner that the job will run on runs-on: ubuntu-latest # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - uses: appleboy/ssh-action@master with: host: ${{secrets.UAT_SSH_HOST}} key: ${{secrets.UAT_SSH_KEY}} #private key username: ${{secrets.UAT_SSH_USERNAME}} script: | cd /var/www/html/app docker-compose stop git pull docker-compose up -d ls -al ``` #### N+1 Query One of the frequent performance antipatterns in ORMs is the N+1 query problem. It occurs when an application retrieves data from the database and then loops through the results of that data, or when a query is run on each result of the prior query. ``` # app/models/Company.rb class Company < ApplicationRecord belongs_to :user end # app/models/user.rb class User < ApplicationRecord has_many :companies end Company.all.each do |Company| puts "#{Company.title} was written by #{Company.user.username}" end The above code works, but it makes far too many independent database queries: Company Load (0.5ms) SELECT "companies".* FROM "companies" User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 5], ["LIMIT", 1]] User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 6], ["LIMIT", 1]] User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 9], ["LIMIT", 1]] User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 10], ["LIMIT", 1]] User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 15], ["LIMIT", 1]] ``` That is 7 separate database queries In contrast to lazy loading, eager loading occurs when a query loads a resource as soon as the code is executed. As part of the query, it also loads related entities. We need to reduce the number of independent database queries in order to improve the performance of the previous example. ``` Company.includes(:user).each do |Company| puts "#{Company.title} was written by #{Company.user.username}" end Company Load (103.7ms) SELECT "companies".* FROM "companies" User Load (32.1ms) SELECT "users".* FROM "users" WHERE "users"."id" IN (?, ?, ?, ?, ?) [["id", 1], ["id", 5], ["id", 6], ["id", 9], ["id", 10], ["id", 15]] ``` ### RubyGems RubyGems is a package manager for the Ruby programming language. It provides a standard format for distributing Ruby libraries (known as "gems") and a way to easily install, manage and update these gems in a Ruby application. A gem is a self-contained package of code, assets, and documentation that can be used in a Ruby application. Gems typically provide additional functionality that is not part of the core Ruby language, such as libraries for connecting to databases, handling HTTP requests, and so on. RubyGems makes it easy to find, install, and manage gems in a Ruby application. You simply specify the gems that your application depends on in a file called the Gemfile, and then use the bundle install command to download and install all the gems specified in the file. The bundle install command also resolves any dependencies between gems and ensures that the correct version of each gem is installed. ##### Generate the gem skeleton 1. Open your terminal and navigate to the directory where you want to create your gem 2. Run the following command to create the gem skeleton: $ bundle gem my_accounting_app 3. This will create a directory named my_accounting_app with the basic structure of a Ruby gem. ``` module MyAccountingApp def self.total_amount(amount, tax) amount + (amount * tax) end end ``` In the root directory of your gem, you'll find a file named my_accounting_app.gemspec. This is where you'll specify the metadata for your gem, such as its name, version, author, and description. ``` # my_accounting_app.gemspec Gem::Specification.new do |spec| spec.name = "my_accounting_app" spec.version = "0.1.0" spec.authors = ["Your Name"] spec.email = ["your_email@example.com"] spec.summary = "A gem for accounting applications" spec.description = "A gem that provides methods for accounting applications" spec.files = Dir["lib/**/*.rb"] spec.require_paths = ["lib"] end ``` ##### Build the gem ``` gem build my_accounting_app.gemspec Create a new Rails application: $ rails new my_rails_app Add the following line to the Gemfile of your Rails application: gem "my_accounting_app", path: "/path/to/my_accounting_app" ``` ``` class InvoicesController < ApplicationController def show amount = params[:amount].to_f tax = params[:tax].to_f @total_amount = MyAccountingApp.total_amount(amount, tax) end end Rails.application.routes.draw do get "/invoices/show", to: "invoices#show" end ``` Run the following command to publish your gem: `gem push my_accounting_app-0.1.0.gem` ### Page caching You can use the ActionPack page_caching gem to cache individual pages This stores the result of one dynamic request as a static HTML file, which is served in place of the dynamic request on subsequent requests ``` class UsersController < ActionController::Base caches_page :index end Use expire_page to force expiration of the cache by deleting the stored HTML file: class UsersController < ActionController::Base caches_page :index def index @users = User.all end def create expire_page :action => :index end end The syntax of expire_page mimics that of url_for and friends. ``` ### Fragment caching Use cache.write to write a value to the cache: ``` Rails.cache.write('city', 'Duckburgh') Rails.cache.read('city') => 'Duckburgh' ``` ``` Rails.cache.fetch('user') do User.where(:is_awesome => true) end ``` #### Nested Forms ``` <%= nested_form_for @project do |f| %> <%= f.label :name %> <%= f.text_field :name %> <% # Now comes the part for `Todo` object %> <%= f.fields_for :todo do |todo_field| %> <%= todo_field.label :name %> <%= todo_field.text_field :name %> <% end %> <% end %> ``` #### CSV Parsing ``` class Product< ApplicationRecord def self.import(file) CSV.foreach(file.path, headers: true) do |row| Product.create! row.to_hash end end end ``` #### Rails Best Practices ##### Fat Model, Skinny Controller “Fat Model, Skinny Controller” refers to how the M and C parts of MVC ideally work together. Namely, any non- response-related logic should go in the model, ideally in a nice, testable method. Meanwhile, the “skinny” controller is simply a nice interface between the view and model. ``` Let’s look at a simple example. Say you have code like this: def index @published_posts = Post.where('published_at <= ?', Time.now) @unpublished_posts = Post.where('published_at IS NULL OR published_at > ?', Time.now) end You can change it to this: def index @published_posts = Post.published @unpublished_posts = Post.unpublished end Then, you can move the logic to your post model, where it might look like this: scope :published, ->(timestamp = Time.now) { where('published_at <= ?', timestamp) } scope :unpublished, ->(timestamp = Time.now) { where('published_at IS NULL OR published_at > ?', timestamp) } ``` #### Split routes into multiple files ``` config/routes.rb: YourAppName::Application.routes.draw do require_relative 'routes/admin_routes' require_relative 'routes/sidekiq_routes' require_relative 'routes/api_routes' require_relative 'routes/your_app_routes' end config/routes/api_routes.rb: YourAppName::Application.routes.draw do namespace :api do # ... end end ``` #### Collection Vs Member Routes A member route will require an ID because it acts on a member of the resource. This means that it's used for actions that are performed on a specific instance of the resource. For example, ##### if you have a Users resource and you want to add a custom action that changes the status of a specific user, you could define a member route like this: ``` resources :users do member do get 'change_status' end end /users/1/change_status ``` A collection route doesn't require an ID because it acts on a collection of the resource. This means that it's used for actions that are performed on the resource as a whole and not on individual instances. For example, ##### if you want to add a custom action that displays all users who are online, you could define a collection route like this: ``` resources :users do collection do get 'online' end end /users/online ``` 10 + 9 + 8 + 7 + 6 + 5 + 4 + 3 + 2 + 1 = 55 ``` def add(n) if n == 1 return n end n + add(n - 1) end ``` ### Database View A database view is a virtual table based on the result-set of an SQL statement. It does not store the data itself but provides a way to represent the result of complex queries in a simplified manner. You can treat views like regular database tables in your queries. Example Let's say you have two tables: orders and products. You want to create a view that shows the total sales for each product. ``` rails generate migration CreateTotalSalesPerProductView # app/models/total_sales_per_product.rb class TotalSalesPerProduct < ApplicationRecord self.table_name = 'total_sales_per_product' self.primary_key = 'id' # Assuming the view has an 'id' field end class CreateTotalSalesPerProductView < ActiveRecord::Migration[6.0] def up execute <<-SQL CREATE VIEW total_sales_per_product AS SELECT products.id AS product_id, products.name AS product_name, SUM(orders.amount) AS total_sales FROM products INNER JOIN orders ON products.id = orders.product_id GROUP BY products.id; SQL end def down execute <<-SQL DROP VIEW IF EXISTS total_sales_per_product; SQL end end ``` ### Database Indexing in Rails Database indexing is a performance optimization technique that speeds up data retrieval operations. An index is a data structure that improves the speed of data retrieval operations on a database table at the cost of additional storage and decreased performance on data modification operations. ``` class AddIndexToUsersEmail < ActiveRecord::Migration[6.0] def change add_index :users, :email, unique: true end end ``` 1. Database Views: Useful for simplifying complex queries and calculations. They act like virtual tables. 2. Database Indexing: Useful for speeding up data retrieval operations. They create a data structure that allows for quicker lookups. ### Class Vs Module A class is a blueprint for creating objects and encapsulates methods that operate on an object's attributes. Classes support inheritance, allowing you to create a new class that is a subclass of an existing class. ##### Module A module is a collection of methods and constants that can be mixed into a class using include or extend. Modules cannot be instantiated, and they don't support inheritance. However, they are excellent for reusability and keeping classes clean and organized. ``` # app/models/concerns/authentication.rb module Authentication def authenticate(password) # Logic for authentication end end # app/models/user.rb class User < ApplicationRecord include Authentication def full_name "#{first_name} #{last_name}" end end user = User.new user.authenticate("some_password") ``` ``` ╔═══════════════╦═══════════════════════════╦═════════════════════════════════╗ ║ ║ class ║ module ║ ╠═══════════════╬═══════════════════════════╬═════════════════════════════════╣ ║ instantiation ║ can be instantiated ║ can *not* be instantiated ║ ╟───────────────╫───────────────────────────╫─────────────────────────────────╢ ║ usage ║ object creation ║ mixin facility. provide ║ ║ ║ ║ a namespace. ║ ╟───────────────╫───────────────────────────╫─────────────────────────────────╢ ║ superclass ║ module ║ object ║ ╟───────────────╫───────────────────────────╫─────────────────────────────────╢ ║ methods ║ class methods and ║ module methods and ║ ║ ║ instance methods ║ instance methods ║ ╟───────────────╫───────────────────────────╫─────────────────────────────────╢ ║ inheritance ║ inherits behaviour and can║ No inheritance ║ ║ ║ be base for inheritance ║ ║ ╟───────────────╫───────────────────────────╫─────────────────────────────────╢ ║ inclusion ║ cannot be included ║ can be included in classes and ║ ║ ║ ║ modules by using the include ║ ║ ║ ║ command (includes all ║ ║ ║ ║ instance methods as instance ║ ║ ║ ║ methods in a class/module) ║ ╟───────────────╫───────────────────────────╫─────────────────────────────────╢ ║ extension ║ can not extend with ║ module can extend instance by ║ ║ ║ extend command ║ using extend command (extends ║ ║ ║ (only with inheritance) ║ given instance with singleton ║ ║ ║ ║ methods from module) ║ ╚═══════════════╩═══════════════════════════╩═════════════════════════════════╝ ``` #### String A string is a mutable object that can be changed after it has been created. Each string you create is a separate object, even if the text content is the same. ``` # Creating a string name = String.new("John") # Modifying a string name << " Doe" # Comparing two strings with the same content puts "John" == "John" # Output: true # Two different string objects with the same content puts "John".object_id == "John".object_id # Output: false ``` #### Symbol A symbol is an immutable object, meaning it cannot be changed once it has been created. All symbols with the same content refer to the same object, making them more memory-efficient for certain operations like hash keys. ``` # Creating a symbol name = :John # Symbols are immutable # name << " Doe" # This will raise an error # Comparing two symbols with the same content puts :John == :John # Output: true # Same object ID for the same content puts :John.object_id == :John.object_id # Output: true ``` ### Include The include method is used to mix in a module's methods as instance methods in the target class. This allows you to share behavior across multiple classes. ``` module Greetable def greet "Hello, I am #{self.class.name}" end end class Person include Greetable end p = Person.new puts p.greet # Output: "Hello, I am Person" ``` ### Extend The extend method is used to add a module's methods as class methods to the target class or object. ``` module ClassMethods def categories ["Food", "Drink"] end end class Product extend ClassMethods end puts Product.categories # Output: ["Food", "Drink"] ``` ### Prepend The prepend method is used to insert a module's methods into the ancestors chain before the methods of the class itself. This allows you to override methods and call super to get the original functionality. ``` module Greetable def greet "Hello, I am #{self.class.name}" end end module OverrideGreet def greet "Hi, " + super end end class Person include Greetable prepend OverrideGreet end p = Person.new puts p.greet # Output: "Hi, Hello, I am Person" ``` ### Require The require method is used to load a Ruby file and execute all its statements. Once a file has been required, it won't be loaded again if require is called with the same path. ``` # In lib/my_module.rb module MyModule def hello "Hello" end end # In another file require 'lib/my_module' class MyClass include MyModule end puts MyClass.new.hello # Output: "Hello" ``` ### Load The load method is similar to require, but it re-executes the file every time it is called, which can be useful for reloading code without restarting the application. ``` # In lib/my_module.rb module MyModule def hello "Hello" end end # In another file load 'lib/my_module.rb' class MyClass include MyModule end puts MyClass.new.hello # Output: "Hello" ``` #### Summary **include**: Adds instance methods from a module to a class. **extend**: Adds class methods from a module to a class or object. **prepend**: Inserts a module's methods before the class's own methods, allowing for overrides. **require**: Loads and executes a Ruby file once. **load**: Loads and executes a Ruby file every time it's called. ### Scope Variables #### Local Variable Local variables are the most straightforward type of variable. They are available only within the method, loop, or block where they are defined. ``` def show message = "Hello, World!" # Local variable puts message end ``` #### Instance Variables Instance variables are available throughout the current instance of the class. They are prefixed with an @ symbol. ``` # app/controllers/articles_controller.rb class ArticlesController < ApplicationController def show @article = Article.find(params[:id]) # Instance variable end end ``` #### Class Variables Class variables are shared among a class and all its descendants. They are prefixed with @@. ``` class Product @@count = 0 # Class variable def initialize @@count += 1 end def self.count @@count end end ``` #### Global Variables Global variables are accessible throughout your entire application. They are prefixed with a $ symbol. However, their use is generally discouraged in favor of other variable types or constants. ``` $global_variable = "I'm available everywhere" # Global variable ``` #### Constants Constants in Ruby are used to store values that should not be changed once they are assigned. They are written in all uppercase. ``` # app/models/article.rb class Article < ApplicationRecord STATUS = ['Draft', 'Published'].freeze # Constant end ``` #### Summary 1. Local Variables: Limited to the method, loop, or block where they are defined. 2. Instance Variables: Available throughout the current instance of the class. 3. Class Variables: Shared among a class and all its descendants. 4. Global Variables: Accessible throughout the entire application. 5. Constants: Immutable variables, usually used for storing settings or other unchanging values. ### Visiblity Scopes 1. Public By default, all methods are public except for initialize, which is always private. Public methods can be called by any object that can access the class or instance. ``` class MyClass def public_method "I'm public!" end end obj = MyClass.new puts obj.public_method # Output: "I'm public!" ``` 2. Protected Protected methods can be called only within the same class or its subclasses, and they can also be called by other instances of the same class. ``` class MyClass protected def protected_method "I'm protected!" end end class MyDerivedClass < MyClass def call_protected_method_on(obj) puts obj.protected_method end end obj1 = MyDerivedClass.new obj2 = MyDerivedClass.new obj1.call_protected_method_on(obj2) # Output: "I'm protected!" ``` 3. Private Private methods can only be called within the context of the current object (self). They cannot be accessed by other instances of the same class. ``` class MyClass private def private_method "I'm private!" end public def call_private_method puts private_method end end obj = MyClass.new obj.call_private_method # Output: "I'm private!" ``` ### Design Patterns 1. Service Objects 2. View Objects (Presenter) 3. Query Objects 4. Decorators 5. Form Objects 6. Value Objects 7. Policy Objects 8. Builder 9. Interactor 10. Observer #### Service Objects A Service Object is PORO – Plain Old Ruby Object, which is meant to encapsulate business logic and complex calculations into manageable classes and methods. When we need to perform complex calculations or business logic, e.g., if we need to calculate employees’ salaries based on their attendance. When we need to implement any other API, we need to implement a payment gateway such as Stripe. When we want to import CSV that contains the bulk of data. 4. When we need to clear garbage/unused/old data from the database efficiently, it won’t affect the existing data. ``` class PaymentsController < ApplicationController def create PaymentService.new(params).call redirect_to payments_path rescue Stripe: : CardError => e flash[:error] = e.message redirect_to new_Payment_path end end ``` ``` class Payment Service def initialize(options = {}) options.each pair do Jkey, value instance_variable_set("@#{key}", value) end end def call Stripe: : Charge.create(charge_attributes), end private attr_reader : email, :source, :amount, :description def amount @amount. to_i * 100 end def customer @customer ||= Stripe::Customer.create(customer_attributes), def customer_attributes { email: email, source: source } end def charge_attributes { customer: customer.id, amount: amount, description: description } end end ``` #### View Objects (Presenter) View Objects allow us to encapsulate all view-related logic and keep both models and views neat. View objects are the kind of rails patterns that are easy to test as they are just classes. To solve the calculation logic problem, we can use rails helper, but if the complexity of the code is high, in that case, we should use the Presenter. ``` <p> User Full Name: <%= "#{user.first_name} #{user. last_name}" %> <%= link_to "View More Detail", user, class: "W-75 p-3 text-#{user.active? ? "orange" : " green"} border-#{user.active? ? "orange" : "green"}" %> </p> ``` **Solution** ``` app/presenters/user_presenter.rb class UserPresenter def initialize(user) @user = user end def full_name "#{@user.first_name} #{@user. last_name}", end def css_color @user.active?? "orange" : "green", end end <% presenter = UserPresenter.new(user) %> User Full Name: <%= presenter.full_name %> <%= link_to "View More Detail", user, class: "W-75 p-3 text-#{presenter.css_color} border-#{presenter.css_color}" %> ``` #### Query Object Query Object is a design pattern in rails that lets us fetch query logic from Controllers and Models into reusable classes. ``` class PostsController < ApplicationController def index @posts = Post.accessible_by(current_ability) .where(type: :video) .where('view_count > ?', 100), end end ``` Solution To make the controller skinny, readable, and neat, we can use scopes: ``` class Post < ActiveRecord::Base, scope : video_type, ->{ where(type: :video) } scope : popular, -> { where('view_count > ?', 1000) } scope : popular_video_type, -> { popular.video_type } end ``` ``` class Articles Controller < ApplicationController def index @posts = Post.accessible_by(current_ability), .popular_video_type end end ``` ``` class VideoQuery def call(ability) ability .where(type: : video) .where('view_count > ?', 1000) end end class PostsController < ApplicationController def index ability = Post.accessible_by(current_ability), @articles = VideoQuery.new.call(ability) end end ``` #### Decorators The decorator is a design pattern that allows the behavior to be added to an object, dynamically, without disturbing the behavior of other objects of the same class. Decorators can be useful for cleaning up logic/code written inside the view and controller in an RoR application. Create an app/decorator folder. Add decorate helper in ApplicationHelper. ``` module ApplicationHelper def decorate(model_name, decorator_class = nil), (decorator_class || "#{model_name. class}Decorator".constantize).new(model_name), end end ``` Add base_decorator.rb in app/decorators folder. ``` class BaseDecorator < SimpleDelegaton def decorate (model_name, decorator_class = nil), ApplicationController.helpers.decorate(model_name, decorator_class), end end ``` Add user_decorator.rb in app/decorators folder. ``` class UserDecorator < BaseDecorator def full name "#{fname} #{lname}" end end ``` Initialize @user_decorator in your user_controller. ``` class users Controller < Application Controller def show @user_decorator = helpers. decorate(current_user), end end <%= @user_decorator.full_name %> <%= @user_decorator.email %> ``` #### Form Objects The form object is a design pattern that is used to encapsulate the code related to validation and persisting data into a single unit. Let’s have a look at the example of the Form Objects. Let’s assume that we have a rails post model and a controller (posts_controller) action for creating the new post. Let’s discuss the problem, Here Post Model contains all validation logic, so it’s not reusable for other entities, e.g., Admin. app/controller/posts_controller.rb ``` class PostsController < ApplicationController def create @post = Post.new(post_params) if @post. save render json: @post else render json: @post.error, status: :unprocessable_entity end end private def post_params params. require(: post).permit(:title, :description, :content) end end app/model/post.rb class Post < ActiveRecord: :Base validates :title, presence: true validates : content, presence: true end ``` The better solution is to move the validation logic to a separate singular responsibility class that we might call PostForm: ``` class Post Form include ActiveModel: :Model include Virtus.model attribute :id, Integer attribute :title, String attribute :description, String attribute : content, String attr_reader : record def persist @record = id ? Post. find(id) : Post.new if valid? @record. save! true else false end end end ``` ``` class PostsController < ApplicationController def create @form = Post Form.new(post_params) if @form. persist render json: @form. record else render json: @form.errors, status: :unpocessably_entity end end private def post_params params. require(: post).permit(:title, :description, :content) end end ``` #### Value Object The Value object is a type of Ruby pattern that encourages small, simple objects and allows you to compare these objects as per the given logic or specific attributes. It represents value but not something unique in your system like a user object. Value Objects always return only values ``` class EmailReport def initialize(emails) @emails = emails end def data emails_data = [ ] emails.each do email| emails_data << { username: email.match(/([^@]*)/).to_s, domain: email.split("@"). last } end emails_data end private attr_reader : emails end ``` Now, let’s create the value object: ``` class Email def initialize(email) @email = email end def username email.match(/([^@]*)/).to_s end def domain email.split("a"). last end def to_h { username: username, domain: domain } end private attr_reader : email end ``` Calling Email values ``` class EmailReport def initialize(emails: emails), @emails = emails end def data emails.map { email| Email.new(email).to_h } end private attr_reader : emails end ``` #### Policy Object The policy object is similar to the Service object; the only difference is policy object is responsible for the read operations, and the service object is responsible for write operations. In rails, we use cancan or pundit gem for the authorization, but these gems are suitable if the application complexity is medium, If the application complexity is high (in terms of authorization), then, in that case, we use policy object for better efficiency. It returns a boolean value (true or false). ``` class UserService def initialize(user) @user = user end def name user_policy.take_email_as_name?? user.email : user.full_name end def account_name user_policy.is_admin? ? "Administrator": "User", end private attr_reader : user def user_policy @_user_policy II = UserPolicy.new(user), end end ``` Let’s create a policy (app/policies/user_policy.rb): ``` class UserPolicy def initialize(user) @user = user end def is_admin? user_role_is_admin? end def take_email_as_name? user_full_name_is_not_present? && is_user_email_present? end private attr_reader : user def user_full_name_is_not_present? user.full_name.blank? end def is_user_email_present? user.email.present? end def user_role_is_admin? user.sign_in_count > 0 && user.role == "admin" end end ``` #### Observer In this pattern, other interested objects are notified whenever an event has occurred. The observed object contains its observers’ list and notifies them by sending an update whenever its state changes When you want to make several views changes manually When an object’s state is dependent on a specific state When several views are dependent on a particular object’s state ``` require 'observer' class Product include Observable attr_reader :productName, :availableProducts def initialize(productName = "", availableProducts = 0) @productName, @availableProducts = productName, availableProducts add_observer(Notifier.new) end def update_availableProducts(product) @product = product changed notify_observers(self, product) end end ``` Now, we will build a class which would be notified with the updates on available products. ``` class Notifier def update(product, availableProducts) puts "Yes, #{product.name} is available" if availableProducts > 0 puts "Oops! Sorry, #{product.name} is unavailable!" if availableProducts == 0 end end ``` ``` isProductAvailable = Product.new("Iphone 12") isProductAvailable.update_availableProducts(5) # => Yes, Iphone 12 is available isProductAvailable.update_availableProducts(8) # => Yes, Iphone 12 is available isProductAvailable.update_availableProducts(0) # => Oops! Sorry, Iphone 12 is unavailable ``` --- ### Reverse Array ``` a = [1, 2, 3, 4, 5, 6] left = 0 right = a.length - 1 while left < right # Swap the elements at the 'left' and 'right' indices a[left], a[right] = a[right], a[left] # Move the pointers towards the center left += 1 right -= 1 end puts a # Output will be [6, 5, 4, 3, 2, 1] ``` --- ### Query Optimisation technique Query optimization is essential for the performance of any database-driven application, including those built on Ruby on Rails with Active Record. Here are some techniques and best practices for optimizing your queries: 1. **Select Only What You Need**: - Instead of fetching all columns with `Model.all`, use `select` to fetch only the columns you need. ```ruby User.select(:id, :name) ``` 2. **Use Indexes**: - Index frequently queried columns to speed up search operations. However, avoid over-indexing as it can slow down write operations. 3. **Eager Loading with `includes` or `eager_load`**: - Helps to reduce the "N+1 query problem". ```ruby User.includes(:posts).where("posts.published = ?", true) ``` 4. **Use `exists?` for Presence**: - Instead of `Model.any?` or `Model.count > 0`, use `Model.exists?` to check for a record's presence. 5. **Use Database Constraints**: - Utilize `UNIQUE`, `FOREIGN KEY`, and other constraints in the database to ensure data integrity without additional application-level checks. 6. **Limit and Offset**: - Use `limit` and `offset` to paginate results and avoid fetching more records than necessary. 7. **Caching**: - Use tools like Redis or Memcached to cache frequently accessed queries. 8. **Batch Processing**: - Use methods like `find_each` or `find_in_batches` for processing large sets of data. ```ruby User.find_each(batch_size: 100) do |user| # process each user end ``` 9. **Avoid Ruby Iteration Over Large Data Sets**: - Instead of loading a large number of records and processing them in Ruby, try to handle as much data processing as possible in the database. 10. **Use SQL Views or Materialized Views**: - For complex queries that join several tables, consider using database views or materialized views. 11. **Be Mindful of Joins**: - Limit the number of `joins` in a query, as they can substantially slow down performance. Consider using `subqueries` when applicable. 12. **Optimize Database Configuration**: - Tools like the `pgtune` for PostgreSQL can help optimize the database configuration. 13. **Review Logs**: - Regularly check your development logs to spot inefficient queries. 14. **Database Tools and Extensions**: - Use tools like `EXPLAIN` (in SQL databases) to understand how your query is being executed and optimize accordingly. 15. **Keep Application and Database Updated**: - New versions often come with optimizations and improvements. Ensure you're using a well-maintained version of both your database and Rails. 16. **Use Counter Cache**: - Instead of running a `COUNT` query every time you need to know the number of associated records, use Rails' `counter_cache` feature. 17. **Avoid `default_scope`**: - `default_scope` in Rails can lead to unexpected behaviors and negatively impact performance. Use scopes with caution. 18. **De-normalization**: - In situations where read performance is crucial, consider de-normalizing the database to reduce the need for complex joins or calculations. Remember, before making any optimizations, it's essential to identify the bottlenecks. Tools like `rack-mini-profiler`, `Bullet`, and database-specific profilers can be invaluable in this regard. ### SOLID #### Single Responsibility Principle (SRP) The Single Responsibility Principle dictates that a class should have only one reason to change, meaning it should have only one job or responsibility. ```ruby # Good: Single responsibility of sending email class EmailSender def send_email(email, content) # logic to send email end end # Bad: Multiple responsibilities class UserManager def create_user(params) # logic to create user end def send_email(email, content) # logic to send email end end ``` #### Open/Closed Principle (OCP) According to the Open/Closed Principle, software entities (classes, modules, methods, etc.) should be open for extension but closed for modification. ```ruby # Base class class ReportGenerator def generate # common logic end end # Extended classes class PDFReport < ReportGenerator; end class XMLReport < ReportGenerator; end ``` #### Liskov Substitution Principle (LSP) The Liskov Substitution Principle states that objects of a superclass should be able to be replaced with objects of a subclass without affecting the correctness of the program. ```ruby class Bird def fly 'I can fly' end end class Penguin < Bird def fly 'I cannot fly' end end ``` Here, a Penguin is-a Bird, but it violates LSP because it cannot fly, unlike other birds #### Interface Segregation Principle (ISP) The Interface Segregation Principle suggests that a class should not be forced to implement interfaces it does not use. In other words, don't add additional responsibilities to an existing class or module. ```ruby # Good class OrderProcessor def process_order; end end class PaymentProcessor def process_payment; end end # Bad class OrderAndPaymentProcessor def process_order; end def process_payment; end end ``` #### Dependency Inversion Principle (DIP) The Dependency Inversion Principle states that high-level modules should not depend on low-level modules; both should depend on abstractions. Also, abstractions should not depend on details; details should depend on abstractions. ```ruby # Good class OrderProcessor def initialize(payment_gateway) @payment_gateway = payment_gateway end end # Bad class OrderProcessor def initialize @payment_gateway = CreditCardPaymentGateway.new end end ``` --- --- ### Transactions Transactions are a feature of databases that allow you to execute a series of Active Record operations atomically, meaning that either all the operations succeed, or none of them do. Transactions ensure data integrity and consistency even in situations where multiple operations need to occur in a sequence. ```ruby User.transaction do user = User.new(username: 'John') user.save! profile = Profile.new(user: user, bio: 'Hello, world!') profile.save! end User.transaction do user = User.create(username: 'John') Profile.transaction do profile = Profile.create(user: user, bio: 'Nested transaction') raise ActiveRecord::Rollback if some_condition_not_met end end User.transaction do user.save! raise ActiveRecord::Rollback if some_condition_not_met end ``` #### Why Use Transactions? **Atomicity**: Ensures that a series of operations are atomic. **Consistency**: Helps maintain database consistency. **Isolation**: In a multi-user environment, ensures that operations from one transaction are isolated from another until committed. **Durability**: Once committed, the changes are permanent. ### Eager Loading One way to improve performance is to cut down on the number of SQL queries. You can do this through eager loading. `User.find(:all, :include => :friends)` Here you are firing only two queries : 1) One for all users. 2) One for all friends of users . ### Lazy Loading : When you have an object associated with many objects like a User has many Friends and you want to display a list as in Orkut you fire as many queries as there are friends, plus one for the object itself. ```ruby users = User.find(:all) Then query for each user friend , like : users.each do |user| friend = Friend.find_by_user_id(user.id) end ``` 1) One query for all users. 2) N query for N no. of users friends . ### CSRF (Cross-Site Request Forgery) The CSRF (Cross-Site Request Forgery) mechanism in Ruby on Rails is designed to prevent unauthorized commands from being transmitted from a user that the web application trusts. Here's a brief overview of how Rails handles and validates the CSRF token to ensure this protection: 1. Token Generation: When you use form_for or other Rails form helpers, a hidden input field named authenticity_token is included in the generated form. This token is generated by the server. The same token is also stored in the session on the server side. 2. Sending the Token: For traditional form-based requests, the token is included as a hidden form field and thus sent as part of the form data. For AJAX requests, the token can be added as an HTTP header called X-CSRF-Token. 3. Validation: When the form is submitted or an AJAX request is made, Rails will expect the CSRF token to be present either in the form data or in the HTTP header. The server-side Rails framework retrieves the token from the session and compares it with the token sent with the request. If the tokens don't match or if the token is missing from the request, Rails will raise an `ActionController::InvalidAuthenticityToken` exception, thereby preventing the request from succeeding. 4. Configuring the CSRF Protection: In a Rails application, CSRF protection is enabled by default. You'll find the following line in the ApplicationController: `protect_from_forgery with: :exception` This means if a CSRF token is invalid or missing, an exception will be raised. However, you can also use with: :null_session if you want to reset the session instead of raising an exception. **Use Cases**: If for some reason you need to disable CSRF protection for specific actions or controllers (not typically recommended for web-facing routes due to security concerns), you can use skip_before_action :verify_authenticity_token. Remember, CSRF attacks rely on the fact that browsers send cookies with every request, even if the request originates from a different site. By requiring a CSRF token, which is not automatically sent by browsers, Rails ensures that the request is made intentionally by the user and from the legitimate site. ### Find the overlapping sets and merge together in ruby on rails ```ruby def merge_intervals(intervals) return [] if intervals.empty? # Sort intervals by start time intervals.sort_by! { |interval| interval[0] } merged = [intervals[0]] intervals[1..-1].each do |current_interval| last_merged = merged[-1] # If the current interval overlaps with the last merged interval if current_interval[0] <= last_merged[1] # Merge them last_merged[1] = [last_merged[1], current_interval[1]].max else # Otherwise, just add the current interval to the merged list merged << current_interval end end merged end input = [[80,120], [100, 160], [180, 240], [280,320], [300, 360], [340, 400]] output = merge_intervals(input) puts output.inspect # This should output: [[80,160], [180, 240], [280,400]] ```