Rails, Polymorphic Association - rendering associated instances only
Asked Answered
M

0

0

I'm trying to learn how to use polymorphic associations in my Rails 5 app.

I have models called Organisation, Proposal and Package::Bip.

The associations are:

Organisation

 has_many :bips, as: :ipable, class_name: Package::Bip
    accepts_nested_attributes_for :bips,  reject_if: :all_blank, allow_destroy: true

Proposal

has_many :bips, as: :ipable, class_name: Package::Bip
    accepts_nested_attributes_for :bips,  reject_if: :all_blank, allow_destroy: true

Package::Bip

belongs_to :ipable, :polymorphic => true, optional: true, inverse_of: :bip

Package::Bip can be associated with either of Organisation or Proposal. I'm struggling to figure out how to show the Package::Bips that only belong to proposal in my proposal show and the same for organisation.

My package::bip table has these 2 columns:

#  ipable_id      :integer
#  ipable_type    :string

The type gets set to either Proposal or Organisation.

In my proposal show, I have:

<% if @proposal.bips.present? %>
    <%= link_to package_bips_path(@proposal) do %> 
<% end %>

I think the (@proposal) should be the constraining factor in deciding which package_bip instances to return. But, it isn't. I'm not sure what it's doing because all package_bips get rendered (including those that are associated with an organisation).

In my proposal controller, I have:

    def show
    @images = @proposal.images
    @bips = @proposal.bips.where(ipable_type: 'Proposal')
  end

I added the above where scope to try and force the show to use only those that have Proposal set in the ipable_type attribute - but that doesnt do any thing to help.

I'm not sure if I'm supposed to make an if statement in the Package::Bips controller to see whether the ipable_type is proposal. I don't understand how to exctract associated instances of the polymorphic object so that I can just show the relevant ones in an index on the proposal show page (with the index being populated only with the instances that belong to the proposal).

I tried writing the index action of the Package::Bips controller as:

def index


    if params[:ipable_type]
      @bips = Package::Bip.where(ipable_type: 'Proposal')
    elsif params[:ipable_type]
      @bips = Package::Bip.where(ipable_type: 'Organisation')
    else
       @bips = Package::Bip.all
    end

    # authorize @bips
  end

That doesnt do anything at all. So I'm not sure why it at least doesnt give an error message.

Rendering the right view

In my proposals/show - I have:

<%= link_to package_bips_path(@proposal) do %>

That link goes to my views/package/bips/index.html. That view has:

  <% @bips.each do |ip| %>. 

That is currently listing out all of the bips (for both organisation and proposal). I thought that the @proposal in the link in proposal/show might constrain things, or the index action method in my bips controller might(above) might help. But neither of these things filter the instances.

If I write:

 <%= @proposal.bips.first.title %> 

in my proposals show view, it works to render the title.

Now my challenge is how to let the bip index view render the correct set of instances (only proposal or only organisation) depending on which page sent the request to render that view. Maybe I could write request referer to see if its an organisation controller or proposals controller request?

TRYING TO RENDER THE VIEW

views/proposals/show.html.erb

<% if @proposal.bips.present? %>
    <%= link_to package_bips_path(@proposal) do %> 
      <h6 style="margin:2%; "> Intellectual Property </h6>
    <% end %>
<% end %>

views/bips/index.html.erb

<% @bips.each do |ip| %>
   <%= ip.title.titleize %>
   <%= ip.classification.humanize %>
<% end %>

views/bips/show.html.erb

<%= @bip.title.titleize %></h5><p><%= @bip.status %>

proposals controller

def index
    @proposals = Proposal.all
    # @bips = @proposal.bips
    # authorize @proposals
  end

  def show
    @images = @proposal.images
    @bips = @proposal.bips#.where(ipable_type: 'Proposal')
  end

bips controller

def index
    # if params[:status]
    #   @bips = Package::Bip.where(:status => params[:status])
    # else
    #   @bips = Package::Bip.all
    # end  

    if params[:ipable_type]
      @bips = Package::Bip.where(ipable_type: 'Proposal')
    elsif params[:ipable_type]
      @bips = Package::Bip.where(ipable_type: 'Organisation')
    else
       @bips = Package::Bip.all
    end

    # authorize @bips
  end

New attempt

I found this:

http://rails-bestpractices.com/posts/2010/09/23/generate-polymorphic-url/

I don't understand whether 'parent' where used in the article is some kind of keyword, or if i need to define it somewhere.

Im trying to nest my routes, so now:

resources :proposals do 
    namespace :package do
      resources :materials
      resources :insights
      resources :facilities
      resources :participants
      resources :fundings
      resources :facts
      resources :bips 
    end

... and the same for organisations.

But, I am getting errors. I think maybe the article has jumped forward a few steps. Are there conventions for using polymorphic paths?

I've now found this resource:

http://www.codequizzes.com/rails/learn-rails/polymorphism

The create action looks unusual to me. Can anyone explain the create action concept used in this approach?

NEXT ATTEMPT

new index action in the Package::Bips controller:

def index

    if Package::Bip.ipable_type: 'Proposal'
      @bips = Package::Bip.where(ipable_type: 'Proposal')
    elsif Package::Bip.ipable_type: 'Organisation'
      @bips = Package::Bip.where(ipable_type: 'Organisation')
    else
       @bips = Package::Bip.all
    end

But now, that still isn't letting me try to see if it works.

When I try to render a show view for a proposal, I get an error that says:

undefined method `package_bips_path' for #<#<Class:0x007fa72ea9b730>:0x007fa72ea93ee0>

This is the link that the show page contains:

<% if @proposal.bips.present? %>
    <%= link_to package_bips_path(@proposal) do %> 
    <h6 style="margin:2%; "> Intellectual Property </h6>
<% end %>

ANOTHER GO AT IT

bips controller, index action:

 if params[:ipable_type] == 'Proposal'
      @bips = Package::Bip.where(ipable_type: 'Proposal')
    else params[:ipable_type] ==  'Organisation'
      @bips = Package::Bip.where(ipable_type: 'Organisation')
    # else
    #    @bips = Package::Bip.all
    end

proposals show:

<% if @proposal.bips.present? %>
    <%= link_to proposal_package_bips_path(@proposal) do %> 
    <% end %>
<% end %>

views/bips/index

<div class="container-fluid" style="margin-bottom: 50px">
  <h2 style="text-align: center">Intellectual Property Assets</h2>

    <div class="row">
      <div class="col-sm-10 col-sm-offset-1">
        <div class="row" style="margin-top:50px; margin-bottom:50px">
          <div class="col-md-8 col-md-offset-2">
              <span style="margin-right:50px; padding-left:50px"> 
                <%= link_to "All", action: :index %> 
              </span>  
              <span style="color:grey">·</span>
                <span style="margin-right:50px; padding-left:50px"> 
                  <%#= link_to "Intellectual Property we can offer", package_bips_path(:status => "Offered") %> 
                </span>
                <span style="color:grey">·</span>
                <span style="margin-right:50px; padding-left:50px">
                  <%#= link_to "Intellectual Property sought", package_bips_path(:status => "Sought") %>
                </span>
          </div>
        </div>
      </div>
    </div> 


    <div class="row">
      <div class="col-xs-10 col-xs-offset-1">
        <div class="table-responsive" style="margin-left:30px; margin-top:15px">
          <table class="table table-bordered">
            <tr>
              <td> <h5>Title</h5> </td>
              <td> <h5>Asset</h5> </td>
              <td> <h5>Added</h5> </td>

             <%# if policy(@package_ips).update? %> 
                <td> <h5>Manage this asset</h5></td> 
             <%# end %>

            </tr>
            <% @bips.each do |ip| %>


            <tr>

              <td>
                  <%= ip.title.titleize %>
              </td>
              <td>
                  <%= ip.classification.humanize %>
              </td>
              <td>
                  <%= ip.created_at.try(:strftime, '%e %B %Y') %>
              </td>

              <%# if policy(@package_ips).update? %>
                <td>  
                   <%#= link_to 'More details', package_bip_path(ip) %> <span style="color:grey">·</span>
                   <%#= link_to "Edit", edit_package_ip_organistion_path(@organisation) %>
                   <!-- <span style="color:grey">·</span> -->
                   <%#= link_to 'Destroy', ip, method: :delete, data: { confirm: 'Are you sure?' } %>
                </td>  
              <%# end %>   

            </tr> 
            <% end %> 
          </table>
        </div>
      </div>
    </div>          
</div>

This doesnt work (still renders wrong bips), BUT besides being incorrect, I can't have any links on the show page. If i want to link to :status params on the bips show view, it starts from a path that is not prefixed by proposal/organisation - so it doesnt work.

NEXT ATTEMPT

The bips controller index action now has:

 if params[:ipable_type] == "Proposal"
      @bips = Package::Bip.where(:ipable_type == 'Proposal')
    else params[:ipable_type] ==  'Organisation'
      @bips = Package::Bip.where(:ipable_type == 'Organisation')

This now renders bips, but it renders proposal bips in the organisation index (as well as the proposal index).

I'm stuck!

In the console, I can do:

p = Proposal.last
  Proposal Load (6.1ms)  SELECT  "proposals".* FROM "proposals" ORDER BY "proposals"."id" DESC LIMIT $1  [["LIMIT", 1]]
 => #<Proposal id: 15, user_id: 4, title: "testing filter",  created_at: "2016-11-08 00:59:09", updated_at: "2016-11-08 00:59:09"> 
2.3.1p112 :123 > p.bips
  Package::Bip Load (0.5ms)  SELECT "package_bips".* FROM "package_bips" WHERE "package_bips"."ipable_id" = $1 AND "package_bips"."ipable_type" = $2  [["ipable_id", 15], ["ipable_type", "Proposal"]]
 => #<ActiveRecord::Associations::CollectionProxy [#<Package::Bip id: 17, identifier: "testing filter",  ipable_id: 15, ipable_type: "Proposal", created_at: "2016-11-08 00:59:09", updated_at: "2016-11-08 00:59:09"]> 

This is correct. I can also do:

o = Organisation.first
  Organisation Load (1.1ms)  SELECT  "organisations".* FROM "organisations" ORDER BY "organisations"."id" ASC LIMIT $1  [["LIMIT", 1]]
 => #<Organisation id: 1, title: "bhjhghdddd",  created_at: "2016-10-21 05:20:39", updated_at: "2016-11-06 21:36:30"> 
2.3.1p112 :125 > o.bips
  Package::Bip Load (0.4ms)  SELECT "package_bips".* FROM "package_bips" WHERE "package_bips"."ipable_id" = $1 AND "package_bips"."ipable_type" = $2  [["ipable_id", 1], ["ipable_type", "Organisation"]]
 => #<ActiveRecord::Associations::CollectionProxy []> 

This is also correct.

I just can't find a way to get the index populated with the right bips in the code.

ANOTHER ATTEMPT

I'm trying to use the advice in this tutorial: http://www.codequizzes.com/rails/learn-rails/polymorphism

It suggests:

# views/articles/show.html.erb
<%= render @article.comments %>

# views/comments/_comment.html.erb
<%= comment.body %><br />
The render @article.comments in the show page automatically knows to load a file called views/comments/_comment.html.erb. This is Rails magic, so just memorize this.

Taking that, I made a partial in my views/package/bips folder called _bips.html.erb.

In my proposals show view, I tried:

<%= render @proposal.bips %>

I get an error that says:

Missing partial package/bips/_bip with {:locale=>[:en], :formats=>[:html], :variants=>[], :handlers=>[:raw, :erb, :html, :builder, :ruby, :coffee, :jbuilder]}. Searched in:
  * "/Users/d/cv/cflive/app/views"

That is the right location. I'm wondering if this problem might have something to do with the routes being nested. My routes file now has:

resources :proposals do 
    namespace :package do
       resources :bips 
    end

Can anyone see why this approach isn't working?

Muns answered 6/11, 2016 at 22:12 Comment(47)
Yeah, you shouldn't need to jump through the hoops you've tried (eg that where clause) you should be able to just do: @proposal.bips and it should Just Work the way you're expecting it to. This means there's something unexpected that's interfering with this happening. I don't know for sure, but we can try changing just-one-thing one at a time (then putting it back) to see if that fixes it... my first suggestion might be to try inverse_of :bip on the Bips association-definition... I'm not sure it works for polymorphic associations (though maybe it does). but worth trying.Chameleon
I tried to add inverse_of: :proposal to the bips association, but it gives an error that says: Could not find the inverse association for bips (:proposal in Package::Bip)Muns
It's strange. In the console, I can write: Organisation.first.bips and get a result. When I try: Proposal.first.bips, I get an error that says: NoMethodError: undefined method `bips' for #<Proposal:0x007fa7641eeb38>Muns
Section 3.5 guides.rubyonrails.org/… says that inverse of doesnt work with polymorphic assoc.Muns
Cool - so remove that and see what happens :) If it fixes it then yay and I'll make that into an answer. if it doesn't, then we'll know to look for something else :)Chameleon
I didn't have it there in the first place. I tried adding it and go the error that says: Could not find the inverse association for bips (:proposal in Package::Bip)Muns
Now, I have: belongs_to :ipable, :polymorphic => true, optional: true, inverse_of: :bip in my Package::Bip model (as i did before i found this problem). I get no errors when I try to find organisation.bips, but I get an error when i try to find proposal.bips. The error says: NoMethodError: undefined method `bips' for #<Proposal:0x007fa7641eeb38>Muns
If I remove ", inverse_of: :bip" from Package::Bip.rb, then I don't get console errors when I try proposal.bips (so it works in the console the same way as organisation), but I'm still stuck for how to separate out the instances so that I just show proposal bips in the proposal show viewMuns
Ok, so we know not to use inverse-of... that was a fix you attempted, but didn't work and isn't going to work, so we need to remove it for now and try something else instead. Here's the rails guide with a "classic" example of polymorphism: guides.rubyonrails.org/… Now the basic example doesn't use class_name and when I look at examples that do... it seems to be expecting a string containing the classname, not an actual class (eg instead of class_name: Package::Bip try class_name: "Package::Bip") and see if that makes any difference.Chameleon
"but I'm still stuck for how to separate out the instances so that I just show proposal bips in the proposal show view " it should do that automatically for you - you shouldn't need to do something different to the basic example given to make it separate them. it will do that by using the id and type of the bips themselves. eg if you have a proposal with id of 42 then if you type proposal.bips it will look in the Bip table for Bips that have type=Proposal and id=42... that's just what Rails does for you. So if it isn't doing that, we need to find out why.Chameleon
Another thing to try (in the console, after you've removed inverse_of and also tried the class_name fix) is to try: Proposal.first.bips.to_sql - which will; show us the sq that rails is trying to generate from the association. That might help us find out how rails is getting confused, and thus how we can try to fix it.Chameleon
hmm - I don't have anywhere to type proposal.bips in my code. Ill update my post in a second to show the current problemMuns
in your code? Right now we're just testing this stuff in the console until it works... :)Chameleon
In my console - writing Proposal.first.bips works just fineMuns
Proposal.first.bips.to_sql Proposal Load (1.8ms) SELECT "proposals".* FROM "proposals" ORDER BY "proposals"."id" ASC LIMIT $1 [["LIMIT", 1]] => "SELECT \"package_bips\".* FROM \"package_bips\" WHERE \"package_bips\".\"ipable_id\" = 13 AND \"package_bips\".\"ipable_type\" = 'Proposal'" 2.3.1p112 :107 >Muns
ok... just to make sure I'm on the right page: finding the bips for any given proposal just works. and the same for any given organisation? And now you want the index page to list all bips for all proposals (but only for proposals) if the person selects to see proposal-bips (and likewise for organisation bips)?Chameleon
Right now, yes - I can find bips that belong to a specific proposal or organisation (in the console). In the code, I can only get a list of all bips (both organisation and proposal and I can't separate out the bips that belong to a specific proposal/org).Muns
ok, just so I'm being very clear... when you say "in the code" do you mean you are using exactly the same code as what you type into the console? ie you are calling bips on a single instance of a proposal and/or organisation? or are you talking about the code you've got above in your index controller - which is quite different to that, and is searching for all bips belonging to all proposals? If the latter - have you tried doing that in your console (eg Package::Bip.where(ipable_type: 'Proposal')) to see what you get?Chameleon
I mean in the views, I'm trying to figure out how I can use the bip view pages to render the view based on whether its a proposal or an organisation. I copied what I have for the moment. I can't find a place, in the bip views folder, to specifically reference '@proposal' or '@organisation'. I'm not sure if I can use the views in the bips folder to display the relevant subset.Muns
Package::Bip.where(ipable_type: 'Proposal') Package::Bip Load (1.3ms) SELECT "package_bips".* FROM "package_bips" WHERE "package_bips"."ipable_type" = $1 [["ipable_type", "Proposal"]] => #<ActiveRecord::Relation [#<Package::Bip id: 14, ipable_id: 13, ipable_type: "Proposal", created_at: "2016-11-06 21:41:43", updated_at: "2016-11-06 21:41:43", title: "asd", status: nil, classification: "Know_how">, #<Package::Bip id: 16, identifier: "dd", ipable_id: 14, ipable_type: "Proposal", created_at: "2016-11-07 04:22:44", updated_at: "2016-11-07 04:22:44", title: "dd">]>Muns
I found this: github.com/voxdolo/render-polymorphic, which might have some clues about how to get this working. I want to try to figure this out without a gem if I canMuns
Why do you need to specifically call @proposal or @organisation in the views? Do you have access to which one it is in the controller? If so - how about in the controller, you do something like @bips = @proposal.bips then in the view, all you need to worry about is that there is an @bips set up. In the controller, you can tell which one you have by looking at params eg if params[:organisation_id].present?Chameleon
My proposal controller has: def show @bips = @proposal.bips#.where(ipable_type: 'Proposal') endMuns
But it doesnt do anything.Muns
In my bips controller, I've tried defining the index action as I posted above, but that doesnt do anything either.Muns
@bips = Package::Bip.where(ipable_type: 'Proposal') does this work in your console?Chameleon
if params[:ipable_type] ... elsif params[:ipable_type] what is being tested here? I'd do something like: if params[:ipable_type] == 'Proposal'... elsif params[:ipable_type] == 'Organisation' otherwise you'll always end up in the final else-clause (and it'll always give you all the bips)Chameleon
do you think i've written the index method wrong? I have a test to see if the ipable_type is Proposal or Organisation and then a fallback (which there should never be a reason to use) for all bips. Do you think I wrote it wrongly?Muns
What you've posted above does't actually test if the ipable_type is Proposal or not... it just tests if ipable_type exists at all. So if what you wrote above is what you are actually using, then yes, it's not doing what you hope it's doing :)Chameleon
hmmm - doesnt the "where(ipable_type: 'Proposal')" get the ones that are a Proposal?Muns
This codequizzes.com/rails/learn-rails/polymorphism suggests I should be writing something in the create action of the bips controller to define parent. Do you think that is an idea? The advice for that says: The create action determines if the parent for a given comment is an article or another comment. The @comment = @parent.comments.new(params[:comment]) associates the comment with its parent.Muns
"doesnt the "where(ipable_type: 'Proposal')" get the ones that are a Proposal?"... yes, but... you haven't ever reached that code, because you don't know whether the user has chosen to show Proposal or Organisation type... if params[:ipable_type] literally says "if the user has passed anything at all" and will thus be true even if the user chooses an ipable_type of "Banana" :)Chameleon
"suggests I should be writing something in the create action of the bips controller to define parent" - how about we start with getting the index action right, then move on to that.Chameleon
ahhh - ok. I see. I'll give it another go.Muns
I had another go at it. I can render a view with the right instances, but the show view still isn't right because i can't have any links in that page.Muns
Actually - I spoke too soon. These attempts aren't working. They do filter the list, but I have no idea what rules are being employed to do that filtering.Muns
I have 4 Package::Bips in my db. 3 belong to organisation and 1 to proposal. The same 2 get displayed in each of the organisation/1/package/bips and the proposal/13/package/bips. My ipable_type param doesnt seem to impact on the filtering at all.Muns
Hi @TarynEast - any more ideas on where I might be going wrong? I'm going crazy trying to find new ways to approach this problem.Muns
what do you mean by "can't have any links in that page" Do you want links in that page? what have you tried to add links?Chameleon
Also - are we now talking about the show page (instead of the index page?) can we keep that separate? (ie can you ask a new question about that so our conversations don't get mixed up)? Is the index page working now?Chameleon
No - the index page doest work yet. I'm still struggling to sort it out. in each of my organisation show and my proposal show page, Im trying to render an index of each bip that belongs to that specific organisation or proposal.Muns
I have tried to add links but none of them work. I've commented all of them out because they start from package_bips instead of proposal_package_bips etc.Muns
ok, please ask a new question to address the links-in-page problem (it's likely a different bug). Add the code you've tried - all of them, and what errors you got when you tried them. This question is for "why is it rendering proposal bips when I want organisation bips (and vice versa)". Have we sorted that problem yet? if not... can I recommend we start afresh with what you have now... this convo is really stretching a bit long ;)Chameleon
the first problem isn't solved. i still cant get the right bips to return. maybe this question is now too messy. ill try again with a new one starting from where I am with the current attempt.Muns
ps: I'm not permitted to ask another question for 90mins. Ill do some more research until that time passes. Thanks againMuns
Thanks - i just find myself getting too confused - starting fresh will help :) Point me at your new question(s) when you put them up and I'll head over there :)Chameleon
Hi @TarynEast - im finally allowed to post this: #40519387 Thanks so much for having a lookMuns

© 2022 - 2024 — McMap. All rights reserved.