Skip to content

Instantly share code, notes, and snippets.

@kdonovan
Last active November 7, 2016 23:19
Show Gist options
  • Save kdonovan/2a460d53a8b91041906d205a44c5b1cc to your computer and use it in GitHub Desktop.
Save kdonovan/2a460d53a8b91041906d205a44c5b1cc to your computer and use it in GitHub Desktop.
Bloc.io candidate assessment

Full Stack Mentor - Candidate Assessment

Javascript

Hi Mentee,

The for loop looks good - it starts with btnNum set to zero and increases it by one each time through, then stops looping when btnNum is 3. The tricky part here is when the different parts of code are executed. Each time through the loop the document.getElementById('btn-' + btnNum).onclick = runs immediately with the current value of btnNum, so it finds the correct button and attaches the function to be executed when the button is clicked. However, the function it attaches is not executed until the button is clicked.

It's kinda like telling the computer "use this bag of code when the button is clicked", and the computer saying "OK, I've got the bag but I'm not going to even look inside it until I know I have to use it". When the button IS clicked, THEN the computer will try to run the code, and at that point the variables it's referencing may have different values than they did when the bag was put together.

Whenever the computer tries to execute alert(prizes[btnNum]) it looks up the current value of btnNum to figure out what text to alert. Since this javascript runs as the page is loading, and the buttons won't be clicked until after the page has loaded, by the time any button is clicked the for loop has already completed. Since the loop doesn't stop until btnNum is 3, that means whenever ANY button is clicked the function tries to run alert(prizes[3]). The last element in prizes is prizes[2], though (remember array indexing starts with 0 rather than 1), so the fourth element is undefined, which explains the alert messages you're getting.

To see this for yourself: if you add an alert(btnNum) as the first line inside the for loop, it'll alert 0, 1, 2 as expected. If you move that to just below // tell her what she's won!, though, it'll alert 3 on every button click.

[NOTE TO REVIEWERS] My final "pointing them in the right direction" section would depend on the specifics of their curriculum -- i.e. what sort of solutions they've been taught so far and what they were supposed to be working on here. e.g. if they know about it already I may point towards using setAttribute to cache the desired value in the for loop body and getAttribute on event.target in the onclick function to use that value rather than a runtime lookup.

Rails

Good questions! has_many lets you specify just the relationship:

Simple has_many specifies the relationship

class Company
  has_many :employees
end

class Employee
  belongs_to :company
end

allows you to access the company's employees: Company.first.employees. But what if you want to store more info about the relationship, like the employee's salary or when they began their current role? Information only about the employee (i.e. employee's name) clearly belongs in the employee model, but information about the relationship between the company and a given employee needs to live somewhere too - that's where :through comes in.

Use has_many :through to specify additional information about the relationship

class Company
  has_many :employees, though: :employee_contracts
end

class EmployeeContract
  belongs_to :company
  belongs_to :employee

  # this model specifies information about the relationship: salary, start date, position title, etc.
end

class Employee
  has_many :employees, though: :employee_contracts

  # this model stores information intrinsic to the employee herself - name, address, phone number
end

With through we can still ignore the extra information in the EmployeeContract model when we don't need it (How many employees total? Company.first.employees.count), but we can also pull it out when needed (How many managers? Company.employee_contracts.where(role: 'Manager').count).

has_one through

has_one through is the same as has_many through except you only have one associated model - in the example above, if the system were only designed to track one employee per company you'd use has_one instead.

Overview

Basically, the default choice if you need to indicate a relationship between models is to use has_many. If you find yourself needing to store additional information about the relationship itself, however, you can extract the relationship into a third model and splice it between the original two by using has_many through.

SQL

Say you want to make a search function on your website. You need to use information from the user (their search query) to generate SQL to send to the database to retrieve their results. "SQL injection" occurs when the user enters something malicious that causes your SQL query to execute differently than you'd intended.

For instance, say you have a method that allows searching out website users by name: "SELECT * FROM USERS WHERE name = '%s'" % params[:q]. If the search query is "Bill", then you tell the database to execute SELECT * FROM USERS WHERE name = 'Bill'. But if the user enters Bill' OR 1=1 the SQL becomes SELECT * FROM USERS WHERE name = 'Bill' OR 1=1 -- i.e. it will return ALL user records.

This shows the concept, but there are a bunch of other ways an attacker can use this opening to learn the structure of your database, gain access to records they shouldn't be able to see, access and change e.g. user passwords, delete your data (e.g. imagine if the search param from above was Bill'; DROP TABLE 'users') or sometimes even gain control of the database machine itself.

