My notes from the Rails tutorial

26 min. read

These are some notes I took some time ago while I was going through the Rails tutorial. There are almost no notes for the first chapters, which I think is due to the fact that I was already familiar with the concepts. I talked about my first experience with Rails here. After that, I paired with other developers at my job to work in some huge Rails apps. But I still wanted to publish these notes because they came in handy.

Ok, here are my old notes

  • To run the Rails server in cloud 9 from the command line:
    
    $ rails server -b $IP -p $PORT
    
  • We take the sqlite3 gem out of the way in the Gemfile, which prevents potential conflicts with the database used by Heroku (Heroku uses Postgres). To achieve that, we can use the group reserved word:
    
    group :development, :test do
      gem 'sqlite3', '1.3.9'
    end
    
    group :production do
      gem 'pg', '0.17.1'
    end
    

    I'll probably use postgres in both my Rails project and Heroku, otherwise I think I'll find it confusing if errors happen, but I'll go with the book.

  • I just learnt a new git way to undo changes! It is:
    
    $ git checkout -f
    

    I would normally just use git stash followed by git stash drop. Good to know that I can do it in a single command!

  • Type:
    
    $ bundle install --without production
    

    to create the right Gemfile.lock, in this case the one to be used in development and tests.

  • To install heroku in your local system (cloud 9 has it already installed) use Heroku Toolbelt.
  • This is a note to self: when you do $ heroku create, take note of the link. By the way, I love the default names Heroku associates to your sites, so far I've got "still brook" and "powerful sierra".
  • Regarding the REST verbs PUT and PATCH, earlier versions of Rails used PUT for data updates, but PATCH is the more appropriate method according to the HTTP standard.
  • I didn't know that you could have an .irbrc configuration file for irb, the Ruby interpreter, but if you think about it, it makes sense that you can. It's all in the irb docs. I normally prefer pry because I find it more powerfull, but this is good to know! Here is what I used at cloud 9:
    
    $ more ~/.irbrc
    IRB.conf[:PROMPT_MODE] = :SIMPLE
    IRB.conf[:AUTO_INDENT_MODE] = false
    
  • You can do class Word < String to add new methods to a core Ruby class. Although I don't like the idea of modifying the core classes.
  • Rails serves css, js, and img all from just "assets" (assets pipeline).
  • This:
    
    get 'help' => 'static_pages#help'
    

    Creates:

    
    help_url  -> 'http://www.example.com/help'
    help_path -> '/help'
    
  • You can generate an integration test which basically will create a new class that inherits from the right class.
    
    $ rails generate integration_test site_layout
    
  • You can run rake only for integration tests adding the flag test:integration. But now that we talk about "flags", my favourite one is TESTOPTS='--pride', which will change your way of running tests forevah:
    
    $ be rake test:integration TESTOPTS='--pride'
    

    See it in action:


    I even went so far as to add an alias in my .bash_aliases. I called it beproud, lol:

    
    alias beproud="be bin/rake test TESTOPTS='--pride'"
    
  • If you don't want to make any changes in the database when you work with the console:
    
    $ rails console --sandbox
    
  • @user.update_attributes and @user.update_attribute save to the database, just as @user.update. But @user.update returns the resulting object whether it was saved successfully to the database or not. Therefore, User.update(@user.id, user_avatar_params) does not update the @user variable if you do not make the assignment. @user.update_attributes(user_avatar_params) does change the @user variable implicitly. Also, update_attributes runs the validations but update_attribute doesn't.
  • You can use this command inside rails console to check the error messages:
    
    $ user.errors.full_messages
    
  • Regex for emails: https://www.railstutorial.org/book/modeling_users#_table-valid_email_regex
  • The book users a gem called BCrypt to store passwords securely. To make the password digest, has_secure_password uses a state-of-the-art hash function called bcrypt. It also automatically adds an authenticate method to the corresponding model objects. This method determines if a given password is valid for a particular user by computing its digest and comparing the result to password_digest in the database.
    
    user = User.find_by(email: "mhartl@example.com")
    user.authenticate("not_the_right_password")
    
  • Don't forget to do this:
    
    $ heroku run rake db:migrate
    $ heroku run console --sandbox
    

    and create a user in the heroku db.

  • Extract:

    by design, encryption is reversible; the ability to encrypt implies the ability to decrypt as well. In contrast, the whole point of calculating a password’s hash digest is to be irreversible, so that it is computationally intractable to infer the original password from the digest. By design, the bcrypt algorithm produces a salted hash, which protects against two important classes of attacks (dictionary attacks and rainbow table attacks).

ENVIRONMENTS

The default environment for the Rails console is development:


$ rails console
  Loading development environment
$ rails console test
  Loading test environment

Development is the default environment for the Rails server:


$ rails server --environment production

If you view your app running in production, it won’t work without a production database, which we can create by running $ rake db:migrate in production:


