Ruby On-Ramp Questions and Answers
Describe – in your own language – Bounded Context
Oh boy, I am not familiar with this term. Can I make an educated guess? No, it wouldn't be a very good one; both “bounds” and “contexts” are pretty general ideas and could mean a number of different things.
Do you prefer ERB or HAML? Why?
I prefer HAML, because for me it's a lot cleaner and more readable, and it's simpler to type. I think meaningful indentation is a great programming concept and HAML uses it well. A good markup language should stay out of your way, and HAML is much better than ERB at that. And because it's less in-the-way, it's easier to see and comprehend the structure of the underlying HTML.
Describe what a “concern” is within Rails.
A concern is a group of methods, written inside of a Ruby Module, that can be included in other classes. It usually represents a set of shared behavior that we want to give to several different models or service objects. This has several advantages, mainly that we don't have to write the same code more than once, and we can make any future changes to the logic in one place. It also allows us to reduce the size of our model files, which are always at risk of becoming long and unwieldy.
Describe the intent of a policy. How would you write a policy to block users from creating users, but to allow a super user to create a user?
You may use any gem you'd like in your answer's code – or you may describe the logical process in your own language!
Policies allow us to control access to objects in our system. We could look at it in two ways: 1) we are granting access to an object to certain users; 2) we are blocking access to an object to certain users.
Let's assume we have a button on the Users index page that you click to create a new user. To allow only super users to do this, we need to put some code in our view that only includes this button if the current user is a super user. To do this, we would grab the current user object, which should be accessible from our view in some way, and pass it to some kind of interactor (this could be provided for us via a gem like Pundit) which essentially has a list of behaviors/actions and knows how to determine if a given user is allowed to do each of the behaviors/actions. We could call something like PolicyHelper.new(currentuser: user).createusers?, which would return true if our user can do it, and false if they can't.
We could also perform this logic in the controller (or really, in an interactor that the controller reaches out to) and then get our answer and pass it into the view, via a local. It's usually good to keep as much logic out of the views as possible.
This button, when clicked, would take us to the page with the Create User form. So we also need to do this same check as part of that controller action, in case someone navigates directly to this URL. Before we render the action's template, we do our policy check. If it comes back false, then we redirect the user to the home page, with a flash message that says something like, “Sorry, you're not allowed to perform this action.”
What is a Polymorphic Relationship? How could you use this idea in a practical way?
A polymorphic relationship exists between two domain concepts in a software system when one or both of the concepts can be represented by more than one domain objects, or in Rails terms, models. A practical example: in a billing system, you have a concept of receipts, which are paid for by payment methods. A payment method could be many different things: a credit card, a bank account, a paper check, etc. In our Rails domain model, we could have a Receipt model that has two fields, a paymentmethodtype and a paymentmethodid. The paymentmethodtype represents the name of a model that corresponds with the payment method a receipt was paid with: CreditCard, BankAccount, Check, etc. And the id tells you which particular CreditCard, BankAccount, Check, etc., paid for the receipt. The benefit to this is that a Receipt doesn't have to care what kind of thing paid for it. It can call methods on its payment_method and the different types of models can implement those methods in the way that makes sense for them.
Describe – in your own words – what a controller is generally responsible for.
The controller fields requests from a user and generates a response. Some parts of this are taken care of by the Rails framework, so that the controller gets to deal with an abstract and simplified representation of the request, and it doesn't have to actually deliver the response to the user over the wire. But everything between those two poles is the controller's responsibility. In a well-architected application, the controller will actually outsource many things to specialized interactors, like authorizing that the current user is allowed to make the request and access the data that would be returned, figuring out which data needs to populate a list on the page, etc. But the controller does need to know which interactors to call in order to get those things done. If a database operation is being performed, the controller usually needs to know if the operation succeeded or failed, and provide that information to the user accordingly, for example, by redirecting to an error page. It also needs to know what response format is being requested (for example: HTML, JSON, Javascript, etc.) and make sure it provides its response in that format, or tells the user if it can't handle the requested format.
Describe – in your own words – what a model is generally responsible for.
A model is the representation of an external domain concept in the application. It is almost always also a layer on top of the database representation of that domain concept (models are generally one-to-one with database tables). This means that it should be able to control the application's access to the database table, at least regarding “normal” user interface operations. (A developer can always bypass the model level and make direct edits to the database.) In Rails, models get a lot of functionality from an API called ActiveRecord, which allows us to give them some additional responsibilities. For example, when a new database record is being created, the model validates that the data is appropriate first, and if it's not, valid, it provides a helpful message to explain why. Models also generally perform dynamic business logic operations that are used to surface information for users. For example, in a baseball application, a Player model might represent a player's number of hits and at-bats (as stored in database fields on a “players” table). In our UI, we might want to show users the players' batting average in real table, so we could define a “batting_average” method on the Player model that divides hits into at-bats.
What is an ActiveRecord validation? When – or which method calls – make ActiveRecord validations run?
An ActiveRecord validation is a method that compares a data value that is set on a model field with a rule for what that field's values are allowed to contain. If the check fails, an error message is generated explaining why the data value is invalid, which can then be inspected by other code. For example, you can set a validation that the name field on a model must be at least 10 characters. If you then set the name on an instance of that model to a 9-character string and run the validation, it will fail and populate an error message. Validations can be run explicitly by calling “valid?” or “validate” on a model object. They also run as callbacks—pre-checks, essentially—before any operation that would update the information in the database, such as “save,” “update,” and “destroy.” The idea is that we want to make sure the data is valid first, before we put it in the database.
Describe the idea of “middleware” in Rails
One of the biggest responsibilities of the Rails framework is to take a low-level representation of a request to a web server and perform some transforming operations on it, which makes it a lot friendlier for application developers to interact with. In Rails, middleware is a term for modules of code that can be run as part of this request transformation. The framework itself comes with a bunch of middleware that already runs by default, but you can also write your own custom middleware and specify where in the “middleware chain” it will run. For example, you might maintain your own list of which browsers your application officially supports, and you want to make it easy to find out if a request is coming from a supported browser or not. You could write middleware to examine the browser identifier string from the low-level request and set a special field on the Request object, like “supported_browser?” that just returns true or false. This helps you a lot as an application developer, since you don't necessarily care precisely which browser version the user has; you might just want to add a feature that displays a message if their browser isn't supported.
Users can specify which format they want by putting a dot at the end of a URL, followed by the name of the format. For example, “example.com/users.json” or “example.com/users.html”. In your views folder, you can have different files corresponding to the different formats you want to render. You can also render the response directly from the controller, as is often done in the case of JSON (since the request is likely coming from some automated process rather than a person typing in a URL). The special “respond_to” controller method takes a block in which you can specify custom handling for any request format you want to support, and Rails will invoke your logic based on the incoming format.
What is “ActiveRecord” and what usually extends it?
ActiveRecord is an ORM, which is a type of software library that provides a higher-level interface to a database. It maps database tables to models, and gives the models a rich API for interacting with the data in the underlying table. With ActiveRecord, you (usually) don't need to worry about whether your app's database is postgres, MySQL, sqlite, etc., because it comes with adapters that provide you a common interface to work with and know how to translate the ActiveRecord methods you call into database-specific statements. This way, you can learn one set of operations and yet be able to interact with all kinds of different databases. If you're doing something really complicated, you might still need to interact directly with the database in its own language, but the proposition ActiveRecord makes is that you'll rarely need to do this, and most of the time you can just use its much simpler and friendlier API.
Describe – in your own words – the purpose of a “migration”. What are some challenges you would face when working as a team when you do not have the idea of “migrations” available to you?
In the old days, any changes that needed to be made to a database's schema were hidden away from the applications that were using that database. This makes it pretty difficult to keep the schema and the application code in sync. But Rails provides the ability to make your database schema changes from inside the application. It envisions this process as a series of events called “migrations,” which are sets of discrete schema changes. Migrations can be as small as changing the default of a boolean column from false to true, or as large as adding several new tables to the database. Because each of these migrations is defined in a single file in your application, they also provide a single point at which you can manage any changes you need to make to your data before or after the schema change. For example, if you write a migration to set the maximum length of a string column to 20, you first need to ensure that all values in that column are 20 or less; you can run this code directly in your migration. In addition to make this an easy way to update your production database, other devs on the project can also run the migrations to keep their local database in sync. This also gives you an easy way to roll back to a previous state if the changes didn't work out. Without migrations, team members would be responsible for keeping their own local database in sync with the main project, and they would have to be very careful about coordinating their changes to avoid schema conflicts.
Describe in detail how Rails will process a POST request to, say, create a new student in an application at the /students/new path. I'm looking for even a numbered list of what code gets executed and in what order?
https://github.com/getflywheel/on-ramp-wp-engine-2022-rails-app-/blob/application/app/controllers/students_controller.rb#L18
Well, I'm not able to access that link, unfortunately. I'm also a little confused because /students/new would likely be fielding GET requests only and displaying a form, which is then what would be POSTed (but to the create path). I'm going to assume that's what this is asking about. The first thing that will happen is that Rails will be sent the data about the request from the web server (this actually happens in one of Rails's lower-level dependencies called Rack). Next, the Rails router will attempt to match the URL path to a controller and action that will handle the request, using the code in the routes.rb file. The first match it finds will get executed. Then Rails will run any callbacks that are configured for that controller action, and then call the action method itself, which will most likely render a view template (if it doesn't explicitly do so, Rails will determine which template to render based on the controller and action names) in the format specified by the URL or the controller action, or if one isn't specified, HTML. Rails then gives the resulting markup to the web server to send over the wire to the client that made the request.
These tools allow us to run non-time-sensitive application logic asynchronously. In other words, it runs in the background, in another process on the web server, or even on another server entirely. The main benefit of these tools is that, if we have some logic to run that will take a while, we don't have to make the user wait; we can finish up handling our web request, send HTML back to the user, and do the rest of our stuff on its own schedule. For example, when a user orders a product from us, we might want to send them a confirmation email. But since emails can take time to process and send, and the user is patiently waiting on us to display a confirmation page that their order was successful, we can have Resque or Sidekiq send our email in the background. It's okay if the user gets the email a minute or two later. Another big advantage of these tools is that if there's an error running some code, they will retry it several times, according to a configurable schedule.
Describe – in your own language – Domain Context
I'm not familiar with this term either, but I feel a little more comfortable making a guess. The term “domain” in software development refers to the real-world context in which an application operates. For example, if I am writing an app to process insurance claims, I need to know some information about how this is done today. Who is eligible to submit a claim? Who needs to be notified when a claim is submitted? If a claim is fulfilled, how do we alert the submitter and send them their benefit? There may be a great deal of knowledge, rules, conventions, terminology, etc. that is part of the domain but isn't necessarily reflected directly in your application, but forms the rationale for why your app has the features and behaviors that it does. This information is essential to understanding whether the app is fulfilling its purpose and doing what it was designed to do.
What is your favorite example of an engine? Why are engines helpful?
I'm not actually sure I can name an engine off the top of my head—maybe something like Administrate?—but basically, they are like mini apps that you can “mix in” to your own app when Rails boots it, to add some common functionality that you wouldn't want to write yourself, like an admin dashboard or user management system.
What is the rails console useful for?
Oh, I use the console all day! One use case is for quickly trying out some code in the context of my application. Yeah, sometimes I just want to figure out how some Ruby code works, but often times I am working on some feature or bug fix for an app, and I need to see if my idea is going to do what I think it will do. It's nice to have access to all your app's data for these kinds of tests, with the ability to wipe any changes away when I close my console. It even allows you to overwrite application classes and methods on the fly. The console is also really useful for making data fixes in staging and production environments in a relatively safe way.
Describe – in your own words – what a view is generally responsible for.
A view is responsible for presenting some data to the user. In most cases, it is the code that is directly responsible for creating the HTML markup that will be served to the user. All matters of style and UI “look and feel” ought to be handled by the view—although there is a common pattern of providing the view with an object called a “presenter” which may have some augmented data and allows the view to stay very “dumb” and just render basic markup. The name “view” implies that you could have several different ways to view the same set of data, and you can pick which one you want to use based on who the user is, or some characteristic of the data.
Describe the intent of writing code by following the service object pattern versus the interactors pattern. What is your opinion about this pattern?
I thought these were essentially two different names for the same pattern. I love the pattern myself, as I think it allows the controllers and models to be a lot smaller and more devoted to their core responsibilities. It gives you a central place to put some unit of logic, making it more extensible, reusable, readable, and testable.
Describe the practical difference between the two testing commands bin/rspec
and bin/rails test
?
RSpec is a testing gem that gives you a more descriptive, natural language-like DSL for writing tests. It originated as part of the Behavior-Driven Development paradigm, the idea being that you use the tests to specify how your code behaves as opposed to what specifically it's supposed to return. In theory this decouples your tests from your implementation, but in practice, that is pretty difficult to achieve. It also doesn't come with Rails. On the other hand, bin/rails test runs your tests with Minitest, which does come with Rails and doesn't have a DSL; as the maintainer likes to say, it's “just Ruby.” It also has a module that allows you to use the more descriptive RSpec-like syntax (describe blocks, let statements, etc.).