Some notes from Rails architect MasterClass

What is the business domain? A translation into code of the business model. A business domain in object-oriented programming is the set of classes that represent objects in the business model being implemented.

Event Sourcing to make our Rails Apps totally free from ActiveRecord, like a gem.

What are bounded contexts? Bounded context defines tangible boundaries of applicability of some sub-domain.

What is a sub-domain? domain that is a part of another domain.

Connect your code, not the data, to your external apps or clients, otherwise it would be very difficult to refactor.

A bounded context has: data and code.

Big problems:

  • The code of one bounded context is reading the data directly from another bounded context. Solution: when something happens in the data the code publishes a Domain Event and they can connect to a different bounded context.

namespaces :: are great sep for bounded context but as long as our persistence in the db is mixed together with has_many relations for example, it is a problem, we don’t have strong boundaries.

Do we need event sourcing when we have been event driven (domain events)? Yes, event sourcing is a good complement to event driven. First even driven and then, event sourcing.

Event sourcing: another way to storing the current state of the data. To store all the changes. If you want to read those changes, you read from the read model.

You don’t fix mistakes, you create a new event.

Is there any magic trick which turns active record into events? With this tool you can track create/update/destroy actions on active_record models: https://github.com/OnApp/custom_active_record_observer

Active Record associations are cure and poison.

Moving the business vocabulary to the code base

Domain-driven design is the concept that the structure and language of your code should match the business domain.

Domain model. Solution space. Validate your model and explore other possibilities.

Domain discovery: model exploration whirlpool, event storming, domain storytelling…

A new language: Ubiquitous Language, to understand

Domain events: Described at past tense and relevant for business. DE as an interface to communicate between Bounded Contexts.

Commands: Usually result of some decision made. Different sources. Can be a source of more than 1 domain event.

Aggregate: Let the names emerge, do not focus on them yet. Focus on behaviour not data.

Two difficult problems in CS: cache invalidation and naming things.

Domain Safari. We go to online places and see how domain experts communicate between them.

How to find the core domain? The discourse.

Strategic patterns: Bounded context, context maps, core & sub-domains.

Tactical patterns:

  • Value object(1 pound. We need the number 1 and the currency to know our object. VatRate, GrossAmount, Level, ID, ActiveSupport::TimeZone). They don’t have identity, they are immutable,
  • Entity. Domain object with unique identity. Usually have mutable state. What you have in ActiveRecord Base, my tables in the db.
  • aggregate: Set of composed domain objects defining single consistency unit. Root of the objects hierarchy is an aggregate root. Usually it is used as conceptual name of an aggregate. Order (aggregate root)has many order rows (entities that can have value objects).
  • domain event,
  • design patterns like: Repository, Factory, Strategy…

CQRS (Command-query separation) to speed your app having 2 models, one for reading and the other one to perform some action, write.¬† “Every model should either be a command that performs an action, or a query that returns data to the caller.”

Articles:

One simple trick to make Event Sourcing click

Event Sourcing is a transferable skill

What is a process manager?

Microservices:
Microservices are not much about architecture.  Microservices are a deployment strategy.
Code can be divided into: libraries, components and deliverables/assemblies. Microservices don’t have state so they are not components. They are from the 3rd group.

Rails with JavaScript – Recipe Manager

So… Starting with my project!

In first place, I want to build the JSON API using the Active Model Serializers gem. I add to my Gemfile:

  • gem ‘active_model_serializers’, that¬†allows you to generate your JSON in an object-oriented and convention-driven manner.

How the JSON API works?

The user makes a request, clicking a link, filling a form… where we had added an event listener. That info is serialized, translated to JSON, and submitted via an AJAX POST or GET request. With that response or data we create a new JS object and then we append the info of that object that we want to show to the DOM using a jQuery selector.

Building a JSON API consists basically in creating the response with the right data to a request and the Active Model Serializers gem is the perfect tool for this. It allows us to format our JSON, select only the information we want, as well as get access to our relationships with a single request.

Setting up the Serializer files

One of the requirements for this project is render at least one index page and one show page via JS and an Active Model Serialization JSON Backend. I was thinking in rendering my recipes index and my recipes show pages.