$ bundle exec rake db:migrate RAILS_ENV=production

Naturally, since Heroku is a platform for production sites, it runs each application in a production environment.


$ heroku run console
  >> Rails.env
  => "production"
  • In order to make a user profile page, we need to have a user in the database, which introduces a chicken-and-egg problem: create it with the console (it will be the admin user):
    
    $ User.create(name: "admin", email: "admin@example.com", password: "foobar", password_confirmation: "foobar")
    
  • Debugging can be done with byebug adding "debugger" (like pry.binding when using pry). It will open a debug console in the terminal where you are running the rails server. when you are finished, Ctrl + D and remove the line debugger.
  • By default, methods defined in any helper file are automatically available in any view.
  • To empty the development db: bundle exec rake db:migrate:reset.
  • the form_for() helper method takes in an Active Record object and constructs a form using the object’s attributes. So we have to:
    
      def new
        @user = User.new  # <-- this line
      end
    
  • Common Rails convention: using a dedicated app/views/shared/ directory for partials expected to be used in views across multiple controllers.
  • Understanging the Rails authenticity token.
  • Don't forget to add SSL (Secure Sockets Layer). In config/environments/production.rb:
    
    Rails.application.configure do
      # Force all access to the app over SSL, use Strict-Transport-Security,
      # and use secure cookies.
      config.force_ssl = true
    end
    
  • Setting up a production site to use SSL involves purchasing and configuring an SSL certificate for your domain:
    https://devcenter.heroku.com/articles/ssl-endpoint
  • WEBrick isn’t suitable for production use:
    https://devcenter.heroku.com/articles/ruby-default-web-server
    https://devcenter.heroku.com/articles/deploying-rails-applications-with-the-puma-web-server
  • Flash messages are shown in the next page. For example, in the create action:
    
    flash[:success] = "Welcome to the Sample App!"
    redirect_to @user
    

    will show the message in the show page.

  • HTTP is stateless, so we use cookies. A session is a semi-permanent connection between two computers, and is RESTful (new, create, destroy, etc.)

    - Forget user on browser close: session

    - Persist user on browser close: cookies

    - Choose to remember me: both

  • Temporary sessions are encrypted, cookies are not and can be hijacked.
  • resources :users creates EVERYTHING (new, create, etc.)
  • If you have a model, active record provides errors (@modelname.errors). If you don't have a model, use flash instead.
    
    If we have a model: form_for(@user)
    If not:             form_for(:session, url: login_path)
    
  • Run just one test file:
    
    $ bundle exec rake test TEST=test/integration/users_login_test.rb
    

    Run one test method:

    
    $ bundle exec rake test TEST=test/integration/users_login_test.rb TESTOPTS="--name test_login_with_valid_information"
    
  • User.find(session[:user_id]) raises an exception, but the user can surf the site not logged so: User.find_by(id: session[:user_id]) this just returns nil if not logged.
  • Extract from 8.4.1:

    There are four main ways to steal cookies:

    (1) using a packet sniffer to detect cookies being passed over insecure networks,15

    (2) compromising a database containing remember tokens,

    (3) using cross-site scripting (XSS), and

    (4) gaining physical access to a machine with a logged-in user.

    We prevented the first problem in Section 7.5 by using Secure Sockets Layer (SSL) site-wide, which protects network data from packet sniffers. We’ll prevent the second problem by storing a hash digest of the remember token instead of the token itself, in much the same way that we stored password digests instead of raw passwords in Section 6.3. Rails automatically prevents the third problem by escaping any content inserted into view templates. Finally, although there’s no iron-clad way to stop attackers who have physical access to a logged-in computer, we’ll minimize the fourth problem by changing tokens every time a user logs out and by taking care to cryptographically sign any potentially sensitive information we place on the browser.

  • On a production site with significant traffic:
    
    $ heroku maintenance:on
    $ git push heroku
    $ heroku run rake db:migrate
    $ heroku maintenance:off
    

    This arranges to show a standard error page during the deployment and migration.

  • When constructing a form using form_for(@user), Rails uses POST if @user.new_record? is true and PATCH if it is false.
  • Quoting:

    the use of target="_blank" in the Gravatar link is a neat trick to get the browser to open the page in a new window or tab, which is convenient behavior when linking to a third-party site.

    I removed it because of this (old knowledge).

  • Still don't understand this: http://stackoverflow.com/questions/36575977/update-action-with-empty-password
  • Authentication allows us to identify users of our site. Authorization lets us control what they can do.
  • Redirects don’t happen until an explicit return or the end of the method, so any code appearing after the redirect is still executed. MINDBLOWN.
  • Clear the db and fill it with our seeds:
    
    $ bundle exec rake db:migrate:reset
    $ bundle exec rake db:seed
    

    And in Heroku we need to do:

    
    $ heroku pg:reset DATABASE
    $ heroku run rake db:migrate
    $ heroku run rake db:seed
    $ heroku restart
    
  • Running render @users automatically calls the _user.html.erb partial on each user in the collection.
  • user.toggle!(:admin) swaps the boolean value of admin.
  • Web browsers can’t send DELETE requests natively, so Rails fakes them with JavaScript. This means that the delete links won’t work if the user has JavaScript disabled. If you must support non-JavaScript-enabled browsers you can fake a DELETE request using a form and a POST request, which works even without JavaScript.
  • To avoid anybody not admin to delete users:
    - use permit so that they can't delete through the web
    - use a before_action filter so that they can not delete through the command line
  • The way to set a query parameter in Rails is to include a hash in the named route:
    
    edit_account_activation_url(@user.activation_token, email: @user.email)
    

    will produce:

    
    account_activations/q5lt38hQDc_959PVoo6b7A/edit?email=foo%40example.com
    
  • To run only the mailer tests:
    
    $ bundle exec rake test:mailers
    
  • Use:
    
    hidden_field_tag :email, @user.email
    

    instead of:

    
    f.hidden_field :email, @user.email
    

    because the reset link puts the email in params[:email], whereas the latter would put it in params[:user][:email].

  • This:
    
    update_attribute(:activated,    true)
    update_attribute(:activated_at, Time.zone.now)
    

    can be

    
    update_columns(activated: true, activated_at: Time.zone.now)
    
  • Instead of Micropost.create, user.microposts.create is the idiomatically correct way to make a micropost, namely, through its association with a user.
  • default_scope -> { order(created_at: :desc) } is the "stabby lambda" syntax for an object called a Proc (procedure) or lambda, which is an anonymous function (a function created without a name). The stabby lambda -> takes in a block and returns a Proc, which can then be evaluated with the call method. We can see how it works at the console:
    
    >> -> { puts "foo" }
    => #<Proc:0x007fab938d0108@(irb):1 (lambda)>
    >> -> { puts "foo" }.call
    foo
    => nil
    
  • create! will raise an exception on failure, create just returns false.
  • new vs build: If you're creating an object through an association, build should be preferred over new as build keeps your in-memory object, user (in this case) in a consistent state even before any objects have been saved to the database.
  • In Micropost.where("user_id = ?", id) the question mark ensures that id is properly escaped before being included in the underlying SQL query, thereby avoiding a serious security hole called SQL injection.
  • request.referrer || root_url is closely related to the request.url variable used in friendly forwarding, and is just the previous URL (in this case, the Home page). request.referrer corresponds to HTTP_REFERER, as defined by the specification for HTTP. Note that "referer" is not a typo, the word is actually misspelled in the spec. Rails corrects this error by writing "referrer" instead. This is convenient because microposts appear on both the Home page and on the user’s profile page, so by using request.referrer we arrange to redirect back to the page issuing the delete request in both cases.
  • For image uploads:
    
    <%= form_for(@micropost, html: { multipart: true }) do |f| %>
    
  • In contrast to other model validations, file size validation doesn’t correspond to a built-in Rails validator. As a result, validating images requires defining a custom validation.
  • The :validate declaration is used for custom validation where as :validates is used for generic validation like presence, uniqueness etc. on a field. http://stackoverflow.com/a/18141015
  • Note the use of the special fixture_file_upload method for uploading files as fixtures inside tests.
  • THE AUTHOR RECOMMENDED IMAGEMAGICK

    wait... WAIT...

    CarrierWave USES IMAGEMAGICK

    wait... WAIT...

    HEROKU USES IMAGEMAGIC BY DEFAULT

    http://www.imagemagick.org/script/index.php

    head explodes

  • Quote:

    The image uploader we developed uses the local filesystem for storing the images, which isn’t a good practice in production. Instead, we’ll use a cloud storage service to store images separately from our application. file storage on Heroku is temporary, so uploaded images will be deleted every time you deploy. There are many choices for cloud storage, but we’ll use one of the most popular and well-supported, Amazon.com’s Simple Storage Service (S3).

  • Priceless quote:
    
    has_many :followeds, through: :active_relationships
    

    Rails would see "followeds" and use the singular "followed", assembling a collection using the followed_id in the relationships table. But, as noted in Section 12.1.1, user.followeds is rather awkward, so we’ll write user.following instead. Naturally, Rails allows us to override the default, in this case using the source parameter (as shown in Listing 12.8), which explicitly tells Rails that the source of the following array is the set of followed ids.

    
    has_many :following, through: :active_relationships, source: :followed

  • following.include?(other_user)
    looks like it might have to pull all the followed users out of the database to apply the include? method, but in fact for efficiency Rails arranges for the comparison to happen directly in the database. On the contrary, user.microposts.count performs the count directly in the database.
  • Just change form_for to form_for ..., remote: true and Rails automagically uses Ajax in tests. Also, use xhr :post, path instead of post path.

Phew! That's it : )

Comments