Search By Label
class User def discounted_plan_price(discount_code) coupon = Coupon.new(discount_code) coupon.discount(account.plan.price) end end
account.plan.price
above violates the Law of Demeter by invoking price
on the return value of plan
. The price
method is not a method on User
, its parameter discount_code
, its instantiated object coupon
or its direct component account
.class User def discounted_plan_price(discount_code) account.discounted_plan_price(discount_code) end end class Account def discounted_plan_price(discount_code) coupon= Coupon.new(discount_code) coupon.discount(plan.price) end end
delegate
class method:class User delegate :discount_plan_price, to: :account end
<%= button_to "Delete", [quote, line_item_date] %>
<%= button_to "Delete", quote_line_item_date_path(quote, line_item_date) %>
redirect_to @quote
redirect_to quote_path(@quote)
rails new jwt_rails_api --api
gem 'jwt', '~> 2.7' gem "bcrypt", "~> 3.1.7"
User
and Product
Modelsrails g model User username:string password:string rails g model Product name:string description:text
rails db:migrate
, our setup with models is complete, and our schema, found in db/schema.rb
, should now look similar to this:ActiveRecord::Schema[7.0].define(version: 2024_03_26_224534) do create_table "products", force: :cascade do |t| t.string "name" t.text "description" t.datetime "created_at", null: false t.datetime "updated_at", null: false end create_table "users", force: :cascade do |t| t.string "username" t.string "password_digest" t.datetime "created_at", null: false t.datetime "updated_at", null: false end end
jwt
Gem Wrapperapp/lib/json_web_token.rb
and looks like this:class JsonWebToken JWT_SECRET = Rails.application.secrets.secret_key_base def self.encode(payload, exp = 12.hours.from_now) payload[:exp] = exp.to_i JWT.encode(payload, JWT_SECRET) end def self.decode(token) body = JWT.decode(token, JWT_SECRET)[0] HashWithIndifferentAccess.new(body) end end
data = {"name"=>"Juanequex"} JsonWebToken.encode(data) # => "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiQXBwU2lnbmFsIiwiZXhwIjoxNjg1NDI0MjI5fQ.zWJyFHH8Pa6phBOU99XgtRntyfZQSOTX4TdwOxFY9gY" JsonWebToken.decode(JsonWebToken.encode(data)) # => {"name"=>"Juanequex", "exp"=>1685424262}
User
ModelUser
model at app/models/user.rb
. All we need to do here is add the has_secure_password
class method:class User < ApplicationRecord has_secure_password end
User.create(username: "juanequex", password: "password") Product.create(name: "Rad Ruby", description: "A book collection of Ruby tips")
ApplicationController
at app/controllers/application_controller.rb
:class ApplicationController < ActionController::API before_action :authenticate rescue_from JWT::VerificationError, with: :invalid_token rescue_from JWT::DecodeError, with: :decode_error private def authenticate authorization_header = request.headers['Authorization'] token = authorization_header.split(" ").last if authorization_header decoded_token = JsonWebToken.decode(token) User.find(decoded_token[:user_id]) end def invalid_token render json: { invalid_token: 'invalid token' } end def decode_error render json: { decode_error: 'decode error' } end end
AuthenticationController
to which users can send requests and get a signed JSON Web Token from our server. This controller should be placed at app/controllers/authentication_controller.rb
and may look like this:class AuthenticationController < ApplicationController skip_before_action :authenticate def login user = User.find_by(username: params[:username]) authenticated_user = user&.authenticate(params[:password]) if authenticated_user token = JsonWebToken.encode(user_id: user.id) expires_at = JsonWebToken.decode(token)[:exp] render json: { token:, expires_at: }, status: :ok else render json: { error: 'unauthorized' }, status: :unauthorized end end end
rails g controller Product index
so we have something like:class ProductsController < ApplicationController before_action :authenticate def index @products = Product.all render json: @products end end
config/routes.rb
should look something like:Rails.application.routes.draw do post 'login', to: "authentication#login" get 'products', to: "products#index" end
curl -H "Content-Type: application/json" -X POST -d '{"username":"manny","password":"password"}' http://localhost:3000/login
{"error":"unauthorized"}
curl -H "Content-Type: application/json" -X POST -d '{"username":"juanequex","password":"password"}' http://localhost:3000/login
{"token":"eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJleHAiOjE2ODU0NTEyMTR9.1UEYAbmFOSF93yp9pJqNEzkdHr3rVqutPNZWRIPDYkY","expires_at":1685432077}
curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJleHAiOjE2ODU0NTEyMTR9.1UEYAbmFOSF93yp9pJqNEzkdHr3rVqutPNZWRIPZYkY" http://localhost:3000/products
{"decode_error":"decode error"}
[{"id":1,"name":"Rad Ruby","description":"A book collection of Ruby tips","created_at":"2024-03-26T19:33:30.826Z","updated_at":"2024-03-26T19:33:30.826Z"}]
find_or_initialize_by
method is a convenient way to find a record in a database based on certain conditions and, if not found, initialize a new instance of the model with those conditions set.# Example usage @user = User.find_or_initialize_by(email: 'example@example.com')
I18n
and incorporating specific HTML
instructions, such as the requirement to set a string in bold, we follow this approach:# config/locales/en.yml en: homepage: services: title: "This is the <strong>example</strong>"
html_safe
method in views like:<h1><%= t(:title, scope: %w[homepage services]).html_safe %></h1>
HTML
tag can work effectively.:back
symbol in Rails is often used to generate a URL that represents the previous page or location the user came from. It's commonly utilized in conjunction with methods like redirect_to
redirecting users back to where they were before performing a specific action.redirect_to
you can use :back
with the redirect_to
method to send the user back to the previous page:redirect_to :back
:back
as the path in a link to create a "Back" button or link:<%= link_to 'Back', :back, class: 'btn btn-primary' %>
--force
to skip prompts.rails db:system:change --to=postgresql
export EDITOR=
bundle open
wicked_pdf
gem is:template: 'folder/file', formats: [:html]
bin/rails db:environment:set RAILS_ENV="YOUR_ENVIRONMENT"
'/'
even though you have another enable route '/welcome'
.app/controllers/application_controller.rb
:def after_sign_in_path_for(resource) welcome_path end