Ruby: How to use prepend for cleaner monkey patches
Let's say you have a gem which has the following module:
module SuperClient
def self.foo
'Foo'
end
def bar
'Bar'
end
end
For reasons you need to override foo
and bar
.
Keep in mind: Your code quality is getting worse with with each prepend
(other developers are not happy to find many library extensions). Try to avoid it if possible.
- Add a
lib/ext/super_client.rb
to your project (see How to organize monkey patches in Ruby on Rails projects) - Add the extension, which ov...
Deliver Paperclip attachments to authorized users only
When Paperclip attachments should only be downloadable for selected users, there are three ways to go.
The same applies to files in Carrierwave.
- Deliver attachments through Rails
The first way is to store Paperclip attachments not in the default public/system
, but in a private path like storage
inside the current release. You should prefer this method when dealing with sensitive data.
Make ...
Capybara: Preventing server errors from failing your test
When your Rails application server raises error, Capybara will fail your test when it clears the session after the last step. The effect is a test that passes all steps, but fails anyway.
Capybara's behavior will help you to detect and fix errors in your application code. However, sometimes your application will explode with an error outside your control. Two examples:
- A JavaScript library references a source map in its build, but forgets to package the source map
- CarrierWave cleans up an upload or cache file after the record was delet...
Heads up: Quering array columns only matches equally sorted arrays
Given you have an array column like this:
create_table "users", force: :cascade do |t|
t.integer "movie_ids", default: [], array: true
end
You might think that the following queries yield the same result:
User.where(movie_ids: [16, 17])
User.where(movie_ids: [17, 16])
Turn's out - they are not! They do care about array ordering more than I do.
To query for identical arrays independent of their order you have to either:
- Sort both the query and database content. If you're on Rails 7.1 you can use the new [`normal...
Testing ActiveRecord callbacks with RSpec
Our preferred way of testing ActiveRecord is to simply create/update/destroy the record and then check if the expected behavior has happened.
We used to bend over backwards to avoid touching the database for this. For this we used a lot of stubbing and tricks like it_should_run_callbacks
.
Today we would rather make a few database queries than have a fragile test full of stubs.
Example
Let's say your User
model creates a first Project
on cr...
Don't require files in random order
A common pattern in Ruby is to to require all files in a specific diretory, using something like
Dir.glob(Rails.root.join('lib/ext/**/*.rb')).each do |filename|
require filename
end
However, this causes files to be required in an order determined by the file system. Since load order can be important, this may lead to different behavior on different machines which are hard to debug.
Simply add a .sort
:
Dir.glob(Rails.root.join('lib/ext/**/*.rb')).sort.each do |filename|
require filename
end
Ruby 3
...
JSON APIs: Default design for common features
When you build a JSON API you need to come up with a style to represent attributes, pagination, errors or including associated objects. Instead of reinventing the wheel, you may reuse successful API designs.
JSON API
JSON:API specifies common features found in most JSON APIs, like pagination, ordering and nested resources. The spec looks very similar to how one would build an API with Rails and uses similar patterns. Even if you don't plan on supporting the whole spec, it can still make sense to know how th...
Cucumber: Identifying slow steps that drag down your test speed
In most projects I know, Cucumber test suite speed is not an issue. Of course, running 350 features takes its time, but still each test for itself is reasonably fast. There is nothing you can do to fundamentally speed up such a test (of course, you should be using parallel_tests).
However, in projects that go beyond clicking around in a web GUI and checking results, there might be various measures to speed things up. Katapult tests for example could be sped up more than 4 times by re...
Vortrag: Content Security Policy: Eine Einführung
Grundidee
CSP hat zum Ziel einen Browser-seitigen Mechanismus zu schaffen um einige Angriffe auf Webseiten zu verhindern, hauptsächlich XSS-Angriffe.
Einschub: Was ist XSS?
XSS = Cross Site Scripting. Passiert wenn ein User ungefiltertes HTML in die Webseite einfügen kann.
<div class="comment">
Danke für den interessanten Beitrag! <script>alert('you have been hacked')</script>
</div>
Rails löst das Problem weitgehend, aber
- Programmierfehler weiter möglich
- manchmal Sicherheitslücken in Gems oder Rails
Lösungsid...
When reading model columns during class definition, you must handle a missing/empty database
When doing some meta-programming magic and you want to do something for all attributes of a class, you may need to access connection
or some of its methods (e.g. columns
) during class definition.
While everything will be fine while you are working on a project that is in active development, the application will fail to boot when the database is missing or has no tables. This means that Raketasks like db:create
or db:migrate
fail on a freshly cloned project.
The reason is your environment.rb
which is loaded for Raketasks and calls...
Detecting if a Ruby gem is loaded
Detect if a gem has been activated
A gem is activated if it is either in the current bundle (Gemfile.lock
), or if you have manually activated it using Kernel#gem
(old-school).
To detect if e.g. activerecord
has been activated:
if Gem.loaded_specs.has_key?('activerecord')
# ActiveRecord was activated
end
Detect if a particular gem version has been activated
To detect if e.g. activerecord
ma...
How to fix: Pasting in IRB 1.2+ is very slow
IRB 1.2 (shipped with Ruby 2.7, but works on 2.5+) brings pretty syntax highlighting and multiline cursor navigation. However, pasting longer contents is incredibly slow. You can fix that by disabling said features. [1]
Ruby 3.0.0-pre2 solved the issue (however, the fix does not appear to be included in IRB 1.2.6, it must be Ruby itself).
Option 1:
Add a command line flag when opening an IRB:
irb --nomultiline
This also works on modern Rails...
How to search through logs on staging or production environments
We generally use multiple application servers (at least two) and you have to search on all of them if you don't know which one handled the request you are looking for.
Rails application logs usually live in /var/www/<project-environment-name>/shared/log
.
Web server logs usually live in /var/www/<project-environment-name>/log
.
Searching through single logs with grep
/ zgrep
You can use grep
in this directory to only search the latest logs or zgrep
to also search older (already zipped) logs. zgrep
is used just like grep
...
MySQL: Disable query cache for database profiling
If you want to see how long your database queries actually take, you need to disable MySQL's query cache. This can be done globally by logging into a database console, run
SET GLOBAL query_cache_type=OFF;
and restart your rails server.
You can also disable the cache on a per query basis by saying
SELECT SQL_NO_CACHE * FROM ...
You also probably want to disable Rails internal (per-request) cache. For this, wrap your code with a call to ActiveRecord::Base.uncached
. For example, as an around_filter
:
d...
Carrierwave: How to remove container directories when deleting a record
When deleting a record in your Rails app, Carrierwave automatically takes care of removing all associated files.
However, the file's container directory will not be removed automatically. If you delete records regularly, this may be an annoyance.
Here is a solution which was adapted from the Carrierwave GitHub wiki and cleans up any empty parent directories it can find.
class ExampleUploader < CarrierWave...
Storing trees in databases
This card compares patterns to store trees in a relation database like MySQL or PostgreSQL. Implementation examples are for the ActiveRecord ORM used with Ruby on Rails, but the techniques can be implemented in any language or framework.
We will be using this example tree (from the acts_as_nested_set docs):
root
|
+-- Child 1
| |
| +-- Child 1.1
| |
| +-- Child 1.2
|
+-- ...
ES6 imports are hoisted to the top
From Exploring ES6:
Module imports are hoisted (internally moved to the beginning of the current scope). Therefore, it doesn’t matter where you mention them in a module and the following code works without any problems:
foo(); import { foo } from 'my_module';
Footgun example
When you're not aware of import hoisting you may be surprised that your code runs in a different order than you see in the source file.
The example below is taken from the [...
Why two Ruby Time objects are not equal, although they appear to be
So you are comparing two Time
objects in an RSpec example, and they are not equal, although they look equal:
expected: Tue May 01 21:59:59 UTC 2007,
got: Tue May 01 21:59:59 UTC 2007 (using ==)
The reason for this is that Time
actually tracks fractions of a second, although #to_s
doesn't say so and even though you probably only care about seconds. This means that two consecutive calls of Time.now
probably return two inequal values.
Consider freezing time in your tests so it is not dependent on the speed of the executi...
PostgreSQL cheat sheet for MySQL lamers
So you're switching to PostgreSQL from MySQL? Here is some help...
General hints on PostgreSQL
-
\?
opens the command overview -
\d
lists things:\du
lists users,\dt
lists tables etc
Command comparison
Description | MySQL command | PostgreSQL equivalent |
---|---|---|
Connect to the database | mysql -u $USERNAME -p |
sudo -u postgres psql |
Show databases | SHOW DATABASES; | \l[ist] |
Use/Connect to a database named 'some_database' | USE some_database; | \c some_dat... |
ActiveRecord: count vs size vs length on associations
TL;DR: You should generally use #size
to count associated records.
size
- Counts already loaded elements
- If the association is not loaded, falls back to a
COUNT
query
count
- If a counter cache is set up, returns the cached value
- Issues a
COUNT
query else
Caveats
- If you trigger a
COUNT
query for an association of an an unsaved record, Rails will try to load all children where the foreign keyIS NULL
. This is not what you want. To prevent this behavior, you can useunsaved_record.association.to_a.size
. - `c...
PSA: Be super careful with complex `eager_load` or `includes` queries
TLDR
Using
.includes
or.eager_load
with 1-n associations is dangerous. Always use.preload
instead.
Consider the following ActiveRecord query:
BlogPost.eager_load(
:comments
:attachments,
).to_a
(Let's assume we only have a couple of blog posts; if you use pagination the queries will be more complicated, but the point still stands.
Looks harmless enough? It is not.
The problem
ActiveRecord will rewrite this into a query using LEFT JOIN
s which looks something like this:
SELECT "blog_posts...
Heads up: pg_restore --clean keeps existing tables
When restoring a PostgreSQL dump using pg_restore
, you usually add the --clean
flag to remove any existing data from tables.
Note that this only removes data from tables that are part of the dump and will not remove any extra tables. You need to do that yourself.
When is this relevant?
As an example: You want to load a staging dump into your development machine. On your development machine, you have run migrations that introduced more tables which do not yet exist on staging. pg_restore
with --clean
will loa...
Do not use "permit!" for params
Rails' Strong Parameters enable you to allow only specific values from request params
to e.g. avoid mass assignment.
Usually, you say something like params.permit(:email, :password)
and any extra parameters would be ignored, e.g. when calling to_h
.
This is excellent and you should definitely use it.
What is permit!
and why is it dangerous?
However, there is also params.permit!
whic...
redirect_to and redirect
There are multiple ways to redirect URLs to a different URL in Rails, and they differ in small but important nuances.
Imagine you want to redirect the following url https://www.example.com/old_location?foo=bar
to https://www.example.com/new_location?foo=bar
.
Variant A
You can use ActionController::Redirecting#redirect_to
in a controller action
class SomeController < ActionController::Base
def old_location
redirect_to(new_location_url(params.permit(:foo)))
end
end
This will:
- It will redirect with a 302 st...