1

New to rails. Following a tutorial on polymorphic associations, I bump into this to set @client in create and destroy.

@client = Client.find(params[:client_id] || params[:id])

I'm normally only used to that you can only find @client = Client.find(params[:id])

so how does this work with there being two params? How does the || work?

FavoriteClientsController.rb:

class FavoriteClientsController < ApplicationController

  def create
    @client = Client.find(params[:client_id] || params[:id])

    if Favorite.create(favorited: @client, user: current_user)
      redirect_to @client, notice: 'Leverandøren er tilføjet til favoritter'
    else
      redirect_to @client, alert: 'Noget gik galt...*sad panda*'
    end
  end

  def destroy
    @client = Client.find(params[:client_id] || params[:id])

    Favorite.where(favorited_id: @client.id, user_id: current_user.id).first.destroy
    redirect_to @client, notice: 'Leverandøren er nu fjernet fra favoritter'
  end

end

Full code for controller, models can be seen here

Using rails 5

VAD
  • 2,351
  • 4
  • 20
  • 32
sneglefar
  • 117
  • 9

4 Answers4

4

Expression: params[:client_id] || params[:id] is the same as:

if params[:client_id]
  params[:client_id]
else
  params[:id]
end
maicher
  • 2,625
  • 2
  • 16
  • 27
2

Wow thats an incredibly bad way to do it.

A very extendable and clean pattern for doing controllers for polymorphic children is to use inheritance:

class FavoritesController < ApplicationController

  def create
    @favorite = @parent.favorites.new(user: current_user)
    if @favorite.save 
      redirect_to @parent, notice: 'Leverandøren er tilføjet til favoritter'
    else
      redirect_to @parent, alert: 'Noget gik galt...*sad panda*'
    end
  end

  def destroy
    @favorite = @parent.favorites.find_by(user: current_user)
    redirect_to @parent, notice: 'Leverandøren er nu fjernet fra favoritter'
  end

  private

  def set_parent
    parent_class.includes(:favorites).find(param_key)
  end

  def parent_class
    # this will look up Parent if the controller is Parents::FavoritesController
    self.class.name.deconstantize.singularize.constantify
  end

  def param_key
     "#{ parent_class.naming.param_key }_id"
  end
end

We then define child classes:

# app/controllers/clients/favorites_controller.rb
module Clients
  class FavoritesController < ::FavoritesController; end
end

# just an example
# app/controllers/posts/favorites_controller.rb
module Posts
  class FavoritesController < ::FavoritesController; end
end

You can then create the routes by using:

Rails.application.routes.draw do

  # this is just a routing helper that proxies resources
  def favoritable_resources(*names, **kwargs)
    [*names].flatten.each do |name|
      resources(name, kwargs) do
        scope(module: name) do
          resource :favorite, only: [:create, :destroy]
        end
        yield if block_given?
      end
    end
  end

  favoritable_resources :clients, :posts
end

The end result is a customizable pattern based on OOP instead of "clever" code.

max
  • 96,212
  • 14
  • 104
  • 165
  • Thanks for a thorough explanation. Don't completely understand how the routes works though. Anyways: What are the advantages of this approach compared to the one in the tutorial? – sneglefar Jan 27 '17 at 12:31
  • It will generate `POST /clients/:client_id/favorite` etc which will route to `Clients::FavoritesController`. Try running `bin/rake routes` to see the full output. – max Jan 27 '17 at 12:34
  • The main advantage is that you can add different logic for when favorites belong to Clients and Posts in an OOP way instead of making a huge messy controller which sniffs the params. – max Jan 27 '17 at 12:35
  • Ah.. So the original tutorial will "clunge up" my controller whenever I need to add a Post to favorite, a team to favorite, a coffee to favorite, etc? – sneglefar Jan 27 '17 at 12:37
  • Correct, you end up with a ton of code paths in a single controller like `if @parent.is_a?(Team)`. – max Jan 27 '17 at 12:38
  • Ah! Got it! Thanks a ton for the thorough explanation! So if I don't want a whole lot of things to favorite, but basically just one: favoriting clients - isn't doing a polymorphic association overkill? Should I use use a has_many :through? instead – sneglefar Jan 27 '17 at 12:41
  • 1
    If you only ever are going to favorite one thing you don't need a polymorphic relationship - not using polymorphism removes a lot of the headaches when joining (since the joined table cannot be known beforehand). But you don't have to use `through`. Thats for creating indirect relationships through a joining model. – max Jan 27 '17 at 12:44
  • For example if you have `Country -> City -> Steet` you would use `through ` to setup relationships between country and street without duplicating the foreign keys. – max Jan 27 '17 at 12:47
1

The tutorial which teaches you to do

Client.find(params[:client_id] || params[:id])

is a super-duper bad tutorial :) I strongly recommend you to switch to another one.

Back to the topic: it is logical OR: if first expression is neither nil or false, return it, otherwise return second expression.

Community
  • 1
  • 1
Andrey Deineko
  • 51,333
  • 10
  • 112
  • 145
1

That thing is just trying to find client by client_id if there is one in the request params. If not it's trying to find client by id.

However such practic can make you much more pain than profit.

VAD
  • 2,351
  • 4
  • 20
  • 32
  • Thanks. I'm thinking the tutorialmaker does it for a reason though - what would that reason be? – sneglefar Jan 27 '17 at 11:59
  • Can't be sure about it:). Maybe he wanted the user could provide `id` as well as `client_id`. Anyway I don't really see any sense here. – VAD Jan 27 '17 at 12:06