DEV Community

Cover image for Rails/JS Project: Apple Expert Back End
Jessie Rohrer
Jessie Rohrer

Posted on

Rails/JS Project: Apple Expert Back End

For my 4th project for the Flatiron School software engineering boot camp, we have to build a single page application with a Rails back end and an HTML/CSS/object-oriented JavaScript front end. I came up with a couple of ideas, but settled on a little app that will give you apple variety suggestions based on what type of recipe you're trying to make. (This project gave me major fall vibes, and now I just want to hang out with some cider!)

I have to admit that I found this section of the curriculum lacking...We were introduced to some pretty high level JS stuff without covering the basics, and there was a side-step into functional programming for 1 lesson and a pretty hefty lab. I spent a lot of time on my own reading and learning the JS basics elsewhere. I know there is a section of JS in the pre-work for the boot camp, but as a self-paced student, I was told to skip the pre-work and jump right into the curriculum, so I think I missed out, and now I no longer have access to the pre-work.

I also had some difficulty with OOJS, mostly because JS has prototypal inheritance, whereas Ruby has class-based inheritance. Thanks to the syntactic sugar of ES6, writing a JS class looks an awful lot like writing a Ruby class does, with some exceptions, but it behaves differently. I stumbled a lot trying to figure out what should go into a JS class vs. living in the index.js file or in an API class, and how to get the class-level data to be accessible elsewhere. My app is working, but I am going to spend more time before my assessment researching so that I can fully understand what I've written.

When I sat down to start this project, I shot myself in the foot a little bit by choosing a has-and-belongs-to-many relationship between my models in the back end. I couldn't think of a good third model to instead use the more familiar (and better-supported) has-many-through relationship. So, an apple has and belongs to many categories, and a category has and belongs to many apples. Each apple can have multiple categories and each category can have multiple apples.

So the first thing I did was generate models for both apple and category, set up the migrations, and seed the database with the 6 categories (table apples, cooking/baking apples, sauce apples, pie apples, juice apples, and butter apples) and the 10 most common apple varieties. Once that was done, I spent some time in the Ruby console getting familiar with my data and how it needs to be called given this new-to-me relationship. It took some tweaking and a private helper method in the apple controller to call out apples based on their categories, but I was able to get the back end behaving correctly.

def get_apples_by_category(category)
    Apple.joins(:apples_categories).where(apples_categories: { category_id: category })
  end
Enter fullscreen mode Exit fullscreen mode

Next, I gave my back end the necessary routes for the behaviors my front end needs to have. I namespaced the routes so that if I create any updates or overhauls, it will be easier to change the endpoints and link the front end to the new back end.

namespace :api do
    namespace :v1 do
      resources :apples, only: [:index, :create, :destroy]
      resources :categories, only: [:index]
    end
end
Enter fullscreen mode Exit fullscreen mode

The next step was to set up the needed controller actions. The apple controller has index, create, and destroy actions. While I was at it, I added the private strong params method. I include the empty array with category_ids so that all of an apple's categories can be passed through.

  def apple_params
    params.require(:apple).permit(:variety, :harvest, :notes, :image_url, category_ids: [])
  end
Enter fullscreen mode Exit fullscreen mode

I did the same for the categories controller. This one has only an index action and a private strong params method.

class Api::V1::CategoriesController < ApplicationController

  def index
    categories = Category.all
    render json: CategorySerializer.new(categories)
  end

  private

  def category_params
    params.require(:category).permit(:name, :category_id)
  end

end
Enter fullscreen mode Exit fullscreen mode

Next, I added the fast_jsonapi gem to my gemfile and uncommented rack-cors, then re-ran bundle install to add them to my project. (Don't forget to replace the sample url in the cors.rb file with a "*" wildcard for development!) I set up serializers for both of my models.

class AppleSerializer
  include FastJsonapi::ObjectSerializer
  attributes :variety, :harvest, :notes, :image_url, :categories
end
Enter fullscreen mode Exit fullscreen mode

This will give me a data object that contains each of the apple objects. Each apple object will have an attributes object that contains attributes as key/value pairs. This includes the category information for each apple.

"data": [
  {
    "id": "10",
    "type": "apple",
    "attributes": {
      "variety": "Ambrosia",
      "harvest": "Late September",
      "notes": "Store in the refrigerator. Does not brown when sliced. Sweet and honey-like flavor. Good for salads and snacks.",
      "image_url": "https://www.freshpoint.com/wp-content/uploads/commodity-ambrosia.jpg",
      "categories": [...]
    }
}
Enter fullscreen mode Exit fullscreen mode
class CategorySerializer
  include FastJsonapi::ObjectSerializer
  attributes :name, :apples
end

Enter fullscreen mode Exit fullscreen mode

When I visit the categories endpoint, I will see a similar data object that contains each category object, which contains it's own attributes object. When I am working with the data on the front end, I will need to keep this structure in mind. This structure comes from the fast_jsonapi serializer, and it allows me to choose what information gets sent to the front end when a request for an apple or category is made.

So in my controller, my index methods look like this:

def index
    # because I am using a get request to get only apples from the user's requested category, I am sending the category ID back to the controller as a query param. The category variable saves the query param and passes it to an activerecord query method that returns all the Apple instances that match the query. I am then alphabetizing the results and passing that collection to the serializer.
    category = params[:category_id].to_i
    @apples = get_apples_by_category(category)
    sorted_apples = @apples.order(:variety)
    render json: AppleSerializer.new(sorted_apples)
  end
Enter fullscreen mode Exit fullscreen mode

If I wanted to, I could specify here which attributes to include or exclude.

With my database, models, controllers, routes, actions, and serializers set up, I can now go to my endpoints and view serialized data in the browser. My back end is working as expected, and now it's time to go set up the front end.

Here's a link to the github repo for anyone interested in looking around:

https://github.com/jrrohrer/apple-expert-backend

And here's the post about setting up the front-end: https://dev.to/jrrohrer/rails-js-project-apple-expert-front-end-1jm7

Discussion (0)