breaking down Ruby on Rails parts and demistifying it
- Bundler
- Rake
- Rack
- ActiveSupport
- hash_with_indifferent_access gotchas
- ActiveRecord
- ActiveModel
- ActiveMailer - running as standalone mailer
- ActiveJob
- others are too intertwined dependencies on the rest of rails to run individually - these are better run inside rails:
- ActionCable,
- ActionVeiew,
- ActionController
- DSLs in rails/ruby - RSpec, Routes
- Routes: sinatra/hanami/Roda better for
- templates: ERB
- dissecting-rails book/site
bundle exec [command]
bundle init # create Gemfile
bundle add [gemname] # it adds versions, so remove them if required
bundle remove [gemname]
bundle install
bundle show # show all gems in Gemfile
bundle show [gemname] # show path
bundle info [gemname]
bundle open [gemname] # open gem in editor
bundle console # open irb with gems loaded
# [DEPRECATED] bundle console will be replaced by `bin/console` generated by `bundle gem <name>`
ri FileUtils
create a basic Rakefile
:
you need
pandoc
install, as it is used in the example
task default: :html
files = Rake::FileList.new('**/*.md') do |fl|
fl.exclude(/^excluded_dir/)
end
task html: files.ext('.html')
rule '.html' => '.md' do |t|
sh "pandoc -o #{t.name} #{t.source}"
end
Run it
rake
# or for debugging
rake --trace
# dump list of prerequisites
rake -P
# for quiet output
rake -q
you can add trace rules to your Rakefile to help with debugging:
Rake.application.options.trace_rules = true
Rake::FileList.new('**/*')
.pathmap("%f") # => ["README.md", "subdir", "ch1.md", "Rakefile"]
.pathmap("%p") # => ["README.md", "subdir", "subdir/ch1.md", "Rakefile"]
.pathmap("%n") # => ["README", "subdir", "ch1", "Rakefile"]
.pathmap("%d") # => [".", "subdir", "subdir"]
.pathmap("%x") # => ["md", "", "md", ""]
.pathmap("%X") # => ["md", "", "md", ""]
directory 'ship_it๐ข'
task list_files: 'ship_it๐ข' do # dependency dir
# ...
task :clean do
rm_rf 'ship_it๐ข'
end
refer Rakefile in rake_sandbox/2_parallel
dir
task single: files.ext('.html')
multitask parallel: files.ext('.html')
Minimalistic middleware / interfaces, that bridges between web servers, web frameworks, and web application into a single method call.
try building an app with Rack, you don't need a framework
# config.ru
run do |env|
[200, { "some_header" => "lalala"}, ["Hello World"]]
end
gem install rack
rackup # starts your default web server on 9292
# or specify alternative webserver, i.e. 'webrick'
rackup -s webrick
curl -I http://127.0.0.1:9292
cd active_support
bundle install
bundle exec ruby bin/run
with_option
try
hash_with_indifferent_access
presnece
in?
- to_json is better implemented than JSON gem
delegate
anddelegate_missing_to
cattr_accessor
descendants
andsubclasses
"".html_safe?
squish
- singularize, pluralize, camelize / underscore, titleize / dasherize, demodulize, deconstantize, parameterize, humanize, constantize
"Hello World".remove(/Hello /) # => "World"
"Oh dear! Oh dear! I shall be late!".truncate(18, separator: ' ', omission: "๐")) # => "Oh dear! Oh dear!๐"
"Oh dear! Oh dear! I shall be late!".truncate_words(4)
# => "Oh dear! Oh dear!..."
o"foo".starts_with?("f") # => true
"foo".ends_with?("o") # => true
<<HEREDOC.indent(2)
<<HEREDOC..strip_heredoc
'octopus'.pluralize
"equipment".singularize
"admin_user".camelize
"visual_effect".camelize(:lower) # => "visualEffect"
"visualEffect".underscore
"Admin::Hotel::ReservationUtils".demodulize # => "ReservationUtils"
"Admin::Hotel::ReservationUtils".deconstantize # => "Admin::Hotel"
"2010-07-27".to_date # => Tue, 27 Jul 2010
"2010-07-27 23:37:00".to_time # => 2010-07-27 23:37:00 +0200
"2010-07-27 23:37:00".to_datetime # => Tue, 27 Jul 2010 23:37:00 +0000
1_235_551_234.to_fs(:phone, country_code: 61, area_code: true, extension: 555)
1_234_567_890.506.to_fs(:currency) # => $1,234,567,890.51
302.24398923423.to_fs(:percentage, precision: 5)
1_234_567_890_123.to_fs(:human_size) # => 1.12 TB
1_234_567_890.to_fs(:human) # => "1.23 Billion"
12_345_678.05.to_fs(:delimited, separator: ' ') # => 12,345,678 05
1_235_551_234.to_fs(:phone, country_code: 61, area_code: true, extension: 555)
1_234_567_890.506.to_fs(:currency) # => $1,234,567,890.51
302.24398923423.to_fs(:percentage, precision: 5)
1_234_567_890_123.to_fs(:human_size) # => 1.12 TB
1_234_567_890.to_fs(:human) # => "1.23 Billion"
12_345_678.05.to_fs(:delimited, separator: ' ') # => 12,345,678 05
(Date.today..Date.tomorrow).to_fs(:db) # => "BETWEEN '2009-10-25' AND '2009-10-26'"
%w(Earth Wind Fire).to_sentence # => "Earth, Wind, and Fire"
- pluck
- pick
many?
index_by
- including / excluding
- exclude?
- extract
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
odd_numbers = numbers.extract! { |number| number.odd? } # => [1, 3, 5, 7, 9]
numbers # => [0, 2, 4, 6, 8]
- similar to String/Enum methods, like
to
,from
,third
,excluding
.in_groups_of
deep_merge
/merge
/deep_dup
except
symbolize_keys
/stringify_keys
{ a: 1, b: 1 }.merge(a: 0, c: 2)
{ a: 1, b: 2 }.except(:a) # => {:b=>2}
%r{.}m.multiline? # => true
ActiveRecord is essentially ActiveModel with persistence layer
require 'active_record'
ActiveRecord::Base.establish_connection(
adapter: 'sqlite3',
database: 'sample.sqlite3'
)
class CreateUsers < ActiveRecord::Migration[6.0]
def change
create_table :users do |t|
t.string :name
end
end
end
CreateUsers.new.migrate(:up)
class User < ActiveRecord::Base
validates_presence_of :name, on: :create
has_many :posts
end
- ActiveRecord
[look no further](https://www.mayerdan.com/ruby/2022/06/27/rails-query-tracing
module ActiveRecord
class LogSubscriber < ActiveSupport::LogSubscriber
def sql(event)
# NOTE: I add a global $ignore_query == false && if I need to say ignore all the factories or before/after spec specific queries to help
# only find callers in application code.
if /FROM "some_table" WHERE "some_condition"/.match?(event.payload[:sql])
Rails.logger.info "SQL FOUND #{caller_locations[15...150]}"
binding.irb if ENV["QUERY_BINDING"]
# or
# require 'awesome_print'
# ap caller if ENV["QUERY_BINDING"]
end
end
end
end
ActiveRecord::LogSubscriber.attach_to :active_record
include ActiveModel::Model # similar to `ActiveRecord::Base`
include ActiveModel::API # validation
include ActiveModel::Validations
extend ActiveModel::Translation # i18n gem integration
include ActiveModel::Conversion # .persisted?
extend ActiveModel::Callbacks # before_update :reset_me
include ActiveModel::AttributeMethods # define meta attributes
attribute_method_prefix 'reset_'
define_attribute_methods 'age'
def reset_attribute(attribute) = # your nobel prize logic
include ActiveModel::Dirty # model.changed?
include ActiveModel::Serialization
include ActiveModel::Serializers::JSON
include ActiveModel::SecurePassword
has_secure_password
attr_accessor :password_digest
require 'action_mailer'
require 'letter_opener'
ActionMailer::Base.add_delivery_method :letter_opener, LetterOpener::DeliveryMethod, location: File.expand_path('../tmp/letter_opener', __FILE__)
ActionMailer::Base.delivery_method = :letter_opener
class LalaMailer < ActionMailer::Base
def notify
mail(
to: 'ruby@australia.com',
from: 'friendlyantz@pm.me',
subject: 'Hello, World!'
) do |format|
format.text { 'This is my text message' }
format.html { '<h1>this is my html message</h1>' }
end
end
end
LalaMailer.notify.deliver_now