Slide 1

Slide 1 text

Debugging on Rails Mykhaylo Sorochan kiev.rb #2 kiev.rb #2

Slide 2

Slide 2 text

Contents

Slide 3

Slide 3 text

Sample app for the Ruby on Rails Tutorial (Rails 4) https://github.com/railstutorial/sample_app_rails_4 Debugged application

Slide 4

Slide 4 text

debugger gem 'debugger' debugger - call debugger config - .rdebugrc

Slide 5

Slide 5 text

Debug location UserController#show def show @user = User.find(params[:id]) debugger @microposts = @user.microposts.paginate(page: params[:page]) end

Slide 6

Slide 6 text

Debugger commands (rdb:9) help ruby-debug help v1.6.2 Type 'help ' for help on a specific command Available commands: backtrace delete enable help list pry restart source undisplay break disable eval info method ps save start up catch display exit irb next putl set step var condition down finish jump p quit show thread where continue edit frame kill pp reload skip trace

Slide 7

Slide 7 text

Program info (rdb:4) help info Generic command for showing things about the program being debugged. -- List of info subcommands: -- info args -- Argument variables of current stack frame info breakpoints -- Status of user-settable breakpoints info catch -- Exceptions that can be caught in the current stack frame info display -- Expressions to display when program stops info file -- Info about a particular file read in info files -- File names and timestamps of files read in info global_variables -- Global variables info instance_variables -- Instance variables of the current stack frame info line -- Line number and file name of current position in source file info locals -- Local variables of the current stack frame info program -- Execution status of the program info stack -- Backtrace of the stack info thread -- List info about thread NUM info threads -- information of currently-known threads info variables -- Local and instance variables of the current stack frame

Slide 8

Slide 8 text

Listing (rdb:1) help list l[ist] list forward l[ist] - list backward l[ist] = list current line l[ist] nn-mm list given lines NOTE - to turn on autolist, use 'set autolist' -> .rdebugrc

Slide 9

Slide 9 text

Listing: in debug 12 @user = User.find(params[:id]) 13 debugger => 14 @microposts = @user.microposts.paginate(page: params[:page]) 15 end 16

Slide 10

Slide 10 text

Listing: l 23,25 l 23,25 [23, 25] in */sample_app_rails_4/app/controllers/users_controller.rb 23 if @user.save 24 sign_in @user 25 flash[:success] = "Welcome to the Sample App!"

Slide 11

Slide 11 text

