Rails Tricks

Archive

20 Jun

Offline Ruby and Rails documentation

e80bef84-9c82-484c-81da-2c81ac52c6ac.jpg 110 KB

The vacation season is starting, and it can be helpful to have access without the internet to the documentation of Ruby, Rails, and the gems you use. If you are going on a trip and taking your laptop, you can prepare them in advance by following this little guide.


Let’s start with Ruby, that’s the easiest. To browse the API documentation offline, you can download a prebuilt copy from https://ruby-doc.org/downloads/. Extract the downloaded archive, open index.html and you can browse the documentation.


For Rails, you can generate the Rails Guides for local browsing. You need to clone the Rails repository, install some dependencies and run a rake command to generate the guides:

[~/] git clone https://github.com/rails/rails.git
[~/] cd rails/guides
[~/rails/guides] bundle
[~/rails/guides] rake guides:generate:html

When it is done, you will have the files in the output directory. The above will generate the guides for edge Rails, if you want to see the guides for a specific version, you need to checkout that branch. For instance for Rails 7.0:

[~/rails/guides] git checkout 7-0-stable
[~/rails/guides] rake guides:generate:html

You would probably also want to have the API documentation at hand. To generate that, you need to run the following rake command:

[~/rails] rake rdoc

Then open doc/rdoc/index.html in a browser.


For the gems, you have multiple options:


RDoc


First of all, you should make sure you have the rdoc files generated for the gems you have installed on your system. For that you can run:

gem rdoc --all

Once the docs are generated, you need to install the rubygems-server gem and start the server:

gem install rubygems-server
gem server

YARD


YARD is another documentation tool for ruby gem. To run the yard server, you need to install the gem and start the server:

gem install yard
yard gems

RI


If you don’t want to leave your terminal, you can also use ri to browse the documentation. You can run ri Array to view the documentation of the Array class, or ri ActionController::Base to view the rdoc docs for ActionController::Base.


That’s it for the week!

13 Jun

Active Record Transactions - Rails Tricks Issue 10


This week we will look into database transactions.


First of all, let me try to explain what they are. A database transaction is a unit of work that encapsulates dependencies and is executed either completely or rolled back to the initial state. For instance, there is double-entry accounting, where you always have a credit and debit record for a transaction, so your accounts stay in balance, and you never want to end up in a situation where you record only one side of a transaction.


If you were working on an app that helps to split the Bill between Users, when someone pays what they are due, you will decrease their balance by that amount and allocate it to the bill:

bill.record_payment_of(amount)
user.decrease_balance_by(amount)

If there is an exception at the second step of the process, the numbers will be unbalanced, and the user will have a bigger balance left than they should’ve. To prevent this from happening, we can wrap this unit into a transaction, and if an exception is raised inside the block, the whole transaction is rolled back:

ActiveRecord::Base.transaction do
  bill.record_payment_of(amount)
  user.decrease_balance_by(amount)
end

In the above example, we would want to force the rollback of the transaction if the user’s balance doesn’t cover the amount needed. To achieve this, we can raise an ActiveRecord::Rollback exception:

# app/models/user.rb

def has_balance_for?(amount)
  ...
end

def decrease_balance_by(amount)
  raise ActiveRecord::Rollback unless has_balance_for?(amount)
  ...
end
The above code will roll back the transaction by raising the exception if there is no balance to cover the necessary amount. You might see that some people start the transaction by calling the transaction method on an actual model class instead of ActiveRecord::Base:
Bill.transaction do
  bill.record_payment_of(amount)
  user.decrease_balance_by(amount)
end
Since your Active Record models are inheriting from ActiveRecord::Base, this is the same as calling transaction directly on the base class. Choosing one over the other is just a matter of preference.
It is worth noting that Active Record uses transactions internally for many operations to maintain data integrity. For instance, when you have a has_many relation, and you have dependent: :destroy set, Active Record will wrap this into a transaction, and if anything happens while destroying the dependent records, the transaction will be rolled back, and the data will stay in the original state.
That’s it for now, until next time!

30 May

Modify database schema with migrations - Rails Tricks Issue 9


