matthewalexander matthewalexander - 1 year ago 85
Ruby Question

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>]>

Answer Source

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!

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download