Breakpoints (rdb:4) help break b[reak] file:line [if expr] b[reak] class(.|#)method [if expr] set breakpoint to some position, (optionally) if expr == true (rdb:4) help delete del[ete][ nnn...] delete some or all breakpoints

Slide 12

Slide 12 text

Set breakpoint (rdb:4) b ApplicationHelper#full_title Breakpoint 1 at ApplicationHelper::full_title (rdb:4) b 18 Breakpoint 2 file */sample_app_rails_4/app/controllers/users_controller.rb, line 18 (rdb:4) info b Num Enb What 1 y at ApplicationHelper:full_title 2 y at */sample_app_rails_4/app/controllers/users_controller.rb:18

Slide 13

Slide 13 text

Navigate (rdb:4) help step s[tep][+-]?[ nnn] step (into methods) once or nnn times '+' forces to move to another line. '-' is the opposite of '+' and disables the force_stepping setting. (rdb:4) help next n[ext][+-]?[ nnn] step over once or nnn times, '+' forces to move to another line. '-' is the opposite of '+' and disables the force_stepping setting. (rdb:4) help up up[count] move to higher frame

Slide 14

Slide 14 text

Stop at breakpoint (rdb:4) c Breakpoint 1 at ApplicationHelper:full_title [-1, 8] in */sample_app_rails_4/app/helpers/application_helper.rb 1 module ApplicationHelper 2 3 # Returns the full title on a per-page basis. => 4 def full_title(page_title) 5 base_title = "Ruby on Rails Tutorial Sample App"

Slide 15

Slide 15 text

Display (rdb:4) help display disp[lay] add expression into display expression list disp[lay] display expression list (rdb:4) help undisplay undisp[lay][ nnn] Cancel some expressions to be displayed when program stops.

Slide 16

Slide 16 text

Display configured 4 def full_title(page_title) => 5 base_title = "Ruby on Rails Tutorial Sample App" 6 if page_title.empty? (rdb:4) disp 2: controller_name = users 3: base_title =

Slide 17

Slide 17 text

Display changed (rdb:4) n */sample_app_rails_4/app/helpers/application_helper.rb:6 if page_title.empty? 2: controller_name = users 3: base_title = Ruby on Rails Tutorial Sample App [1, 10] in */sample_app_rails_4/app/helpers/application_helper.rb 5 base_title = "Ruby on Rails Tutorial Sample App" => 6 if page_title.empty? 7 base_title

Slide 18

Slide 18 text

Conditional breakpoint (rdb:8) b ApplicationHelper#full_title if page_title=="Alexander" Breakpoint 4 at ApplicationHelper::full_title (rdb:8) c Breakpoint 1 at ApplicationHelper:full_title

Slide 19

Slide 19 text

Variables (rdb:8) help var v[ar] cl[ass] show class variables of self v[ar] co[nst] show constants of object v[ar] g[lobal] show global variables v[ar] i[nstance] show instance variables of object. You may pass object id's hex as well. v[ar] l[ocal] show local variables

Slide 20

Slide 20 text

Show variables 12 @user = User.find(params[:id]) 13 debugger => 14 @microposts = @user.microposts.paginate(page: params[:page]) 15 end 16 17 def new (rdb:12) v i @_action_has_layout = true @_action_name = "show" @_config = {} @_env = {"GATEWAY_INTERFACE"=>"CGI/1.1", "PATH_INFO"=>"/users/1", "QUERY_STRING"=>"",... @_headers = {"Content-Type"=>"text/html"} @_lookup_context = #"show", "controller"=>"users", "id"=>"1"} @_prefixes = ["users", "application"] @_request = #"CGI/1.... @_response = #

Slide 21

Slide 21 text

Remote debugger Client # rdebug --client -h 127.0.0.1 Connected. */sample_app_rails_4/app/controllers/users_controller.rb:14 @microposts = @user.microposts.paginate(page: params[:page]) [9, 18] in */sample_app_rails_4/app/controllers/users_controller.rb 9 end 10 11 def show 12 @user = User.find(params[:id]) 13 debugger => 14 @microposts = @user.microposts.paginate(page: params[:page]) Server Debugger.wait_connection = true Debugger.start_remote

Slide 22

Slide 22 text

pry REPL

Slide 23

Slide 23 text

debugger-pry Call pry from debugger

Slide 24

Slide 24 text

pry-debugger Debugger commands inside pry: step, next, continue, breakpoints

Slide 25

Slide 25 text

jazz_hands jazz_hands is an opinionated set of console- related gems and a bit of glue: pry, awesome_print, hirb, pry-rails, pry-doc, pry-git, pry-remote, pry-debugger, pry-stack_explorer, coolline, coderay

Slide 26

Slide 26 text

pry – jazz_hands 1

Slide 27

Slide 27 text

pry – jazz_hands 2

Slide 28

Slide 28 text

pry – jazz_hands 3

Slide 29

Slide 29 text

pry – jazz_hands 4

Slide 30

Slide 30 text

Logging ActiveSupport::Logger is used for logging Rails.root/log/[environment_name].log

Slide 31

Slide 31 text

Log levels Rails.logger.level | name | level | |----------+-------| | :debug | 0 | | :info | 1 | | :warn | 2 | | :error | 3 | | :fatal | 4 | | :unknown | 5 | development, testing: log_level = 0 (:debug) production: log_level = 1 Messages with equal or higher level are sent to log

Slide 32

Slide 32 text

Write to log logger.debug User.inspect logger.info user.id logger.fatal "Help!"

Slide 33

Slide 33 text

Tagging log messages logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT)) logger.tagged("USER") { logger.info "hello" } => [USER] hello