At the moment¬† I am displaying a list with all the recipes and links to their show page. I am not displaying more info than the title of the recipe in this page, so that’s what I want.

First I add to my Gemfile and bundle install:

  • gem ‘active_model_serializers’

Then I create my serializer file for my recipes. I tipe in my terminal:

rails g serializer recipe

And after creating it I specify which info, attributes and info of its relations I would like to have, in this case, the id and the name of the recipe.

If we specify in our contoller to “render :json” and because I have installed Serializer, Rails will now look to this folder before rendering a resource. Every object we pass to “render :json” in our controllers needs a corresponding serializer file.

* If you want deep nesting by default, you can set a configuration property in an active_model_serializer initializer. (stack overflow)

Create the file: app/config/initializers/active_model_serializer.rb with the following content:

I am going to create my serializers and specify which info I want.

Configuring the Controller to respond to JSON requests.

This is my new index action in the recipes controller:

Uhhm…I just realize that I have a problem. I want different info depending on where the object is rendered. For the index page I only want the title and the id of the recipe but for the show page I want all the info.

Lets make some changes and create two different serializers for recipes.

At the end I created these serializers:

  • category_serializer.rb
  • comment_serializer.rb
  • full_recipe_serializer.rb
  • ingredient_serializer.rb
  • rating_serializer.rb
  • recipe_ingredient_serializer.rb
  • simple_recipe_serializer.rb
  • user_recipe_serializer.rb

A lot of serializers…. I started playing with my app and encounter this error when I was trying to get one of the recipes of my index:

I think that this error is due to the info that the function fetch() is getting is not JSON.
My solution was in the serializers, I wasn’t displaying correctly the info and the relations between the objects. When I was trying to create my JS recipe object my function didn’t have the necessary info. It didn’t have the info for the has_many relations, so when I was trying to create these objects like the comments or the ingredients for the recipe I got this error.
I realize too that I didn’t need a serializer for my user class doing some methods inside the serializers where I wanted that info, for example in the comment serializer I added a method to display the name of the user who create that comment. This is better too in terms of security. I am not displaying my users data.¬†Better not to have the associations because it will give access to the whole user object, including the authenticity token.¬†
This is how my serializers look now:
Two different serializers for a recipe and I have created some methods to get the info I needed and delete the belongs_to relations. I kept the has_many relations trying to avoid defining methods for relations as much as possible, because it’s not clean, neither maintainable¬†but didn’t do it in the case of the user info.

.

I have my serializers and the following images are my changes in my controllers to respond to JSON requests:

Comments controller:

Recipes controller:

Next steps are creating my event listener and JavaScript Object Models.

Creating my files for the JS code

I first add to my Gemfile:¬†gem ‘sprockets-rails’, :require => ‘sprockets/railtie’,¬†a Ruby library for compiling and serving web assets.

I continue adding a new file for all my JS:¬†app/assets/javascripts/main.js. I don’t have to require this directive(main.js) in my manifest (application.js) because this manifest has //= require_tree, which will include all JS files in the same folder that application.js is located.

I’ll have to load the manifest file in my layout, so in the file: app/views/layouts/application.html.erb, inside the head tag, I write:

<%= javascript_include_tag “application” %>
javascript_include_tag is a Rails helper that generates a script tag instructing the browser to load that JS file.

Using arrow syntax of ES6

It differs with the normal JS function by the treatment of this. A normal JS function gets a thisaccording to the calling context (traditional semantics), but the arrow functions keep the this of the context of definition.

Creating a comment with Ajax.

In my comments/new.html.erb I have a form to create a comment. What I want to do is set it up so that I use this form, but use jQuery and AJAX to submit it, so I can handle a JSON response and display the newly created comment without redirecting to the comment show page.

Web users like fast websites and AJAX (Asynchronous JavaScript and XML) is a good technique to keep users engaged. In AJAX we first deliver a page using HTML and CSS and then use JS to add more to the DOM.

Ajax relies on several technologies: promises, XMLHttpRequestObjects, a serialization format called JSON, asynchronous Input / Output and the event loop.

