I see a lot of new Rails developers struggling with some of the basics of the framework. I've been using Rails for a while now, and I've picked up a few tricks along the way. I thought I'd share these tricks with you.
One action, many methods
Sometimes it is usefull to have one action, but make it work with both get
and post
(or any other). For example, if you have a page where user can generate a report. User requires to select some dates in range, and then click button to generate report. And what I often see is that people create two actions inside the controller:
# app/controllers/reports_controller.rb class ReportsController < ApplicationController def super_report # render form end def super_report_post # generate report end end # config/routes.rb get 'reports/super_report', to: 'reports#super_report' post 'reports/super_report', to: 'reports#super_report_post'
But there is a better way to do this. Choose the style you like:
# config/routes.rb # Option one get 'reports/super_report', to: 'reports#super_report' post 'reports/super_report', to: 'reports#super_report' # Option two match 'reports/super_report', to: 'reports#super_report', via: [:get, :post]
And in controller:
# app/controllers/reports_controller.rb class ReportsController < ApplicationController def super_report if request.post? # generate report else # render form end end end
Now we don't split our logic into two actions, but we have one action that works with both get
and post
. It looks nicer, easier to read and maintain.
Namespacing
Namespacing is a way to group controllers and routes. It is usefull when you have many controllers and you want to group them. For example you have controllers: PostsController
, Admin::UsersController
and Admin::PostsController
. You want different views for admin and want authentication for it.
# config/routes.rb resources :posts namespace :admin do resources :users resources :posts end
Now you can access Admin::UsersController
with url /admin/users
and Admin::PostsController
with url /admin/posts
. You should also use namespace inside the controller:
# app/controllers/admin/users_controller.rb class Admin::BaseController < ApplicationController layout "admin" # special view layout for admins before_action :authenticate_admin! end class Admin::UsersController < Admin::BaseController # ... Only admin can work with it end class Admin::PostsController < Admin::BaseController # ... Only admin can work with it end
# Views structure app/views/layouts/admin.html.erb app/views/posts/ app/views/admin/posts/ app/views/admin/users/
Scoped associations
Let's say we have two models: User
and Post
. And we want to get all posts for user. We can do this:
class User < ActiveRecord::Base has_many :posts end class Post < ActiveRecord::Base belongs_to :user end user = User.first user.posts
But what if we don't ever want to include posts for user which are published? Often I see people doing this:
class User < ActiveRecord::Base has_many :posts end class Post < ActiveRecord::Base belongs_to :user scope :published, -> { where(published: true) } end user = User.first user.posts.published
And this is fine, but you can somehow forget to add published
scope to your query. And then you will get all posts, even unpublished. And this is not what you want. Some people use default_scope
for this, but it is not good idea. So what we can do? We can use scope
in association:
class User < ActiveRecord::Base has_many :posts, -> { published } has_many :not_published_posts, -> { not_published } end class Post < ActiveRecord::Base belongs_to :user scope :published, -> { where(published: true) } scope :not_published, -> { where(published: false) } end user = User.first user.posts # only published posts here
Migrations are not only for schema building
You can use migration to add some data to your database. For example you have model User
and you want to create admin with id -1 to database, so anywhere, in any environment you want to be sure, that such user exists. You can do this:
class AddAdmin < ActiveRecord::Migration ADMIN = User.find_by(id: -1) def up User.create(name: 'Admin', id: -1, admin: true) unless ADMIN end def down ADMIN.destroy if ADMIN end end
Wrapping up
I hope you will find these tricks useful. If you have any other cool examples, please share them in the comments.
P.S. Cover image is generated using DALL·E 2
Top comments (0)