DEV Community

Cover image for Rails tricks you may not know
Pavel Tkachenko
Pavel Tkachenko

Posted on

Rails tricks you may not know

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' 
Enter fullscreen mode Exit fullscreen mode

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] 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode
# Views structure app/views/layouts/admin.html.erb app/views/posts/ app/views/admin/posts/ app/views/admin/users/ 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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)