To prevent this you need to sanitize user input before passing it along to the database. The basic idea is to strip out or escape dangerous characters in the user input (e.g. single or double quotes), but because there are so many different ways to slip in unwanted SQL commands this is really tricky to do completely accurately (there are also DB-specific comment characters and more to be aware of). Luckily, though, as Rails developers we can let the framework take care of all that for us automatically -- all we have to do is make sure we don't work around the built-in protections.

Practically speaking, this means that as long as you're allowing Rails to generate the SQL for you you're safe, so e.g. User.where(name: params[:q]) and User.where(["name = ?", params[:q]]) are both safe. If you ever find yourself writing SQL directly, however, that's a red flag that you may be adding a vulnerability and need to be careful -- e.g. User.find_by_sql("SELECT * FROM users WHERE name = #{params[:q]}") is a Bad Idea.

Angular/jQuery

The difference between a library and a framework isn't clear cut and is mostly a matter of degree (and sometimes opinion). In general a library is a collection of pre-written code that you can use, whereas a framework is a bigger structure that may include libraries and usually also specifies patterns of using the pre-written code.

So for instance, the jQuery library makes it much easier to do frequent tasks (e.g. $('#my-id') rather than document.getElementById('my-id')) and also aims to extract out all the hassle of cross-browser compatibility. A great example of where jQuery shines is in handling AJAX requests -- the layer of abstraction added by $.get(url) is massively easier to understand and reason about than explicitly handling all the XMLHttpRequest minutiae manually. At the same time, though, if you want an AJAX request you still have to explicitly specify it.

Angular and other frameworks are higher-level constructs that tend to have more "magic" associated with them, where if you follow certain conventions you get whole flows of behavior for free. In a framework like Angular, for instance, rather than specifying lower level logic like "when button A is clicked then do AJAX request B, if the request is successful then update the form this way, otherwise in case of error do this instead..." you tend to operate at a higher level -- you tell the framework "connect this form to this object and here's the base URL to use for saving" and it handles all the logic of filling in the form field values, updating the server when changes are made, updating the UI when those updates fail to save, etcetera.

Algorithms

Big O is all about figuring out what the worst-case performance (usually either in terms of execution speed or memory usage) of the algorithm could be relative to the size of the input - i.e. how much longer will this take to complete if we give it a list that's 100x larger, or if we let it calculate Fibonacci to the 100th digit instead of the 10th?

Calculating big-O notation is all about counting how many steps the function needs to take in the worst case, and seeing how that varies as you increase the length of the inputs.

For example, given a function to see if an element is contained in an array:

function contains(elem, array) {
  for (var i = 0; i < array.length; i++) {
    if (elem == array[i]) return true;
  }
  return false;
}

In the worst case (the element is not in the array, so we have to loop over every array element), when given an array with n elements this function will have to do something to all n of them, once. We say this is O(n), which is saying that as the length of the array grows the length of time tho function takes to complete will grow linearly.

The great thing about big-O notation is that we're only looking orders of magnitude - we care about how the execution speed varies with longer inputs (constant, linear, quadratic, etc.), but we don't care about the details of what the actual relationship is (i.e. if the contains function were rewritten to convert the elem to uppercase before checking against the array it'll take slightly longer time, but the way it grows in relation to the length of the array remains unchanged).

In contrast, if we wanted to list all elements shared between two arrays:

function shared(a, b) {
  var both = [];
  for (var i = 0; i < a.length; i++) {
    for (var j = 0; j < b.length; j++) {
      if (a[i] == b[j]) {
        both.push(a[i])
      }
    }
  }
  return both;
}

Before we were doing one set of things for every element of an array, but now for every element in the array we're doing that set of things to every element in the entire other array. With the nested loops it now takes O(n^2) to complete, because for each n+1 we add to the array it has to do an additional n steps. The rate at which this slows down with larger inputs is much faster in this case than in the O(n) function.

CSS

One of the more confusing things about floats is that a floated element doesn't contribute to the height of its parent, which means an element with only floated divs inside it (.pricing-tiers in this case) will collapse to zero height by default. We can fix this by adding an element with clear: both; to .pricing-tiers after the floated .tiers, or just by setting overflow: auto on .pricing-tiers itself.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment