malexanders
malexanders

Reputation: 3343

Rails 4, has_many through, return a list of available items for a particular category

I'm wondering if I should create a category controller in this case. Presently I have an items controller that has #index and #show actions. I was thinking about adding a filter for a specific category in the items#index action - but it seems much easier to do from a category controller. Here are my associations which effectively sets up a has_many through relationship between items and categories:

class Category < ActiveRecord::Base
    has_many :categorizations
    has_many :items, :through => :categorizations
end

class Item < ActiveRecord::Base
    has_many :categorizations
    has_many :categories, :through => :categorizations
end

class Categorization < ActiveRecord::Base
    belongs_to :item
    belongs_to :category
end

I need the API I am building to return "a list of available items for a particular category". It is very easy to this from a category controller(which I do not have yet) like so:

category.items

Is it better and more restful to do this? Or should I create a filter in my items#index action, which presently looks like this:

# Returns full list of items
def index
    @items = Item.all
    render json: @items
end

Of course, if you have any ideas that are more efficient/inline with best practices - please let me know!

Thanks!

EDIT - One Solution:

I decided to add a categories controller, so that I can access the available items for a particular category using the following relative path:

categories/:id/available_items

class CategoriesController < ApplicationController
    def available_items
        @available_items = Category.find(params[:id]).items.available
        render json: @available_items
   end
end

The criteria was to return all items, that are associated with a particular category, and have a status of 'available'.

EDIT:

I'm finding that Item.where(category: 1) isn't returning all items which are categorized under category 1. Please see below the byebug console output:

    1: class ItemsController < ApplicationController
    2:   # Returns full list of items
    3:   def index
    4:     @items = Item.all
    5:      byebug
=>  6:   end
    7: 

(byebug) Item.where(category: 1)
  Item Load (0.6ms)  SELECT "items".* FROM "items" WHERE "items"."category" = 1
#<Item::ActiveRecord_Relation:0x007fb5d1a37f08>
(byebug) Category.find(1).items
  Category Load (0.7ms)  SELECT  "categories".* FROM "categories" WHERE "categories"."id" = $1 LIMIT 1  [["id", 1]]
  Item Load (1.7ms)  SELECT "items".* FROM "items" INNER JOIN "categorizations" ON "items"."id" = "categorizations"."item_id" WHERE "categorizations"."category_id" = $1  [["category_id", 1]]
#<ActiveRecord::Associations::CollectionProxy [#<Item id: 1, title: "Gorgeous Cotton Pants", description: "Dolor dicta suscipit aut cupiditate quia officiis ...", price: 73960, status: 0, published_date: "2016-07-14 05:35:49", created_at: "2016-07-17 05:15:07", updated_at: "2016-07-17 05:15:07", seller_id: 1>, #<Item id: 5, title: "Sleek Marble Shoes", description: "Qui mollitia corporis qui placeat. Reiciendis ea s...", price: 35146, status: 0, published_date: "2016-07-14 05:45:02", created_at: "2016-07-17 05:15:07", updated_at: "2016-07-17 05:15:07", seller_id: 1>, #<Item id: 7, title: "Rustic Concrete Lamp", description: "Sit odio non exercitationem. Atque non sapiente vo...", price: 82016, status: 2, published_date: "2016-07-13 00:00:00", created_at: "2016-07-17 05:15:07", updated_at: "2016-07-17 05:15:07", seller_id: 1>, #<Item id: 10, title: "Awesome Wooden Table", description: "Possimus consequatur nulla. Quidem molestiae volup...", price: 59519, status: 2, published_date: "2016-07-09 00:00:00", created_at: "2016-07-17 05:15:07", updated_at: "2016-07-17 05:15:07", seller_id: 1>, #<Item id: 12, title: "Lightweight Concrete Bag", description: "Amet ullam assumenda eligendi consectetur quae. Bl...", price: 72081, status: 2, published_date: "2016-07-16 00:00:00", created_at: "2016-07-17 05:15:07", updated_at: "2016-07-17 05:15:07", seller_id: 2>, #<Item id: 13, title: "Mediocre Plastic Computer", description: "Excepturi modi est non qui iusto. Molestiae offici...", price: 94357, status: 2, published_date: "2016-07-15 00:00:00", created_at: "2016-07-17 05:15:07", updated_at: "2016-07-17 05:15:07", seller_id: 2>, #<Item id: 15, title: "Incredible Plastic Bag", description: "Vel voluptas ducimus soluta atque voluptatem eum. ...", price: 15661, status: 2, published_date: "2016-07-14 00:00:00", created_at: "2016-07-17 05:15:07", updated_at: "2016-07-17 05:15:07", seller_id: 2>, #<Item id: 16, title: "Lightweight Iron Watch", description: "Id sequi rerum dolor sit sunt nemo laborum. Omnis ...", price: 65306, status: 4, published_date: "2016-07-11 00:00:00", created_at: "2016-07-17 05:15:07", updated_at: "2016-07-17 05:15:07", seller_id: 1>, #<Item id: 17, title: "Rustic Linen Chair", description: "Explicabo qui ad nihil. Voluptatem placeat autem. ...", price: 39752, status: 4, published_date: "2016-07-04 00:00:00", created_at: "2016-07-17 05:15:07", updated_at: "2016-07-17 05:15:07", seller_id: 1>, #<Item id: 18, title: "Mediocre Copper Car", description: "Minus qui ut est non vero saepe. Qui sed quos et v...", price: 87765, status: 4, published_date: "2016-07-05 00:00:00", created_at: "2016-07-17 05:15:07", updated_at: "2016-07-17 05:15:07", seller_id: 1>]>

Upvotes: 0

Views: 276

Answers (1)

coreyward
coreyward

Reputation: 80041

Both of your proposed solutions are valid, but let's look at some code:

Option 1 — Rails Defaults

resources :categories do
  resources :items
end

# /categories/42/items/7

On its own, Rails would route this to the ItemsController, which would be responsible for doing something with the category_id that is passed in. If your application architecture/logic doesn't really ask to be done differently, I would start with this approach.

Option 2 — “Show a Category”

resources :categories, :items

class CategoriesController < ApplicationController
  def index
    @categories = Category.all
  end

  def show
    @category = Category.includes(:items).find params[:id]
  end
end

This approach is great if the notion of showing a controller would naturally show its items. This might not be appropriate in your case given the typical nature of many-to-many relationships, but it really depends on the context.

Option 3 — Three Tidy Controllers

If you find that your items#show or items#index methods are starting to get overly conditional, I would look into changing up your routing and adding a controller:

class CategorizedItemsController < ApplicationController
  # ...
end

resources :categories
resources :items
scope '/categories/:category_id/' do 
  resources :items, controller: :categorized_items
end

This is less obvious to another developer joining your project, and starts to make reasoning about your routes a little more challenging, so I wouldn't begin with it. It's a great solution, though, and one that you shouldn't hesitate to adopt if you find your existing controller hierarchy failing to represent your actions within the bounds of RESTful routing.

Cheers!

Upvotes: 1

Related Questions