What is a bundler?
Bundler is a dependency management tool for Ruby which is available as a gem that can be installed through the RubyGems package manager which comes built-in using the following command:
1gem install bundler
Bundler reads the Gemfile for the list of gems and ensures that the gems you need are present in development, staging, and production. It also fetches the metadata from the source provided, resolves the dependencies of each gem, installs them and then requires them while booting.
Without bundler, we would have to handle the installation and manage the dependencies manually.
Let's take a brief look at how Bundler works with Gemfile and Gemfile.lock for dependency management.
Don't use sudo bundle install
In any system, using sudo to install 3rd party packages or apps is not a good idea from a security perspective. When we are installing 3rd party packages those packages have the ability to run some scripts. If we install a package with sudo then we are giving the admin permission to the external package and now this external package can run any malicious scripts on your machine.
It can also mess up the permissions for the system. While installing the package it can update the machine's settings or permissions just to install the given package. This can lead us to endless permission issues afterward.
The same is the case with npm installations. We should not use sudo while installing a package with npm. So if we give sudo privileges while installing a 3rd party package, then it's a security issue and it's a hacky solution. We should dig deeper and fix the underlying issue rather using sudo in the commands.
If you have already used the sudo bundle install and now facing permission issues, then as a possible solution, you can fully uninstall ruby and its dependencies and then install ruby using rbenv again as mentioned in this section. Now change the directory to the project with Gemfile and run bundle install. Now ideally it should be finding the Gemfile and install the gems automatically under the currently logged in user's scope.
Gemfile and Gemfile.lock
Gemfile is a Ruby file which contains a list of gem dependencies of a project. When you install the gems using the command bundle install, bundler looks for the Gemfile at either the directory mentioned by the environment variable BUNDLE_GEMFILE or at the root of the project directory.
Bundler then makes use of RubyGems to install the gems listed on the Gemfile along with their dependencies and it creates a file called Gemfile.lock with the list of the gems installed along with their respective versions.
When you run bundle install the next time, bundler will read the Gemfile.lock and use RubyGems to install the exact versions of the gems specified in Gemfile.lock.
Gemfile.lock gets updated every time you install, update or remove a gem from the list of dependencies of your project.
bundle exec command
In this section we will learn why it is preferred to prefix commands with bundle exec while running them.
Sometimes RubyGems generates executables after installing a gem. Examples of these gems include rails, rake, rspec, etc.
Now, consider a scenario where multiple versions of the same gem are installed. In such a case multiple versions of executables are present for the same gem.
Let us take the example of rake gem. Suppose your project's Gemfile contains v10.4.0 of the rake gem whereas v10.4.2 of the same is installed on your system.
If you were to run the rake command in this case, you would get an error, like so:
1rake db:migrate 2rake aborted! 3Gem::LoadError: You have already activated rake 10.4.2, but your Gemfile requires rake 10.4.0. Prepending `bundle exec` to your command may solve this.
When rake gets called in the above example, there is nothing to ensure that the right version of rake gets activated. In fact, RubyGems will simply activate the latest rake version even if your project depends on an older version.
If something like this happens, you shouldn't remove the incompatible system-wide rake gem to solve this issue. It will lead to other errors in case other projects or tools are dependent on that version.
Additionally, there is no guarantee that simply updating the rake version in your project will fix this issue. Other gems in your project may not be compatible with the updated version.
The solution to this is to use the bundle exec command. bundle exec allows us to run an executable script in the specific context of the project's bundle.
For example, the error encountered in the above example can be fixed using the following command:
1bundle exec rake db:migrate
Upon running the above command, bundle exec will run the executable script for rake version specified in project's Gemfile thus avoiding any conflicts with other versions of rake installed system-wide.
There is however an exception in case of the rails command. The reason being, rails command first loads up the bundler and bundler checks Gemfile.lock for the correct version of command to execute. So if you look into config/application.rb, then you will be able to see the line Bundler.require(*Rails.groups), which does the magic and avoids dependency of prefixing the rails command with bundle exec.
So it's safe to say running bundle exec rails and simply rails is similar. But this is only specific to the rails command.
Running bundle exec and Bundler.require at the same time is not a problem. Thus it's safe to use bundle exec with all commands, even when not needed as long as there's a Gemfile in that directory, it won't activate the gems twice. If there are any performance differences between the two invocations, then it's negligible.
We advocate to use bundle exec at all times. It's a good practice and can save your unnecessary overhead in the long run.
Note that, in this chapter we have used two distinct terms bundle and bundler. Don't let this confuse you. bundler is a gem which whereas bundle is a command.
Although in some places you may notice bundler being used as a command in place of bundle. There is no difference and both bundler and bundle have the same functionality when used in commands.
Updating gems manually
Suppose you want to update a gem that you are using in your application to another version, to do so you should use the following command:
1bundle update gem-name
For example, consider the following from Gemfile.lock in the Granite application:
1rack (2.2.3) 2 rack-proxy (0.7.0) 3 rack 4 rack-test (1.1.0) 5 rack (>= 1.0, < 3)
It seems like the rack gem is being used by the Granite application directly and also as a dependency by other gems. If you wish to update the rack gem then you must only do so for the rack version which is being used by the application. If you update the rack version for rack-test gem as well then it can cause compatibility issues within rack-test gem's functionality.
Hence the correct way to update the rack gem would be like so:
1bundle update rack
In Rails, generators are simply scripts that use templates to create boilerplate code and improve your workflow saving you a quite a bit of time.
For example, when you create a new Rails application you are in fact using a Rails generator.
In the next section we will take a look at how we can use generators to create models, controllers etc.
Rails generate command
Rails includes a lot of generators by default such as model generator, controller generator etc.
You can get a list of all default as well as custom generators available in a Rails project using the following command:
1bundle exec rails generate 2 3Usage: rails generate GENERATOR [args] [options] 4 5General options: 6 -h, [--help] # Print generator's options and usage 7 -p, [--pretend] # Run but do not make any changes 8 -f, [--force] # Overwrite files that already exist 9 -s, [--skip] # Skip files that already exist 10 -q, [--quiet] # Suppress status output 11 12Please choose a generator below. 13 14Rails: 15 application_record 16 assets 17 benchmark 18 channel 19 controller 20 generator 21 helper 22 integration_test 23 jbuilder 24 job 25 mailbox 26 mailer 27 migration 28 model 29 resource 30 scaffold 31 scaffold_controller 32 system_test 33 task 34 35ActiveRecord: 36 active_record:application_record 37 38FactoryBot: 39 factory_bot:model 40 41Pundit: 42 pundit:install 43 pundit:policy 44 45React: 46 react:component 47 react:install 48 49Rspec: 50 rspec:policy 51 52Sidekiq: 53 sidekiq:worker 54 55TestUnit: 56 test_unit:channel 57 test_unit:generator 58 test_unit:install 59 test_unit:mailbox 60 test_unit:plugin 61 test_unit:policy
You can also use g as an alias for generator in the above command, like so:
1bundle exec rails g
To get more information about what a generator can do, you can add --help or -h to the generate command like so:
1bundle exec rails generate generator_name --help
Make sure to replace generator_name in the above command with an appropriate generator name.
In the next section we will see how we can use the generate command to generate boilerplate code for migrations.
Working with migration generators
Rails ships with a migration generator out of the box. Before using the migration generator let's take a look at how to use it.
Run the following command to get more information on how to use the migration generator:
1bundle exec rails generate migration --help
Running the above command will fetch the following output:
1Usage: 2 rails generate migration NAME [field[:type][:index] field[:type][:index]] [options] 3 4Options: 5 [--skip-namespace], [--no-skip-namespace] # Skip namespace (affects only isolated engines) 6 [--skip-collision-check], [--no-skip-collision-check] # Skip collision check 7 -o, --orm=NAME # ORM to be invoked 8 # Default: active_record 9 10ActiveRecord options: 11 [--timestamps], [--no-timestamps] # Indicates when to generate timestamps 12 # Default: true 13 [--primary-key-type=PRIMARY_KEY_TYPE] # The type for primary key 14 --db, [--database=DATABASE] # The database for your migration. By default, the current environment's primary database is used. 15 16Runtime options: 17 -f, [--force] # Overwrite files that already exist 18 -p, [--pretend], [--no-pretend] # Run but do not make any changes 19 -q, [--quiet], [--no-quiet] # Suppress status output 20 -s, [--skip], [--no-skip] # Skip files that already exist 21 22Description: 23 Generates a new database migration. Pass the migration name, either 24 CamelCased or under_scored, and an optional list of attribute pairs as arguments. 25 26 A migration class is generated in db/migrate prefixed by a timestamp of the current date and time. 27 28 You can name your migration in either of these formats to generate add/remove 29 column lines from supplied attributes: AddColumnsToTable or RemoveColumnsFromTable 30 31Example: 32 `bin/rails generate migration AddSslFlag` 33 34 If the current date is May 14, 2008 and the current time 09:09:12, this creates the AddSslFlag migration 35 db/migrate/20080514090912_add_ssl_flag.rb 36 37 `bin/rails generate migration AddTitleBodyToPost title:string body:text published:boolean` 38 39 This will create the AddTitleBodyToPost in db/migrate/20080514090912_add_title_body_to_post.rb with this in the Change migration: 40 41 add_column :posts, :title, :string 42 add_column :posts, :body, :text 43 add_column :posts, :published, :boolean 44 45Migration names containing JoinTable will generate join tables for use with 46has_and_belongs_to_many associations. 47 48Example: 49 `bin/rails g migration CreateMediaJoinTable artists musics:uniq` 50 51 will create the migration 52 53 create_join_table :artists, :musics do |t| 54 # t.index [:artist_id, :music_id] 55 t.index [:music_id, :artist_id], unique: true 56 end
According to the description of the migration generator, this command accepts a migration name and an optional list of arguments. To understand this better let's consider a hypothetical example of adding a priority column to the task table which will accept integer values.
Following command is only to present an example for generating a migration. Do not run this command as it is not required for the Granite application.
You can use the following command to generate the migration:
1bundle exec rails generate migration AddPriorityToTask
Running the above command will generate the following migration:
1class AddPriorityToTask < ActiveRecord::Migration[7.0] 2 def change 3 end 4end
The generated migration file contains the boilerplate code for a migration with along with the change method.
Now it is up to you to add the relevant code which will add a new column to the tasks table. We shall not be discussing that here. If you wish to understand how that works you can refer to Rails migration and Rails migrations in depth chapters.
If you recall, the description for migration generator also mentioned that the command accepts arguments other than the migration name. We can pass the attribute name and attribute type to the migration generator command in the form of add_column_name_to_table_name column_name:type to pre-populate the change method.
The following command will generate a migration with pre-populated change method:
1bundle exec rails d migration AddPriorityToTask priority:integer
The above command will generate the following migration file:
1class AddPriorityToTask < ActiveRecord::Migration[7.0] 2 def change 3 add_column :tasks, :priority, :integer 4 end 5end
That was nice! Rails inferred the column name and table name from the migration name itself and generated the code for that. Not just that, it even inferred the column type from the command.
You can use other generators in a similar manner. For example, you can use the controller generator to generate a controller and if you pass in the correct arguments, Rails will pre-populate the generated controllers with actions.
Play around with the available generators to learn how they work and if you ever get stuck, pass the --help flag to see how a particular generator works.
So far we have seen how to use generators to generate a single migration or a controller. Now, let us see how we can even generate multiple files using the scaffold generator.
A scaffold in Rails is a full set of model, database migration for that model, controller to manipulate it, views to view and manipulate the data, and a test suite for each of the above.
The following command will generate a scaffold for a single resource called Task:
1bundle exec rails generate scaffold Task title:string 2 invoke active_record 3 create db/migrate/20210917025409_create_tasks.rb 4 create app/models/task.rb 5 invoke test_unit 6 create test/models/task_test.rb 7 invoke resource_route 8 route resources :tasks 9 invoke scaffold_controller 10 create app/controllers/tasks_controller.rb 11 invoke erb 12 create app/views/tasks 13 create app/views/tasks/index.html.erb 14 create app/views/tasks/edit.html.erb 15 create app/views/tasks/show.html.erb 16 create app/views/tasks/new.html.erb 17 create app/views/tasks/_form.html.erb 18 invoke resource_route 19 invoke test_unit 20 create test/controllers/tasks_controller_test.rb 21 create test/system/tasks_test.rb 22 invoke helper 23 create app/helpers/tasks_helper.rb 24 invoke test_unit 25 invoke jbuilder 26 create app/views/tasks/index.json.jbuilder 27 create app/views/tasks/show.json.jbuilder 28 create app/views/tasks/_tak.json.jbuilder 29 invoke assets 30 invoke scss 31 create app/assets/stylesheets/tasks.scss 32 invoke scss 33 create app/assets/stylesheets/scaffolds.scss
A scaffold generator can be used to speed up the development process.
It is worth mentioning the -p flag here. If you add this to the command it will simply do a test run and show you what files will be generated without actually generating them, like so:
1bundle exec rails generate scaffold Task title:string -p
If everything looks good, run the command again without the -p flag.
Under the hood, scaffold generator invokes different generators separately to generate the files. Since each generator has a single responsibility, they are easy to reuse, avoiding code duplication.
For instance, the scaffold generator invokes the scaffold_controller generator, which invokes erb, test_unit and helper generators.
This allows us to add/replace/remove any of those invocations. Let's see how we can customize a scaffold generator as per our requirements.
Suppose we do not want to generate the app/assets/stylesheets/scaffolds.scss and test fixture files when scaffolding a new resource. We can do so by updating config/application.rb with the following lines of code:
1config.generators do |g| 2 g.test_framework :test_unit, fixture: false 3 g.scaffold_stylesheet false 4end
Rails destroy command
Suppose you wish to undo the changes introduced by a generator. Manually going through all the changes and undoing them will be very time consuming and you might end up changing something you didn't intend do.
Rails comes with an easy solution for this. You can reverse the changes introduced by a generator using the destroy command, like so:
1bundle exec rails d scaffold Task title:string
You can also use the destroy command to delete certain files. You just need to pass the relative path of the file to the command.
You can replace destroy with the alias d in the above command.
Rails command line executable
In this chapter we have so far discussed the generate and destroy commands in Rails. There are a few more commands that are absolutely critical to your everyday usage of Rails.
Let's take a brief look at those commands.
- bundle exec rails new app_name
This command generates a new Rails application. It creates the entire Rails directory structure with all the code you need to run a simple application right out of the box.
- bundle exec rails server
This command launches a local development server named Puma which comes bundled with Rails. You'll use this any time you want to access your application through a web browser.
You can also replace server with the alias s to launch the server.
- bundle exec rails console
The console command lets you interact with your Rails application from the command line. It uses IRB under the hood.
You can also replace console with the alias c to invoke the console.
- bundle exec rails test
This command lets you run the tests you have added in your Rails project. You can either all the tests at once, like so:
1bundle exec rails test -v
Or you can run tests from a specific test file by passing the relative path of the test file to the above command, like so:
1bundle exec rails test -v test/models/user_test.rb
You can also replace test with the alias t to run the tests.
Passing the -v flag in above command is completely optional. Doing so generates an output with higher verbosity.
- bundle exec rails db:create
When you create your Rails application for the first time, it will not have a database yet. You will need to make sure the database is up and running before implementing the CRUD features.
This command lets you create a database if it doesn't already exist.
- bundle exec rails db:migrate
Every time you create a database migration that adds or deletes a row or a column, creates a new table, you have to run this command for the changes to reflect in your database.
- bundle exec rails routes
This command will list all of your defined routes, giving you a good overview of the URLs in your application.
Creating a Rails app from a specific Rails commit
Suppose the latest Rails release was 10 days ago and you want to use the Rails version from a commit different than the latest release. To do so you can update the rails gem line within Gemfile like so:
1gem "rails", git: "git://github.com/rails/rails.git", ref: commit_id
Replace the commit_id with the commit id you wish to use. For example if the commit id is a8d088f, then update the gemfile like so:
1gem "rails", git: "git://github.com/rails/rails.git", ref: "a8d088f"
You also have an option to pass the branch along with the commit id. If you do not pass the branch name then the default branch would be main. If you don't pass a commit id then the Rails version from latest commit in the main branch will be used.
This is an in-depth chapter and hence you do not need to commit any of these changes.