Slide 34

Slide 34 text

Tagged logging def show @user = User.find(params[:id]) logger.tagged("USER") { logger.info @user.email } # tail -f log/development.log|grep \\\[USER\] [USER] [email protected]

Slide 35

Slide 35 text

Global variables for tracing __FILE__ - file name __LINE__ - line number

Slide 36

Slide 36 text

Some useful CLI commands tail -n 100 tail -f grep, less

Slide 37

Slide 37 text

Querying logs # cat log/development.log|grep GET|tail -n 2 Started GET "/assets/users.js?body=1" for 127.0.0.1 at 2013-11-21 23:32:01 +0200 Started GET "/assets/application.js?body=1" for 127.0.0.1 at 2013-11-21 23:32:01 +0200 # cat log/development.log|grep GET|wc -l 574

Slide 38

Slide 38 text

config.log_formatter Defines the formatter of the Rails logger. Defaults to ActiveSupport::Logger::SimpleFormatter for all modes, production defaults to Logger::Formatter. module ActiveSupport class Logger < ::Logger # Simple formatter which only displays the message. class SimpleFormatter < ::Logger::Formatter # This method is invoked when a log event occurs def call(severity, timestamp, progname, msg) "#{String === msg ? msg : msg.inspect}\n" end end

Slide 39

Slide 39 text

config.log_level Defaults to :debug for all modes, production defaults to :info.

Slide 40

Slide 40 text

config.log_tags See ActionDispatch::Request methods. config.log_tags = [ :fullpath ] [/users/1] Started GET "/users/1" for 127.0.0.1 at 2013-11-... [/users/1] ActiveRecord::SchemaMigration Load (0.1ms) SELECT ... [/assets/application.css?body=1] Started GET "/assets/application.css?body=1" for 127...

Slide 41

Slide 41 text

config.logger Accepts a logger conforming to the interface of Log4r or the default Ruby Logger class. Defaults to ActiveSupport::Logger, with auto flushing off in production mode.

Slide 42

Slide 42 text

Rails console rails c rails c --sandbox irb pry

Slide 43

Slide 43 text

rake routes > all_routes = Rails.application.routes.routes > require 'action_dispatch/routing/inspector' > inspector = ActionDispatch::Routing::RoutesInspector.new(all_routes)

Slide 44

Slide 44 text

All routes > puts inspector.format(ActionDispatch::Routing::ConsoleFormatter.new) Prefix Verb URI Pattern Controller#Action following_user GET /users/:id/following(.:format) users#following followers_user GET /users/:id/followers(.:format) users#followers users GET /users(.:format) users#index POST /users(.:format) users#create new_user GET /users/new(.:format) users#new edit_user GET /users/:id/edit(.:format) users#edit user GET /users/:id(.:format) users#show PATCH /users/:id(.:format) users#update PUT /users/:id(.:format) users#update DELETE /users/:id(.:format) users#destroy sessions POST /sessions(.:format) sessions#create new_session GET /sessions/new(.:format) sessions#new session DELETE /sessions/:id(.:format) sessions#destroy microposts POST /microposts(.:format) microposts#create micropost DELETE /microposts/:id(.:format) microposts#destroy relationships POST /relationships(.:format) relationships#create relationship DELETE /relationships/:id(.:format) relationships#destroy root GET / static_pages#home signup GET /signup(.:format) users#new

Slide 45

Slide 45 text

Routes for controller > puts inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, 'microposts') Prefix Verb URI Pattern Controller#Action microposts POST /microposts(.:format) microposts#create micropost DELETE /microposts/:id(.:format) microposts#destroy

Slide 46

Slide 46 text