Browsers and servers are only able to pass strings of text to each other. By using JSON, we can structure this text in a way that a browser or server can read as a regular JS object.

I will use the JavaScript fetch() function to apply the AJAX technique.

First step is to set up a document ready in order to detect when our HTML page has loaded, and the document is ready to be manipulated.

Then we hook up an event handler to the form’s submit event, and then block the form from doing an HTML submit. We add¬†an event listener for $(‘div#new_comment’).on(‘submit’, function(event) {} to our document¬†ready().

I am using the jQuery on() method. The on() method attaches one or more event handlers for the selected elements and child elements.

To display error message I read this tutorial.

To create the comment via Ajax I need  a JavaScript Object Model too. I added two prototype functions.

This JMO allows me to get all of the attributes of a comment into javascript by passing the comment resource sent by the controller in response to the AJAX request for json and assigning its properties to a new javascript model object. We can then use the properties of this new javascript model object within my methods on the prototype to help me display the content to the page.

After creating this event listener I follow similar procedure to create another one to handle the comments when they are created in a nested resource, and after that one to get, not post, an index of recipes and show a recipe plus a JS Object model for Recipe, RecipeIngredient, Category and Rating.

Link to my repository.

Rails Portfolio Project – Recipe Manager

Recipe Manager was built using Ruby on Rails framework and Bootstrap. All users can safely signup, login and logout. I created authentication and authorization methods without using any gem. Recipe Manager include a third party signup/login via Github thanks to the OmniAuth gem.

Users can check the recipes, rate and comment them but only admin users can create and edit the recipes and the categories. The user can browse recipes by name and author and all the comments by author and recipe. The app include reasonable validations for its forms and an ActiveRecord scope method to get the newest recipe that can be seen in its own URL, in this case: http://localhost:3000/newest_recipe.

My models:

User

  • Has many recipes
  • Has many comments
  • Has many ratings
  • Attributes: name, email, password_digest, admin, and finally I added uid and provider for the signup/login with OmniAuth.

Recipe:

  • Belongs to a user
  • Has many recipe_ingredients
  • Has many ingredients through recipe_ingredients
  • Has many recipe_categories
  • Has many categories through recipe_categories
  • Has many comments
  • Has many ratings
  • Attributes: name, cooking_time, servings, directions, date created

Ingredient

  • Has many recipe_ingredients
  • Has many recipes through recipe_ingredients
  • Attributes: name

RecipeIngredient

  • Belongs to recipe
  • Belongs to ingredient
  • Attributes: quantity.

Category

  • Has many recipe_categories
  • Has many recipes through recipe_categories
  • Attributes: name

RecipeCategory

  • Belongs to Recipe
  • Belongs to Category

Comment

  • Belongs to user
  • Belongs to recipe
  • Attributes: content

Rating

  • Belongs to user
  • Belongs to recipe
  • Attributes: score

Generate my new application:

Generate my models with their associations and create my db:

*Adding :default => true to boolean in existing Rails column

Implementing a sign-in, login and logout functionality:

In order to encrypt the passwords I will use the gem bcrypt, include “has_secure_password” in my User model and have a password_digest attribute in my users table.

Sign in with Github

  • Add to your gemfile the OmniAuth gem and the provider-specific OmniAuth gem, in this case “omniauth-github”, and run bundle install.
  • Create a file named config/initializers/omniauth.rb. I will contain the following:
  • Create an application in Github.

[Settings/Developer Settings/OAuth Apps/Register a new OAuth application]

In homepage url enter: https://localhost:3000/

In the Authorization url: http://localhost:3000/auth/github/callback

After creating the app you get a client ID and a Client secret.

  • Add dotenv-rails to your Gemfile and run bundle install.
  • Create a file named .env at the root of the application and add your Github credentials.

  • Add .env to your .gitignore file.
  • Create a link to login via Github
  • The User model needs these attributes, all strings: name, email and uid, that it is the user’s ID on Github. *I added a provider attribute later on.
  • Create the route where Github will redirect users in the callback phase of the login process.
  • And in our Sessions controller we find or create the user who logging via github.
  • In our User model:

I want to add some helpful links: