Different ways to set attributes in ActiveRecord
Rails 5 / 6 / 7
| Method | Uses Default Accessor | Saves to Database | Runs Validations | Runs Callbacks | Updates updated_at/updated_on | Respects Readonly |
|---|---|---|---|---|---|---|
attribute= |
Yes | No | n/a | n/a | n/a | n/a |
attributes= |
Yes | No ... |
Configuring ActionMailer host and protocol for URL generation
When you generate a URL in a mailer view, ActionMailer will raise an error unless you previously configured it which hostname to use.
There are two options to set the default_url_options of ActionMailer:
- Hardcoded solution (preferred solution when using Rails with ActiveJob/Sidekiq or Cronjobs)
- Dynamic solution
1. Hardcoded solution
When you are sending mails from outside the request cycle, e.g. ActiveJob/Sidekiq or Cronjobs, y...
Cancelling the ActiveRecord callback chain
| Goal | Within before_*
|
Within after_*
|
|---|---|---|
| Cancel later callbacks | throw :abort |
throw :abort |
| Rollback the transaction | throw :abort |
raise ActiveRecord::Rollback |
When a callback raises an error
Exceptions raised in callbacks always rollback the transaction, but only exceptions that are not ActiveRecord::Rollback will bubble up to the caller.
Further readi...
better rails app restart with the passenger restart-app tool
With this command you can initiate an application restart without touching restart.txt. Unlike touching restart.txt, this tool initiates the restart immediately instead of on the next request. http://blog.phusion.nl/2014/01/02/phusion-passenger-4-0-33-released/
If you want to use this with capistrano 2.x just replace the touch command:
- run "touch #{current_path}/tmp/restart.txt"
+ run "passenger-config restart-app --ignore-app-not-running #{deploy_to}...
Delivering Carrierwave attachments to authorized users only
Preparation
To attach files to your records, you will need a new database column representing the filename of the file. To do this, add a new migration (rails g migration <name>) with the following content:
class AddAttachmentToNotes < ActiveRecord::Migration[6.0]
def change
add_column :notes, :attachment, :string
end
end
Don't forget to rename the class and change the column details to fit your purpose. Run it.
1) Deliver attachments through Rails
The first way is to store your Carrierwave attachments not ...
SameSite cookies
TL;DR Most web applications do not require action on this. SameSite=None (old browser default) will continue to work, and SameSite=Lax (new Chrome default, gradually rolled out) is an even better default for cookies. Set SameSite=Strict only for extra security in special cases (see below). If your application is rendered in an iframe (e.g. a video player or some news stream), you need to configure its relevant cookies as SameSite=None.
The SameSite cookie attribute targets **c...
Rails: Configure ActionController to raise when strong params are invalid
Put the line below in the respective env.rb file to make your action controllers raise an ActionController::UnpermittedParameters error when strong params are not valid. This might come in handy when you are implementing an API and you want to let the user know that the request structure was invalid. You can then rescue those errors by implementing a rescue_from to have a generic handling of those cases. Note that you might need to whitelist common params such as :format to not raise on valid requests.
config.action_controller.a...
Differences between transactions and locking
Web applications can be used by multiple users at the same time. A typical application server like Passenger has multiple worker processes for a single app. In a distributed deployment setup like we use at makandra you will even have multiple application servers, each with their own worker pool.
This means that your code needs to deal with concurrent data access. The two main tools we use to cope with concurrency are database transactions and distributed locks. These two are not interchangeable. You ca...
Action Mailer Previews
Rails includes a way to see what an e-mail will look like.
Integration to RSpec
All you need to do is implement a preview-class in spec/mailers/previews/notifier_preview.rb:
class NotifierPreview < ActionMailer::Preview
def welcome
Notifier.welcome(User.first)
end
end
And adapt the preview load path in your application.rb:
config.action_mailer.preview_path = "#{Rails.root}/spec/mailers/previews" # For Rails < 7.1
config.action_mailer.preview_paths << "#{Rails.root}/spec/mailers/previews" # For Rails >=...
Taking screenshots in Capybara
Capybara-screenshot can automatically save screenshots and the HTML for failed Capybara tests in Cucumber, RSpec or Minitest.
Requires Capybara-Webkit, Selenium or poltergeist for making screenshots. Screenshots are saved into $APPLICATION_ROOT/tmp/capybara.
Manually saving a page
Additionally you can trigger the same behavior manually from the test using Capybara::Session#save_and_open_page and [...
Timeouts for long-running SQL queries
While the main goal always is to prevent long-running queries in the first place, automatic timeouts can serve as a safety net to terminate problematic queries automatically if a set time limit is exceeded. This prevents single queries from taking up all of your database’s resources and reduces the need for manual intervention that might destabilize or even crash the application.
As Rails does not set a timeout on database statements by default, the following query will run for an entire day:
ActiveRecord::Base.connection.execute("S...
Logic of `where.not` with multiple attributes
When using where.not with a Hash of multiple attributes, Rails applies logical NAND (NOT (A AND B)).
This contrasts with logical NOR (NOT A AND NOT B), which is achieved by chaining multiple where.not calls.
The difference in logic alters the scope of excluded records:
- NAND: Excludes records only if they match all attributes simultaneously.
- NOR: Excludes records if they match any of the attributes.
| NAND | NOR |
|---|---|
| (also calledvarcharor juststring): Contents are limited to n characters, smaller contents are allowed. -
character(n): All contents are padded with spaces to allocate exactly n characters. -
text: There is no upper or lower character limit (except for the absolute...
Best practice: How to manage versions in a Gemfile
It most cases it's not necessary to add a version constraint next to your gems in the Gemfile. Since all versions are saved in the Gemfile.lock, everyone running bundle install will get exactly the same versions.
There are some exceptions, where you can consider adding a version constrain to the Gemfile:
- You are not checking in the
Gemfile.lockinto the version control (not recommended) - A specific gem has a bug in a more recent version (adding a comment for the reason is highly recommended)
- You want to ensure no one upgrade...
Load order of the environment
Rails 3, 4, 5, 6
config/application.rb-
config/environment.rbbefore theinitialize!call (we don't usually edit this file) - The current environment, e.g.
environments/production.rb - Gems
- Vendored plugins
- All initializers in
config/initializers/*.rb -
config/environment.rbafter theinitialize!call (we don't usually edit this file) - Your own code from
app
Rails 2
- Code in
config/preinitializer.rb(if it exists) -
environment.rb, code above theRails::Initializer.runblo...
HTTP 302 redirects for PATCH or DELETE will not redirect with GET
A HTTP 302 Found redirect to PATCH and DELETE requests will be followed with PATCH or DELETE. Redirect responses to GET and POST will be followed with a GET. The Rails form_for helper will use a workaround to send POST requests with a _method param to avoid this issue for PATCH/DELETE.
If you make requests yourself, watch out for the following behavior.
When you make an AJAX request PATCH /foo and the /foo action redirects to /bar, browsers will request PATCH /bar. You probably expected the second requ...
ActiveRecord: Passing an empty array into NOT IN will return no records
Caution when using .where to exclude records from a scope like this:
# Fragile - avoid
User.where("id NOT IN (?)", excluded_ids)
When the exclusion list is empty, you would expect this to return all records. However, this is not what happens:
# Broken example
User.where("id NOT IN (?)", []).to_sql
=> SELECT `users`.* FROM `users` WHERE (id NOT IN (NULL))
Passing an empty exclusion list returns no records at all! See below for better implementations.
Rails 4+
Use the .not method to let Rails do the logic
`...
Using rack-mini-profiler (with Unpoly)
Debugging performance issues in your Rails app can be a tough challenge.
To get more detailed insights consider using the rack-mini-profiler gem.
Setup with Unpoly
Add the following gems:
group :development do
gem 'memory_profiler'
gem 'rack-mini-profiler'
gem 'stackprof'
end
Unpoly will interfere with the rack-mini-profiler widget, but configuring the following works okayish:
// rack-mini-profiler + unpoly
if (process...
High-level data types with "composed_of"
I recently stumbled upon the Rails feature composed_of. One of our applications dealt with a lot of addresses and they were implemented as 7 separate columns in the DB and Rails models. This seemed like a perfect use case to try out this feature.
TLDR
The feature is still a VERY leaky abstraction. I ran into a lot of ugly edge cases.
It also doesn't solve the question of UI. We like to use
simple_form. It's currently not possible to simply write `f...
Testing ActiveRecord validations with RSpec
Validations should be covered by a model's spec.
This card shows how to test an individual validation. This is preferrable to save an entire record and see whether it is invalid.
Recipe for testing any validation
In general any validation test for an attribute :attribute_being_tested looks like this:
- Make a model instance (named
recordbelow) - Run validations by saying
record.validate - Check if
record.errors[:attribute_being_tested]contains the expected validation error - Put the attribute into a valid state
- Run...
How to disable logging for ActiveStorage's Disk Service routes
In development, we store files using ActiveStorage's disk service. This means that stored files are served by your Rails application, and every request to a file results in (at least!) one non-trivial log entry which can be annoying. Here is how to disable those log entries.
Example
Here is an example of what loading a single <img> in an example application writes to the Rails log.
Started GET "/rails/active_storage/blobs/redirect/..." for ::1 at ...
Processing by ActiveStorage::Blobs::RedirectController#show as SVG
Parameter...
CarrierWave: Default Configuration and Suggested Changes
CarrierWave comes with a set of default configuration options which make sense in most cases. However, you should review these defaults and adjust for your project wherever necessary.
You will also find suggestions on what to change below.
Understanding the default configuration
Here is the current default config for version 2:
config.permissions = 0644
config.directory_permissions = 0755
config.storage_engines = {
:f...
How to update a single gem conservatively
The problem
Calling bundle update GEMNAME will update a lot more gems than you think. E.g. when you do this:
bundle update cucumber-rails
... you might think this will only update cucumber-rails. But it actually updates cucumber-rails and all of its dependencies. This will explode in your face when one of these dependencies release a new version with breaking API changes. Which is all the time.
In the example above updating cucumber-rails will give you Capybara 2.0 (because capybara is a dependency of `cucumber-rail...
Be very careful with 301 and 308 redirects
Browsers support different types of redirects.
Be very careful with these status codes:
301 Moved Permanently308 Permanent Redirect
Most browsers seem to cache these redirects forever, unless you set different Cache-Control headers. If you don't have any cache control headers, you can never change them without forcing users to empty their cache.
Note
By default Rails sends a ...