Filtering routes: GET > puts inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, 'users').lines.grep(/GET/).join following_user GET /users/:id/following(.:format) users#following followers_user GET /users/:id/followers(.:format) users#followers users GET /users(.:format) users#index new_user GET /users/new(.:format) users#new edit_user GET /users/:id/edit(.:format) users#edit user GET /users/:id(.:format) users#show signup GET /signup(.:format) users#new

Slide 47

Slide 47 text

Filtering routes: /relation/ > puts inspector.format(ActionDispatch::Routing::ConsoleFormatter.new).lines.grep(/relation/).join relationships POST /relationships(.:format) relationships#create relationship DELETE /relationships/:id(.:format) relationships#destroy

Slide 48

Slide 48 text

Matching routes > r = Rails.application.routes > r.recognize_path "/users/47" => {:action=>"show", :controller=>"users", :id=>"47"} > r.recognize_path "/users/87", :method => "PUT" => {:action=>"update", :controller=>"users", :id=>"87"} > r.recognize_path "/users/47.json" => {:action=>"show", :controller=>"users", :id=>"47", :format=>"json"}

Slide 49

Slide 49 text

Named routes > app.users_path => "/users" > app.users_path(:json) => "/users.json" > app.user_path(1) => "/users/1" > app.user_path(1, :xml) => "/users/1.xml" > app.user_path(1, :count => 4) => "/users/1?count=4"

Slide 50

Slide 50 text

Making requests > app => # > app.reset!

Slide 51

Slide 51 text

Get requests > app.get '/users/1/edit' Started GET "/users/1/edit" for 127.0.0.1 at 2013-11-26 23:24:18 +0200 Processing by UsersController#edit as HTML Parameters: {"id"=>"1"} Redirected to http://localhost:3000/signin Filter chain halted as :signed_in_user rendered or redirected Completed 302 Found in 3ms (ActiveRecord: 0.4ms) > app.response.body => "You are being redirected." > app.get_via_redirect '/users/1/edit' Started GET "/users/1/edit" for 127.0.0.1 at 2013-11-26 23:26:44 +0200 Redirected to http://localhost:3000/signin ... Started GET "/signin" for 127.0.0.1 at 2013-11-26 23:26:44 +0200

Slide 52

Slide 52 text

Session cookies > app.cookies => # app.cookies.to_hash => {"_sample_app_session"=>"RC9j... app.cookies - received/sent cookies

Slide 53

Slide 53 text

Post requests: signin > app.response.body.lines.grep /csrf-token/ => ["\n"] > app.post '/sessions', :authenticity_token => 'n+9uCcG2JJmgwhnNcp4s9jTwOU55RAPOdtAHWstcpKQ=', 'session[email]' => '[email protected]', 'session[password]' => '123456' Started POST "/sessions" for 127.0.0.1 at 2013-11-26 23:33:01 +0200 Processing by SessionsController#create as HTML Parameters: {"authenticity_token"=>"n+9uCcG2JJmgwhnNcp4s9jTwOU55RAPOdtAHWstcpKQ=", "session"=>{"email" =>"[email protected]", "password"=>"[FILTERED]"}} Redirected to http://localhost:3000/users/1/edit Completed 302 Found in 281ms (ActiveRecord: 7.2ms) app.post_via_redirect

Slide 54

Slide 54 text

Access to restricted resource > app.get '/users/1/edit' Started GET "/users/1/edit" for 127.0.0.1 at 2013-11-26 23:38:47 +0200 Processing by UsersController#edit as HTML Completed 200 OK in 41ms (Views: 35.7ms | ActiveRecord: 0.8ms)

Slide 55

Slide 55 text

Call helper > helper => # # ApplicationHelper#full_title > helper.full_title "Sign Up" => "Ruby on Rails Tutorial Sample App | Sign Up"

Slide 56

Slide 56 text

Call helper with cookie > def cookies; @cookies ||= HashWithIndifferentAccess.new(app.cookies.to_hash); end > helper.current_user => #

