# Phase 3 - Lecture 7: Debugging In Ruby ## Process - What is our code supposed to do? - How do we know when it's done? - How do we split it into smaller pieces? - How does each piece connect to the next? - What should be true at each stage of the process? - How do we get feedback as we go? ## Techniques - adding a `binding.pry` - using `rake console` - adding a `puts` statement ## If we hit an error/bug while working - What are we trying to do? - What's happening instead of that? - Why do we think we're getting the current result instead of what we want? (What's the cause of the error?) - If our (educated) guess about what's causing the problem is correct, how will we get feedback telling us we're right? (use our debugging/testing tools here) - Did we isolate the problem? - If so, what do we change to get closer to our desired result? - If not, return to guessing at explanations for the current problem. >Note, this process emphasizes explaining the problem first, rather than immediately trying to fix it. The reason for this is that learning how to identify and isolate the information that your errors/feedback are giving you is going to improve your understanding of the way the language works. This will help you solve problems more effeciently in the future by paying attention to the feedback that you get while you code. I like to think of coding as having a conversation with the computer. You want to make sure it's a balanced conversation. In a good conversation, both sides have a chance to participate. For us, this means we need to take the time to listen to what the computer is telling us before we respond with new code. ## 5 Tasks and How to Apply Debugging Skills/Tools - How to test whether migrations are correct - How to test that association methods are set up properly - Figuring out where to put a binding.pry to isolate a problem - How to use binding.pry together with rake console and db:seeds - How and when to use puts to get meaningful feedback ### How to Test Whether Migrations Are Correct - **What is our code supposed to do?** - manipulate database structure - store changes in version control - describe what data goes where - delineate our foreign keys (belongs_to models have foreign keys on their corresponding table) - **How do we know when it's done?** - check db/schema.rb - When you can call a method on a class (Bank.all) and you get something back - SQLite Explorer to view DB structure - run `rake db:migrate:status` - compare results with deliverables/instructions/desired features - **How do we split it into smaller pieces?** - migrate one table at a time - probably we don't want to add 1 column at a time, because we'd have to rollback in between every time - **How does each piece connect to the next?** - make sure foreign keys are in the appropriate table pointing to the table row that they belong to - foreign key is connected to: - primary key in another table - the corresponding model (Account for the accounts table) that has a `belongs_to` macro which defines a method that will return the other record whose primary key (id) matches the value stored in the foreign key (bank_id) column - **What should be true at each stage of the process?** - as we run them, we can check the schema, or any other way that we listed above to see that the change we expected actually happened. - **How do we get feedback as we go?** - we mainly get feedback after we run a migration. #### Key Challenges - Making sure we know the proper place to put our foreign keys in our migrations - How do we know where the foreign keys should go? - The foreign key should go in the table corresponding to the model that belongs to another model. (has_many doesn't require a foreign key on the calling model-the model that has has_many on it-the foreign key on the opposing belongs_to model) ### How to test that association methods are set up properly ``` User has many posts Post belongs to user ``` - **What is our code supposed to do?** - establish connection between instances of our classes via instance methods - If we add `has_many :posts` what does that do? - allow us to see posts associated with an instance by adding methods like `posts` - allow us to create posts associated wit a user by adding methods like `posts.create` - **How do we know when it's done?** - `User.first.posts` - there needs to be at least one user - we would need to have posts that belong to that user - if both are true we'd see the posts for that user when we run this in the console - `Post.first.user` - we would need at least one post in the database - if we run this and we get `nil` what would we learn? - post hasn't been assigned to a user - check the foreign key column in the posts table - if the foreign key is null that would explain it - if we have an integer type column and no foreign key constraint, then our integer could point to a deleted user. (Check users table for record with id matching user_id foreign key) - **How do we split it into smaller pieces?** - we can test one association at a time within rake console - **How does each piece connect to the next?** - what association methods are related to one another? - `belongs_to` is related to `has_many` - `has_many, through:` is related to `has_many` - **What should be true at each stage of the process?** - every time we add an association method, we would want to use the debugging tool: `rake console` to call one of the methods added by our macro and confirm that it returns what we expect. - **How do we get feedback as we go?** - As we add association methods, we're going to be thinking about what rows in our database we would need to have to test out the methods. - When we call one of the association methods on an instance of one of our classes, we'll predict what we should see and then try it out in `rake console` to confirm that we actually see that. Any questions/clarifications here about how we can debug migrations/associations? ### Figuring out where/when to put a binding.pry to isolate a problem - Say we have a syntax error in our code, will binding.pry help? - no, why not? We won't hit the pry because the interpreter can't run the code. - Best bet with SyntaxError, best bet is to isolate the line number and where the > or ^ is pointing and just try to look for something that's off around there. - Ends that are missing - curly braces that are not terminated properly - commas that are missing - If we have something like a NoMethodError, binding.pry can be very useful. For a NoMethodError like this: `undefined method 'method_name' for nil:nilClass`, where would you add a binding.pry and why? - ```rb @post = Post.find_by_id(4) @post.content NoMethodError undefined method 'content' for nil:nilClass` class Post < ActiveRecord::Base end create_table :posts do |t| t.text :content end ``` - If you put a binding.pry into your code and you don't hit it, that's useful information as well! What could it tell you if you have a pry in your code, you run it, and you don't end up hitting it? ### How to use binding.pry together with rake console and db:seeds - One of the simplest ways to use `binding.pry` and `rake console` together is to add a `binding.pry` to one of the methods in your code and then boot up `rake console` and call your method. - Things get a bit trickier when you pull in `rake db:seed` - The main reason we use binding.pry is to get feedback about how a particular part of our code is working. If we run 20 lines of code and get an error, we can add a binding.pry to slow things down and run code line by line to see if we can spot a bug (where code is doing something different from what we expect) Let's apply the same thought process to working with the seeds file: - **What is our code supposed to do?** - When we run `rake db:seed:replant` it should clear out all of the previous records in our database and create new records in our database based on the contents of the `db/seeds.rb` file - **How do we know when it's done?** - we can check SQLITE explorer to see that we have the right rows in our tables - **How do we split it into smaller pieces to get feedback as we go?** - we can use `binding.pry` within the `db/seeds.rb` file to stop the code in the middle to verify that we've got certain things working before moving on. - We can even try out seed code in that pry and then paste it into the `db/seeds.rb` file above our pry when we see that it behaves the way we intend. - **How does each piece connect to the next?** - sometimes in seeds we'll be creating associated data, so we'll need to create the parent record (the one that has many of another) before we add children. We can also use association methods like `user.accounts.create` to create associated records without manually assigning a foreign key - **What should be true at each stage of the process?** - Each time we run `rake db:seed:replant` we will be reseting our tables to be empty, so the database should look contain the rows we're intending to create within the `db/seeds.rb` file. If we have a `binding.pry` in the `db/seeds.rb` file, then the database should contain all the rows created **above** the pry when we are paused at the pry. - **How do we get feedback as we go?** - again, using `binding.pry` within the seeds file is a great way to get feedback as you go. ### Notes about db:seed - We can use `rake db:seed:replant` to run the seeds again. Every time we run rake db:seed all of our console records for User and Bank will be destroyed - id numbers will change when we run `rake db:seed:replant` ### How and when to use puts to get meaningful feedback - a process that involves multiple steps that you want to confirm are behaving in a particular way is a good use case for `puts`. - `puts` debugging is useful for situations where you have a hunch about what's happening and want to confirm it, `pry` is better when you're not sure what's happening and want to figure it out. - `puts` is also helpful when you want information about some internals of a method for curiosity's sake. You don't necessarily have to do anything right away, but you just want the information. (Something like `puts self` or `puts self.class`) for example. ## Breaking Down the Process - Clearly understand and describe deliverable - Set up scenario (required objects/code to test deliverable) and expected result - Use debugging tools like console, puts, and binding.pry to test behavior of your code in that scenario ### User#total_balance >returns the total balance for a user based on the sum total of all of their account balances. - Clearly understand and describe deliverable <details> <summary> Deliverable </summary> <hr/> - return the total balance for a user based on the sum total of all of their account balances. <hr/> </details> <br/> - Set up scenario (required objects/code to test deliverable) and expected result <details> <summary> Scenario </summary> <hr/> - This is an instance method in the User class - It relies on the user having associated accounts - we need to calculate the sum total of all this user's account balances - So, we need a User and associated accounts to test - If we had a user with 2 associated accounts, each having a balanced of 250, this method should return 500 <hr/> </details> <br/> - Use debugging tools like console, puts, and binding.pry to test behavior of your code in that scenario <details> <summary> Debug </summary> <hr/> - Create the method and leave it blank except for `binding.pry` - Open up `rake console` - Find a user in the database with multiple accounts - call `.total_balance` on that user instance - in the pry, try reading docs about ActiveRecord calculations and seeing what they do when interacting with this user. - keep trying expressions until we achieve the return value we expect based on the information we see in the DB via SQLite explorer. <hr/> </details> <br/> #### How do we actually test this? ```rb ``` ### Account#summary >should return a string formatted as follows: `"{insert account_label} {insert account_type} account balance: {insert account balance}"` - Clearly understand and describe deliverable <details> <summary> Deliverable </summary> <hr/> summary will be an instance method in the Account class. It should return a string with data about the account included in this format: `"{insert account_label} {insert account_type} account balance: {insert account balance}"` <hr/> </details> <br/> - Set up scenario (required objects/code to test deliverable) and expected result <details> <summary> Scenario </summary> <hr/> - This is an instance method in the Account class - To test it, we need to access or create an instance that has values for the attributes that should be included in the returned string - When we call `summary` on the account instance from the previous step, we should see a string in the above format with the attribute data included. <hr/> </details> <br/> - Use debugging tools like console, puts, and binding.pry to test behavior of your code in that scenario - ... #### How do we actually test this? ```rb ``` ### Bank#accounts_summary(user) > takes a `user` (an instance of the `User` class) as an argument and should return an array of strings containing the account summaries for all the given user's accounts with that `bank` formatted as follows: ["{insert account_label} {insert account_type} account balance: {insert account balance}", "{insert account_label} {insert account_type} account balance: {insert account balance}", ...] - Clearly understand and describe deliverable - ... - Set up scenario (required objects/code to test deliverable) and expected result <details> <summary> Scenario </summary> <hr/> - This is a Bank instance method, so we'll need a bank - It accepts a User instance as an argument, so we'll need a user as well - because it is returning account summaries for that user at this bank, I'm also going to need a bank that my user has accounts with - When I call accounts_summary on the bank and pass in the user, I should get an array of strings matching the number of accounts this user has at that bank. <hr/> </details> <br/> - Use debugging tools like console, puts, and binding.pry to test behavior of your code in that scenario - ... #### How do we actually test this? ```rb ```