Hi, this week, I will show you a few useful methods to modify your database schema with Active Record migrations.


You probably found yourself in a situation when a NOT NULL constraint needs to be removed or added to a table. Active Record provides a useful migration method to do so. To add a constraint, you can use:

change_column_null :table, :column, true

To remove it, you can use:

change_column_null :table, :column, false

If you also want to change the default value of the column in one go, you can pass the default value as the last parameter:

change_column_null :table, :column, false, 0

If you want to change the default value only(without changing the NULL constraint), use:

change_column_default :table, :column, "default value"

To have no default value, you need to change it to nil. If you want your migration to be reversible, you can specify a from and to value:

change_column_default :table, :column, from: nil, to: 0


It is rare in my experience, but you can also add or change comments on the database tables or columns. The methods for those are change_column_comments and change_table_comments. Just as when changing the default value, you can change the comment by specifying a new one:

change_column_comment :table, :column, "comment"

Or if you want the migration to be reversible, you can specify the from and to options:

change_column_comment :table, :column, from: "old comment", to: "new_comment"

I know this issue is pretty short, but good stuff comes in small quantities.

23 May

Infer name with link_to - Rails Tricks Issue 8


Hi, this week I want to tell you about an improvement coming in Rails 7.1. When you are using the link_to helper, it can infer the URL from the object you are passing to it as the second parameter:

e64c5264-b936-4296-8245-4fc6a337d1b0.png 36.7 KB Wouldn’t it be nice to infer the content of the a tag too? Thanks to Olivier Lacan, in Rails 7.1 that will be possible. You can specify what the text should be in the to_s method of the object, and you will only need to pass the object to the helper: d2dae7c6-f1a0-4876-ab44-db492a3a96cf.png 73.7 KB I love these small improvements to the framework.

While we are talking about link_to, I’d like to mention something about this helper. The second parameter accepts a string for the href attribute of the a tag. The HTML specification permits various protocols for that attribute, including javascript, so for instance, you can make a dummy link with the following:

3f3b14aa-c0c6-413a-992d-b38d3e81fd26.png 33.7 KB

Now let’s say in your application a user can specify the URL for their blog and you pass that to link_to:

5999a45b-1f1d-434c-9286-2415cab3838f.png 24.9 KB This user can set the blog URL to javascript: XSS_PAYLOAD, and when someone clicks the link, the browser executes the JavaScript. To mitigate this issue, always validate the format of a URL your application accepts, especially if you intend to use it for linking to that URL.

That’s it for today. You may want to check out a post I wrote about a related topic about using link_to_if and link_to_unless to conditionally render a link in Rails.

16 May

Shared examples with Minitest - Rails Tricks Issue 7


Hi, this week I will show how you can achieve a similar behavior to Rspec’s shared examples with Minitest. We will dry the minitest tests by extracting the common parts into modules.


Imagine you generated two scaffolds, one for a “User” object and one for an “Article” object. Your scaffold tests have almost the same code for most CRUD methods, so let’s share them between the tests. To achieve this, we will create a module:

c5e5fabe-cc72-44e9-aea4-908474f48ce5.png 126 KB

This module uses ActiveSupport::Concern to have a friendlier interface to call methods when this module is included. At that point, we will create a test for displaying the edit page of the CRUD. Instead of naming the instance variable after the actual object, we will use a generic name @subject, and to generate the URL, we will use the url_for helper with the controller generated from the @subject’s class. We need to change both of our controller tests to set the instance variable and to include this module to test the edit endpoint:

b404288a-26ed-461c-9be0-0e90f13d1c3a.png 110 KB

We can remove the generated tests for this edit action from each file and run the tests. To dry our tests further, let’s extract the test for deleting, showing, and listing the records and for the new record action:

1c2b8708-0c82-4320-8362-2941a1328337.png 349 KB

Extracting the create and update tests are more involving because there are different attributes for each model, but with the help of other instance variables we can extract those too:

d96eb149-3623-423b-ab84-5d295c3be12c.png 350 KB

I hope the above examples help to extract common parts of your minitest tests into shared examples.