Slide 57

Slide 57 text

ActiveRecord logging > ActiveRecord::Base.logger = Logger.new(STDOUT) > ActiveRecord::Base.clear_active_connections! > # reload! > User.find 1 D, [2013-12-30T21:55:17.775769 #24810] DEBUG -- : User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 1]] => #

Slide 58

Slide 58 text

ActiveRecord hide logging > ActiveRecord::Base.logger = Logger.new(nil) > ActiveRecord::Base.clear_active_connections! > # reload! > User.find 1 => #

Slide 59

Slide 59 text

Raw SQL requests > ActiveRecord::Base.connection.select_all("select * from users") => # puts _ {"id"=>1, "name"=>"hello", "email"=>"[email protected]", "created_at"=>"2013-....

Slide 60

Slide 60 text

.irbrc Save your helper methods ● routes ● sql logging ● etc

Slide 61

Slide 61 text

CLI tools Making HTTP requests with curl ● -s silent ● -v verbose ● -c save cookie ● -b use cookie ● -data POST data ● -data-urlencode URL-encode POST data

Slide 62

Slide 62 text

Access restricted area curl -s -v http://localhost:3000/users/1/edit > /dev/null > GET /users/1/edit HTTP/1.1 < HTTP/1.1 302 Found < Location: http://localhost:3000/signin

Slide 63

Slide 63 text

Visit /signin, get token curl -s -c hello_cookies http://localhost:3000/signin > /dev/null |grep csrf-token

Slide 64

Slide 64 text

Sign in curl -s -v --data "session[email][email protected];session[password]=123456" \ --data-urlencode "authenticity_token=/t/IoUQxKVEL+KR2/HsnxTKmnALUA99jIr/LvjlgPKs=" \ -b hello_cookies -c cookies \ http://localhost:3000/sessions > /dev/null > POST /sessions HTTP/1.1 < HTTP/1.1 302 Found < Location: http://localhost:3000/users/1

Slide 65

Slide 65 text

Successful access to restricted area curl -s -v http://localhost:3000/users/1/edit -b cookies > /dev/null > GET /users/1/edit HTTP/1.1 < HTTP/1.1 200 OK

Slide 66

Slide 66 text

Browser tools ● console ● render helpers ● debug info ● etc

Slide 67

Slide 67 text

rack-webconsole >> User.find 1 => # >> helper => Error: undefined local variable or method `helper' for # >> app => Error: undefined local variable or method `app' for # >> Rails.root => # >> self => ##

Slide 68

Slide 68 text

rack-webconsole ` - activate

Slide 69

Slide 69 text

Rails Panel

Slide 70

Slide 70 text

rails-footnotes: Assigns

Slide 71

Slide 71 text

rails-footnotes: DB

Slide 72

Slide 72 text

rails-footnotes: disable ?footnotes=false

Slide 73

Slide 73 text

xray-rails Ctrl+Shift+X

Slide 74

Slide 74 text

better_errors 1

Slide 75

Slide 75 text

better_errors 2

Slide 76

Slide 76 text

exception_notifier Provides a set of notifiers for sending notifications when errors occur in a Rack/Rails application

Slide 77

Slide 77 text

letter_opener Preview email in the browser instead of sending it

Slide 78

Slide 78 text

exception_notifier + letter_opener

Slide 79

Slide 79 text

Chrome: Form Editor

Slide 80

Slide 80 text

Chrome: JunkFill

Slide 81

Slide 81 text

Chrome: Sight

Slide 82

Slide 82 text

API calls, sight

Slide 83

Slide 83 text

hurl.it: params

Slide 84

Slide 84 text

hurl.it: response

Slide 85

Slide 85 text

POSTMAN

Slide 86

Slide 86 text

Chrome: REST console

Slide 87

Slide 87 text

Chrome: Advanced Rest Client

Slide 88

Slide 88 text

ВСЁ