diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml new file mode 100644 index 0000000..f398d77 --- /dev/null +++ b/.github/workflows/deploy.yaml @@ -0,0 +1,29 @@ +name: Deploy to Github Pages + +on: + push: + branches: [ "master" ] + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Pages + uses: actions/configure-pages@v5 + - name: Build + uses: actions/jekyll-build-pages@v1 + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + deploy: + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..655b566 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +.jekyll-cache +.DS_Store +.idea + +node_modules + +_site + +assets/stylesheets/* +!assets/stylesheets/.keep diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..af2f836 --- /dev/null +++ b/Gemfile @@ -0,0 +1,8 @@ +source "https://rubygems.org" + +gem "jekyll", "~> 4.3.3" + +group :jekyll_plugins do + gem "jekyll-feed", "~> 0.12" + gem "jekyll-compose", "~> 0.12.0" +end diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..18ea042 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,109 @@ +GEM + remote: https://rubygems.org/ + specs: + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + bigdecimal (3.1.8) + colorator (1.1.0) + concurrent-ruby (1.3.4) + em-websocket (0.5.3) + eventmachine (>= 0.12.9) + http_parser.rb (~> 0) + eventmachine (1.2.7) + ffi (1.17.0) + ffi (1.17.0-arm64-darwin) + ffi (1.17.0-x86_64-darwin) + forwardable-extended (2.6.0) + google-protobuf (4.27.3) + bigdecimal + rake (>= 13) + google-protobuf (4.27.3-arm64-darwin) + bigdecimal + rake (>= 13) + google-protobuf (4.27.3-x86_64-darwin) + bigdecimal + rake (>= 13) + http_parser.rb (0.8.0) + i18n (1.14.5) + concurrent-ruby (~> 1.0) + jekyll (4.3.3) + addressable (~> 2.4) + colorator (~> 1.0) + em-websocket (~> 0.5) + i18n (~> 1.0) + jekyll-sass-converter (>= 2.0, < 4.0) + jekyll-watch (~> 2.0) + kramdown (~> 2.3, >= 2.3.1) + kramdown-parser-gfm (~> 1.0) + liquid (~> 4.0) + mercenary (>= 0.3.6, < 0.5) + pathutil (~> 0.9) + rouge (>= 3.0, < 5.0) + safe_yaml (~> 1.0) + terminal-table (>= 1.8, < 4.0) + webrick (~> 1.7) + jekyll-compose (0.12.0) + jekyll (>= 3.7, < 5.0) + jekyll-feed (0.17.0) + jekyll (>= 3.7, < 5.0) + jekyll-sass-converter (3.0.0) + sass-embedded (~> 1.54) + jekyll-watch (2.2.1) + listen (~> 3.0) + kramdown (2.4.0) + rexml + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + liquid (4.0.4) + listen (3.9.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + mercenary (0.4.0) + pathutil (0.16.2) + forwardable-extended (~> 2.6) + public_suffix (6.0.1) + rake (13.2.1) + rb-fsevent (0.11.2) + rb-inotify (0.11.1) + ffi (~> 1.0) + rexml (3.3.4) + strscan + rouge (4.3.0) + safe_yaml (1.0.5) + sass-embedded (1.77.8) + google-protobuf (~> 4.26) + rake (>= 13) + sass-embedded (1.77.8-aarch64-mingw-ucrt) + google-protobuf (~> 4.26) + sass-embedded (1.77.8-arm64-darwin) + google-protobuf (~> 4.26) + sass-embedded (1.77.8-x86-cygwin) + google-protobuf (~> 4.26) + sass-embedded (1.77.8-x86-mingw-ucrt) + google-protobuf (~> 4.26) + sass-embedded (1.77.8-x86_64-cygwin) + google-protobuf (~> 4.26) + sass-embedded (1.77.8-x86_64-darwin) + google-protobuf (~> 4.26) + strscan (3.1.0) + terminal-table (3.0.2) + unicode-display_width (>= 1.1.1, < 3) + unicode-display_width (2.5.0) + webrick (1.8.1) + +PLATFORMS + aarch64-mingw-ucrt + arm64-darwin + ruby + x86-cygwin + x86-mingw-ucrt + x86_64-cygwin + x86_64-darwin + +DEPENDENCIES + jekyll (~> 4.3.3) + jekyll-compose (~> 0.12.0) + jekyll-feed (~> 0.12) + +BUNDLED WITH + 2.5.16 diff --git a/Procfile.dev b/Procfile.dev new file mode 100644 index 0000000..89ef906 --- /dev/null +++ b/Procfile.dev @@ -0,0 +1,2 @@ +jekyll: bundle exec jekyll serve +css: bun run build:css --watch diff --git a/_assets/stylesheets/application.css b/_assets/stylesheets/application.css new file mode 100644 index 0000000..d07c81a --- /dev/null +++ b/_assets/stylesheets/application.css @@ -0,0 +1,7 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +.boundary { + @apply max-w-3xl mx-auto py-10 px-5; +} diff --git a/_config.yml b/_config.yml new file mode 100644 index 0000000..60d78d2 --- /dev/null +++ b/_config.yml @@ -0,0 +1,18 @@ +# the base hostname & protocol for your site, e.g. http://example.com +url: "" + +# the subpath of your site, e.g. /blog +baseurl: "" + +# use /posts/post-name/ as permalink +permalink: /posts/:title/ + +# the name of your site, e.g. ACME Corp. +title: "def app" + +plugins: + - jekyll-feed + +exclude: + - bin + - node_modules \ No newline at end of file diff --git a/_layouts/default.html b/_layouts/default.html new file mode 100644 index 0000000..1c3f1e4 --- /dev/null +++ b/_layouts/default.html @@ -0,0 +1,12 @@ + + + + + + {{ page.title }} - {{ site.title }} + + + + {{ content }} + + diff --git a/_layouts/post.html b/_layouts/post.html new file mode 100644 index 0000000..064adf1 --- /dev/null +++ b/_layouts/post.html @@ -0,0 +1,18 @@ +--- +layout: default +--- + +
+ +
+
+

{{ page.title }}

+ {{ page.date | date: "%-d %B %Y" }} +
+ +
+ {{content}} +
+ +
+
diff --git a/_posts/2024-01-28-revisiting-the-self-and-singleton-class-in-ruby.md b/_posts/2024-01-28-revisiting-the-self-and-singleton-class-in-ruby.md new file mode 100644 index 0000000..e3f244f --- /dev/null +++ b/_posts/2024-01-28-revisiting-the-self-and-singleton-class-in-ruby.md @@ -0,0 +1,195 @@ +--- +layout: post +title: Revisiting the self and singleton class in Ruby +date: 2024-01-28 23:10:18 UTC +--- + +In Ruby, everything is an object, and every object has an anonymous class, which defines the methods the object can respond to. This anonymous class is called the _singleton class_. + +![ruby singleton class inheritance](/assets/images/2024/01/ruby-singleton-class-inheritance.png) + +When calling a method on an object, Ruby will perform the method lookup by first checking on the object’s _singleton class_, before traversing the rest of the method chain. + +## Ruby has no class methods + +The class methods are just instance methods on its _singleton class_. + +```ruby +class Animal + def self.all; end +end + +Animal.singleton_methods +#=> [:all] +Animal.singleton_class.instance_method(:all) +#=> ##all()> +``` + +## The "current class" + +Ruby always holds a reference to the current class, which is called "[default definee](https://blog.yugui.jp/entry/846)" by Yugui of the Ruby core team. Thus, if you define a method without giving an explicit receiver, the _current class_ will have the method as an instance method. + +```ruby +class Animal + def weight; end +end + +Animal.instance_method(:weight) +#=> # +``` + +If you give a receiver to a method definition, the method will be added into the _singleton class_ of the receiver. + +```ruby +word = "hello" +def word.spell; end + +word.singleton_class.instance_method(:spell) +#=> #>#spell()> +``` + +The `class` syntax changes both `self` and the _current class_ to the class which is being defined. However, method definition doesn't. + +```ruby +class Foobar + def foo + def bar; end + def self.baz; end + end +end + +f = Foobar.new +f.foo + +Foobar.instance_method(:foo) +#=> # +Foobar.instance_method(:bar) +#=> # +Foobar.singleton_methods +#=> [] +f.singleton_methods +#=> [:baz] +``` + +## The eval methods + +In Ruby, `instance_eval` and `class_eval` provide that provide the ability to modify a class or an object. The names are very similar, and their behavior is counterintuitive. + +- Use `ClassName#instance_eval` to define a _class method_ (one associated with the class object but not visible to instances). +- Use `ClassName#class_eval` to define an _instance_ method (one that applies to all of the instances of `ClassName`). + +To understand why this is true, let’s see what happens when we call the eval methods. + +The `instance_eval` changes `self` to the receiver, the _current class_ to its _singleton class_. + +```ruby +class Animal + def weight; end +end + +Animal.instance_eval do + def all; end +end + +Animal.instance_method(:all) +#=> NameError (undefined method `all' for class `Animal') + +Animal.singleton_class.instance_method(:all) +#=> ##all()> +``` + +The `class_eval` changes both `self` and the _current class_ to the receiver. + +```ruby +class Animal; end + +Animal.class_eval do + def weight; 1 end +end + +Animal.instance_method(:weight) +#=> # + +Animal.new.weight +#=> 1 + +Animal.weight +#=> NoMethodError (undefined method `weight' for Animal:Class) +``` + +## Open classes + +Ruby supports a concept known as "Open classes", which opens the object's _singleton class_. This is equivalent to giving a receiver a method definition. + +```ruby +class Example + class << self + def foo; end + end + + def self.bar; end +end + +class << Example + def baz; end +end + +Example.singleton_methods +#=> [:foo, :bar, :baz] +``` + +Let's go through some examples: + +```ruby +class Foobar; end + +Foobar.instance_eval do + self #=> Foobar + def method_by_instance_eval; end +end + +Foobar.class_eval do + self #=> Foobar + def method_by_class_eval; end +end + +class << Foobar + self #=> # + def method_by_open_class; end +end + +Foobar.instance_methods +#=> [:method_by_class_eval, ...] + +Foobar.singleton_methods +#=> [:method_by_instance_eval, :method_by_open_class] +``` + +The above context changes can be summarized in the following table: + +| | self | current class | +| ----------------- | :------------------------------ | ------------------------------- | +| class_eval | the receiver | the receiver | +| instance_eval | the receiver | singleton class of the receiver | +| class << receiver | singleton class of the receiver | singleton class of the receiver | + +## Takeaways + +In Ruby, + +1. everything is an object, and every object has a singleton class; +2. there are no class methods; +3. the `instance_eval` changes `self` to the receiver, the _current class_ to its _singleton class_; +4. the `class_eval` changes both `self` and the _current class_ to the receiver; +5. what "open classes" does is opening the singleton class of the receiver object. + +## References + +- [Understanding Ruby Singleton Classes](https://devalot.com/articles/2008/09/ruby-singleton) +- [What is the Singleton Class in Ruby?](https://maximomussini.com/posts/understanding-the-singleton-class/) +- [Self in Ruby: A Comprehensive Overview](https://airbrake.io/blog/ruby/self-ruby-overview) +- [Metaprogramming in Ruby: It's All About the Self](https://yehudakatz.com/2009/11/15/metaprogramming-in-ruby-its-all-about-the-self/) +- [Three implicit contexts in Ruby](https://blog.yugui.jp/entry/846) +- [Understanding class_eval and instance_eval](https://web.stanford.edu/~ouster/cgi-bin/cs142-winter15/classEval.php) +- [class_eval vs instance_eval - Matheus Moreira](https://stackoverflow.com/a/10306049) +- [Diving into Ruby Singleton Classes](https://medium.com/@leo_hetsch/demystifying-singleton-classes-in-ruby-caf3fa4c9d91) diff --git a/assets/images/2024/01/ruby-singleton-class-inheritance.png b/assets/images/2024/01/ruby-singleton-class-inheritance.png new file mode 100644 index 0000000..d8d8caf Binary files /dev/null and b/assets/images/2024/01/ruby-singleton-class-inheritance.png differ diff --git a/assets/stylesheets/.keep b/assets/stylesheets/.keep new file mode 100644 index 0000000..e69de29 diff --git a/bin/dev b/bin/dev new file mode 100755 index 0000000..74ade16 --- /dev/null +++ b/bin/dev @@ -0,0 +1,8 @@ +#!/usr/bin/env sh + +if ! gem list foreman -i --silent; then + echo "Installing foreman..." + gem install foreman +fi + +exec foreman start -f Procfile.dev "$@" diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000..27dcc33 Binary files /dev/null and b/bun.lockb differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..0f84c4f --- /dev/null +++ b/index.html @@ -0,0 +1,28 @@ +--- +layout: default +title: Home +--- + +
+
+ {% for post in site.posts %} +
+

{{ post.date | date_to_string }}

+ +

+ {{ post.title }} +

+
+
+ {{ post.excerpt }} +
+ +
+ {% endfor %} +
+
diff --git a/package.json b/package.json new file mode 100644 index 0000000..bd301fd --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "digital-garden", + "private": true, + "dependencies": { + "@tailwindcss/typography": "^0.5.14", + "autoprefixer": "^10.4.20", + "postcss": "^8.4.41", + "postcss-cli": "^11.0.0", + "postcss-import": "^16.1.0", + "postcss-nesting": "^13.0.0", + "tailwindcss": "^3.4.9" + }, + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "scripts": { + "build:css": "postcss _assets/stylesheets/application.css -o ./assets/stylesheets/application.css" + } +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..071c57d --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,8 @@ +module.exports = { + plugins: { + "postcss-import": {}, + "tailwindcss/nesting": "postcss-nesting", + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..b4fc22b --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,15 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [ + './*.html', + './_layouts/*.html', + './_includes/*.html', + ], + theme: { + extend: {}, + }, + plugins: [ + require('@tailwindcss/typography'), + ], +} + diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..861fd76 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + // Enable latest features + "lib": [ + "ESNext" + ], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +}