Euruko 2017 Notes
Vừa rồi mình đi Euruko 2017 ở Budapest, một số bài nói cũng khá thú vị nên mình sẽ note lại ở đây.
It’s undeniable that Rails is a great framework to speedily build up your application. However, despite of its handiness, like other frameworks, Rails has its own flaws and is never a silver bullet. This post is going to show you some of the gotchas (or pitfalls you name it) I encountered while working with Rails.
The code below is supposed to assign posts to a specific user.
class User < ActiveRecord::Base
has_many :posts
end
class Post < ActiveRecord::Base
belongs_to :user
end
user = User.find_by_name 'John'
user.posts = [Post, Post, Post]
user.save!
Of course it works (perfectly)! Then you might ask what’s the problem? Good question. When do you think the posts will get saved?
For a common people with common sense, posts
will be persisted when you
invoke user.save!
, then you’re trapped. Right after user.posts
was assigned,
the persistence will be invoked, so calling User#save!
here is redundant.
after_save
callbackI always recommend people not to use Rails callbacks but if you DO have to, use
after_commit
instead.
A lot of people has written about this, see what Justin Weiss said.
In the example below, if you invoke User.foo
without preloading
lib/user/bar.rb
, your code blows up (probably in development environment) with a constant
not found exception.
# app/models/user.rb
class User
def foo
Bar.xyz
end
end
# lib/user/bar.rb
class User
class Bar
def xyz
"foot"
end
end
end
But why, didn’t I already define User::Bar
under lib
directory?
The reason is because Rails treats all missing constants as TOP_LEVEL
constants. So when User#foo
, since Bar
is not yet defined, Rails will look
up in app/models/bar.rb
and lib/bar.rb
. That’s why Bar
(of course) cannot be found.
To fix this you can explicitly tell Rails its parent module/class.
# app/models/user.rb
class User
def foo
::User::Bar.xyz
end
end
How to avoid N+1 query in Rails? Typically this is how many people will do.
User.all.includes(:posts)
But do you know how on earth it works?
First ActiveRecord will fetch all users, then map them to ActiveRecord::Base objects and hold them in memory.
SELECT * FROM `users`;
Then it fetches all posts related to those user IDs, and holds them in memory.
SELECT * FROM `posts` WHERE `posts`.`user_id` = [1, 2, 3, 4, 5, 6, 7, 8, ..., N]
Then it automatically does some “posts-to-user” mapping using Ruby Enumerator.
For instance, here’s the pseudo code of how includes
works.
users = User.all
posts = Post.where(user_id: users.pluck(:id))
users.each do |user|
user.posts = posts.select { |post| post.user_id == user.id }
end
This works seamlessly most of the time, but when you reach 100_000 users with 1_000 posts each, this strategy might not be so efficient, as it consumes up a great deal of memory. (Yeah, if you’re gonna debate how cheap hardware is, go convince your boss!)
depend_on
Probably you’re using SASS @import
instead of traditional //= require
to import
CSS sub-files. To know why @import
is preferable, see @iain’s comment below.
// app/assets/stylesheets/application.css
@import "users/index";
@import "users/show";
@import "foo";
@import "bar";
But you will find that your application.css
is not re-precompiled after you made some
changes to the sub-files e.g. users/index
, foo
, etc.
That is because of Rails Sprockets’ underlying caching mechanism, which is supposed to
re-precompile your asset if the file has literally changed. On top of that, the caching
framework also observes all dependent files to invalidate cache accordingly. However,
the mechanism doesn’t work with SASS’s @import, but its own require
directive.
To fix the cache invalidation problem above, you need to explicitly declare which files
Sprockets should observe, by using Sprockets offered depend_on
directive.
// app/assets/stylesheets/application.css
//= depend_on "users/index";
//= depend_on "users/show";
//= depend_on "foo";
//= depend_on "bar";
@import "users/index";
@import "users/show";
@import "foo";
@import "bar";
Good understanding on your tool’s pros/cons is one of the essential things to be a good programmer.
Vừa rồi mình đi Euruko 2017 ở Budapest, một số bài nói cũng khá thú vị nên mình sẽ note lại ở đây.
A few days ago I encountered a strange behavior of Bundler so this post notes down how my experience with it was.
We can’t deny the contribution RVM gemset gave up to the Ruby community, but do we really need gemsets to isolate our project dependencies these days?