Once upon a time, there was a Rails developer who wanted to become a Rails contributor. Deep down he even wanted to become a Rails core team member! So one day he decided to take a first step: he downloaded the source code of Ruby on Rails!
So I downloaded the source code of Rails:
$ git clone email@example.com:rails/rails.git $ cd rails $ ls actioncable actionmailbox actionmailer actionpack actiontext actionview activejob activemodel activerecord activestorage activesupport Brewfile ci CODE_OF_CONDUCT.md CONTRIBUTING.md Gemfile Gemfile.lock guides MIT-LICENSE package.json rails.gemspec RAILS_VERSION railties Rakefile README.md RELEASING_RAILS.md tasks tmp tools version.rb yarn.lock
Damn.. so much stuff in here.. I know most of Rails components, but there are some novelties in here as well. Thankfully Rails has several guides to help me fill the gaps.
ActionCable is a WebSocket wrapper that helps Rails applications do server/clients real-time communication. If you want your web page to receive notifications from your server without AJAX calls, this is what you need.
ActionMailbox allows your application to receive e-mail and process them in controllers.
ActionMailer is for sending e-mails from your application.
ActionPack bundles two major components: ActionDispatch and ActionController.
ActionDispatch is in charge of routing an incoming request to the right controller/action for processing.
ActionController has the responsibility of processing the request and providing a response (MVC).
ActionText provides a rich textbox to your forms (a textbox where you can put styled text, images, etc.)
ActionView is a set of tools (layouts, partials, form builders, ..) for compiling the response of the controller (MVC).
ActiveJob is an interface for scheduling asynchronous jobs.
ActiveModel provides helpers to help you structure your model classes (defining attributes, setting validations and callbacks, ..).
ActiveRecord is an ORM, meaning it is the layer that lets your controllers talk to your database(s) (MVC).
ActiveStorage takes care of file upload for you.
ActiveSupport makes Ruby programming even nicer by adding to it a lot of pretty neat features.
Railties is the core of the Rails framework. It provides several hooks to extend Rails and/or modify the initialization process. This allows the above-mentioned components and any gem included in your Rails application to extend the Rails framework.
The other files are irrelevant for me at this point, I just need to get a sense of how Rails source code is organized.
Okay, so I have an overview of the Rails repository structure. Now what?
Well, my goal is to contribute to Rails. The simplest -yet always useful- contribution one can make to an open source project is a contribution to the documentation. Let’s start with that.
After forking the Rails repository, I need to clone it and configure it to have an easy way to update it from upstream:
$ cd $ mv rails rails_upstream $ git clone firstname.lastname@example.org:<username>/rails.git rails_fork $ cd rails_fork $ git remote add upstream email@example.com:rails/rails.git $ git fetch upstream
Now when I need to update my fork, I’ll do the following:
$ git checkout master $ git pull upstream master $ git push
And when I’m asked to rebase my branch on master, I’ll do the following:
$ git checkout master $ git pull upstream master $ git push $ git checkout <my-branch> $ git rebase master <my-branch> $ git push -f
Contribution to documentation
ActiveStorage and its documentation are pretty new, so I guess there are more chances to find something easy to contribute there.
Reading the source code of ActiveStorage (
activestorage/lib/active_storage/service.rb), I noticed a call to
ActiveSupport::Notifications.instrument. I didn't know what that is so I looked it up:
The instrumentation API provided by Active Support allows developers to provide hooks which other developers may hook into. There are several of these within the Rails framework. With this API, developers can choose to be notified when certain events occur inside their application or another piece of Ruby code.
Okay cool. There are several hooks added by ActiveStorage, so maybe they’re not all mentioned in Active Support Instrumentation guide.
I guessed right, three hooks are missing:
I think I found my first contribution to Rails :D
$ git checkout -b add-activestorage-instrumentation-hooks-to-guide $ # apply wanted modifications to guides/source/active_support_instrumentation.md $ git add guides/source/active_support_instrumentation.md $ git commit $ git push -u origin add-activestorage-instrumentation-hooks-to-guide
Important: since this commit only changes documentation, it is important to add
[ci skip] to the commit message so that the CI doesn't get overloaded unnecessarily.
Now that my branch is pushed, I have to go to https://github.com/rails/rails and open a new pull request.
Pull Request: https://github.com/rails/rails/pull/36010
commit 78260d5663702f153f6bae64073f9659de366946 Author: Younes SERRAJ <firstname.lastname@example.org> Date: Wed Apr 17 19:25:46 2019 +0200 Mention more ActiveStorage hooks in Active Support Instrumentation guide [ci skip] Hooks added: - `service_download_chunk.active_storage` - `service_update_metadata.active_storage` - `preview.active_storage` diff --git a/guides/source/active_support_instrumentation.md b/guides/source/active_support_instrumentation.md index e5ed283c45..4868b00bbe 100644 --- a/guides/source/active_support_instrumentation.md +++ b/guides/source/active_support_instrumentation.md @@ -545,6 +545,14 @@ Active Storage | `:key` | Secure token | | `:service` | Name of the service | +### service_download_chunk.active_storage + +| Key | Value | +| ------------ | ------------------------------- | +| `:key` | Secure token | +| `:service` | Name of the service | +| `:range` | Byte range attempted to be read | + ### service_download.active_storage | Key | Value | @@ -582,6 +590,23 @@ Active Storage | `:service` | Name of the service | | `:url` | Generated URL | +### service_update_metadata.active_storage + +| Key | Value | +| --------------- | ------------------------------ | +| `:key` | Secure token | +| `:service` | Name of the service | +| `:content_type` | HTTP Content-Type field | +| `:disposition` | HTTP Content-Disposition field | + +INFO. The only ActiveStorage service that provides this hook so far is GCS. + +### preview.active_storage + +| Key | Value | +| ------------ | ------------------- | +| `:key` | Secure token | + Railties --------
There is now one very difficult thing to do: wait. Wait for a member of the core team to review the pull request and comment on it or merge it. Wait without reloading the Github page every 5 seconds. Just move on to something else and wait for an e-mail notification.
It got merged.
Contribute to a Rails component
I started with ActiveStorage, I’m going to stick with it for now. I don’t have a feature I want to add to it so from time to time I check the issues on Github in search of inspiration. The bug I found that’s related to ActiveStorage and pretty easy to fix is the following: https://github.com/rails/rails/issues/35953
Great! I now have my next thing contribution and this time, it’s code. Code goes with testing, so I have to prepare my development environment to run the test suite of Rails. Scary, but nothing unachievable :)
There are mainly two roads for this: one can either install all the dependencies or run everything in a virtual machine. I’m personally going to do both for the sake of learning, but I’ll only mention the easiest way in this blog, the virtual machine way.
First I followed this guide: https://github.com/rails/rails-dev-box
After installing VirtualBox and Vagrant on my Debian 9.8, I ran the following commands
$ # in the host: $ cd $ git clone https://github.com/rails/rails-dev-box.git $ cd rails-dev-box $ cp -r ~/rails_fork ./rails $ vagrant up $ vagrant ssh $ # once in the virtual machine: $ cd /vagrant/rails $ bundle
Notice I copied my
rails_fork directory inside the
rails-dev-box directory so that I can ssh into the virtual machine and access the source code I'm working on (to run the tests).
Okay so now I have my development environment setup ready to rock n roll, I need to run the tests before anything else. I want to make sure the tests run successfully before changing the source code of ActiveStorage (so that if it fails I know because of my code changes, not some setup mistake).
$ # in the virtual machine: $ cd /vagrant/rails $ bundle exec rake
And again: wait. Just.. wait.. for.. it.. to.. run.
Done. Yeah, it’s green!! Let’s get down to business.
Note: as advised in the documentation, I’ll edit the code in the host and run the tests in the virtual machine. This way I don’t install any software on the virtual machine and avoid accidental changes of package versions and what not.
$ git checkout -b active-storage-bmp-variants-support
I can now add the following changes:
activestorage/lib/active_storage/engine.rbto fix the issue (here I'm whitelisting a mime type for ActiveStorage to know it can create BMP variants)
activestorage/test/models/variant_test.rbto test that ActiveStorage actually succeeds at creating a BMP variant
activestorage/test/fixtures/files/colors.bmpthat will be used by the above test
activestorage/CHANGELOG.mdto inform Rails developers of the changes my commit introduces.
It hasn’t been that hard to write this patch. The only part that I struggeled with a bit was the testing part because I’m used to RSpec, not minitest. I read the existing tests to find out how to write my own, then I googled how to test a single file. In my case, I ran:
$ cd /vagrant/rails/activestorage $ bundle exec ruby -Itest ./test/models/variant_test.rb`
Green. I like that. I can now commit and push to Github:
$ cd /vagrant/rails $ git add ./activestorage $ git commit $ git push -u origin active-storage-bmp-variants-support
Pull request: https://github.com/rails/rails/pull/36051
commit bcf370d689673031073ba2ac5588afe41cc315c9 Author: Younes SERRAJ <email@example.com> Date: Sun Apr 21 19:07:22 2019 +0200 Allow ActiveStorage to generate variants of BMP images diff --git a/activestorage/CHANGELOG.md b/activestorage/CHANGELOG.md index 54fc949172..d524ecf7d6 100644 --- a/activestorage/CHANGELOG.md +++ b/activestorage/CHANGELOG.md @@ -1,3 +1,7 @@ +* Permit generating variants of BMP images. + + *Younes Serraj* + ## Rails 6.0.0.beta3 (March 11, 2019) ## * No changes. diff --git a/activestorage/lib/active_storage/engine.rb b/activestorage/lib/active_storage/engine.rb index fc75a8f816..cbb205627e 100644 --- a/activestorage/lib/active_storage/engine.rb +++ b/activestorage/lib/active_storage/engine.rb @@ -33,6 +33,7 @@ class Engine < Rails::Engine # :nodoc: image/jpeg image/pjpeg image/tiff + image/bmp image/vnd.adobe.photoshop image/vnd.microsoft.icon ) @@ -56,6 +57,7 @@ class Engine < Rails::Engine # :nodoc: image/jpg image/jpeg image/tiff + image/bmp image/vnd.adobe.photoshop image/vnd.microsoft.icon application/pdf diff --git a/activestorage/test/fixtures/files/colors.bmp b/activestorage/test/fixtures/files/colors.bmp new file mode 100644 index 0000000000..3cc1e8764d Binary files /dev/null and b/activestorage/test/fixtures/files/colors.bmp differ diff --git a/activestorage/test/models/variant_test.rb b/activestorage/test/models/variant_test.rb index d98935eb9f..92e3384042 100644 --- a/activestorage/test/models/variant_test.rb +++ b/activestorage/test/models/variant_test.rb @@ -144,6 +144,17 @@ class ActiveStorage::VariantTest < ActiveSupport::TestCase assert_equal 33, image.height end + test "resized variation of BMP blob" do + blob = create_file_blob(filename: "colors.bmp") + variant = blob.variant(resize: "15x15").processed + assert_match(/colors\.bmp/, variant.service_url) + + image = read_image(variant) + assert_equal "BMP", image.type + assert_equal 15, image.width + assert_equal 8, image.height + end + test "optimized variation of GIF blob" do blob = create_file_blob(filename: "image.gif", content_type: "image/gif")
Note: sometimes you’ll have multiple commits in your pull request. You might be asked to squash them to help keep the history as clean as possible. If asked so,
git rebase -i and
git commit --amend are your friends.
So I opened a pull request and.. waited. Yes, there’s a lot of waiting in open source contributions. Keep in mind that the core contributors have their paid job to take care of, and their personal life, and a bunch of issues to read, and a bunch of pull requests to review, and their own pull requests to work on, and mailing lists to keep up to date with, etc. Yes it does take time. Yes we love them for the awesome work they do. Yes when we start contributing, it’s frustrating to wait and we almost get banned from Github for refreshing too often our pull request page and that’s okay. We’re contributing to Rails and that’s really an awesome feeling because we get to help other developers, we learn more about Rails and we appreciate even more the work all these contributors have done before us.
Guys, if you’ve been playing with Rails for two or three years, you probably can contribute. My advice is this: read issues on Github, test them to confirm that they are reproducible or ask for more details when need be, read pull requests, when you find something new to you don’t just read tutorials and documentation but go read the source code as well. It doesn’t matter if you don’t understand everything that’s going on, at least you’re gathering knowledge and slowly building an intuitive understanding of how Rails works. Before you know it, you’ll be so familiar with it that contributing to Ruby on Rails will be a piece of cake.
Peace and love my friends.