Rails 5 - using polymorphic associations - rendering the views
Asked Answered
A

1

5

I'm trying to learn how to use polymorphic associations in my Rails 5 app. I recently asked this question, but I edited it so many times to show all the things I was trying, it has become messy

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 ipable_type gets set to either Proposal or Organisation.

In my proposal show, I have:

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

I found this post. I understand it to mean I should also be able to try this: I have also tried:

<% if @proposal.bips.present? %>
    <%= render @proposal.bips %>
<% 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 index
    @proposals = Proposal.all
    # @bips = @proposal.bips
    # authorize @proposals
  end


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

In my Package::BipsController, I have

def index

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

But, when I save all of this and try it, I get the wrong results.

At the moment, there are 4 instances of Package::Bip in the db.

Package::Bip.pluck(:ipable_type)
   (0.8ms)  SELECT "package_bips"."ipable_type" FROM "package_bips"
 => ["Proposal", "Proposal", "Proposal", "Organisation"] 

3 belong to a proposal and 1 belongs to an organisation.

The 3 that belong to proposals each belong to a different proposal.

Package::Bip.pluck(:ipable_id)
   (0.8ms)  SELECT "package_bips"."ipable_id" FROM "package_bips"
 => [15, 13, 16, 1] 

My objective is that a proposal show view or an organisation show view should only show the Bips that belong to that specific proposal.

However, when I render my proposal show view, which has:

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

In turn, my views/package/bips/index.html.erb has:

<% @bips.each do |ip| %>
   <%= ip.title.titleize %>
      <%#= link_to 'More details', package_bip_path(ip) %> 

I expect this view to render a list containing 1 package_bip instance. Instead I get all 4 instances listed (2 of the proposal.bips belong to a different proposal and one of the bips belongs to an Organisation). None of those should be rendered in the index.

Further, I can't add a link in my bips/index.html.erb to the bips/show.html.erb. This is because that link looks like:

<%= link_to 'More details', package_bip_path(ip) %> <

When I try to render the index with that link in it, I get an error that says:

undefined method `package_bip_path' for #<#<Class:0x007fbce68389c0>:0x007fbcfdc31c18>

I think maybe the link needs to include either proposal or organisation.

My routes are nested:

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

resources :organisations do 
  namespace :package do
    resources :bips 
  end

From the console, I can search with the filters I want to apply.

p = Proposal.last
  Proposal Load (4.8ms)  SELECT  "proposals".* FROM "proposals" ORDER BY "proposals"."id" DESC LIMIT $1  [["LIMIT", 1]]
 => #<Proposal id: 16, user_id: 4, title: "test tenor", description: "test tenor",  created_at: "2016-11-08 21:58:38", updated_at: "2016-11-08 21:58:38"> 
2.3.1p112 :199 > p.bips
  Package::Bip Load (0.3ms)  SELECT "package_bips".* FROM "package_bips" WHERE "package_bips"."ipable_id" = $1 AND "package_bips"."ipable_type" = $2  [["ipable_id", 16], ["ipable_type", "Proposal"]]
 => #<ActiveRecord::Associations::CollectionProxy [#<Package::Bip id: 19, identifier: "test tenor", description: nil, conditions: "test tenor", ipable_id: 16, ipable_type: "Proposal", created_at: "2016-11-08 21:58:38", updated_at: "2016-11-08 21:58:38", title: "test tenor", status: nil, classification: "Copyright">]> 
2.3.1p112 :200 > p.bips.count
   (3.5ms)  SELECT COUNT(*) FROM "package_bips" WHERE "package_bips"."ipable_id" = $1 AND "package_bips"."ipable_type" = $2  [["ipable_id", 16], ["ipable_type", "Proposal"]]
 => 1 

But this doesn't carry over to the code in any of my attempts.

Can anyone refer me to resources that can help me figure out how to setup my polymorphic views so that they work to filter correctly and so that I can use the typical index links (e.g., to show/edit) so that they can be recognised as belonging to one or the other of Proposal/Organisation?

UPDATE

I've now found this post.

I tried adopting the process it suggests by:

  1. Changing create action in proposals controller to:

    def create if params[:organisation_id] parent = Organisation.find(params[:organisation_id]) elsif params[:proposal_id] parent = Proposal.find(params[:proposal_id]) end

    bip = Package::Bip.new
    Package::Bip.ipable = parent
    # @bip = Package::Bip.new(bip_params)
    # authorize @bip
    
    respond_to do |format|
      if @bip.save
        format.html { redirect_to @bip }
        format.json { render :show, status: :created, location: @bip }
      else
        format.html { render :new }
        format.json { render json: @bip.errors, status: :unprocessable_entity }
      end
    end
    

    end

  2. Changing the proposal view to:

When I try this, I get no errors, but all of the Package::Bips are displayed in a proposal show view.

This is despite there being Package::Bips which have an organisation id.

Package::Bip.all.pluck(:ipable_type)
   (0.9ms)  SELECT "package_bips"."ipable_type" FROM "package_bips"
 => ["Proposal", "Proposal", "Proposal", "Organisation", "Proposal"] 

When I restart the server and try again, I get an error message that says:

undefined method `empty?' for #<Class:0x007ff20920e218>

The error message points to my new line:

<%= polymorphic_path(@proposal, Package::Bip) %>

The log shows:

Started POST "/__better_errors/3f34303f5f5c670f/variables" for ::1 at 2016-11-13 10:58:13 +1100
  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", 15], ["ipable_type", "Proposal"]]

This post shows a similar problem, which is solved by adding '[]' to the path.

When I then try:

<%= link_to polymorphic_path([@proposal, Package::Bip]) do  %>

I still get a list of all of the bips (even those which belong to an organisation or a proposal with a different id).

What I can see from the log though might be a clue (if it is - its going over my head).

Started GET "/proposals/15/package/bips" for ::1 at 2016-11-13 11:24:32 +1100
Processing by Package::BipsController#index as HTML
  Parameters: {"proposal_id"=>"15"}
  User Load (1.1ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2  [["id", 4], ["LIMIT", 1]]
  Rendering package/bips/index.html.erb within layouts/application
  Package::Bip Load (0.6ms)  SELECT "package_bips".* FROM "package_bips"
  Rendered package/bips/index.html.erb within layouts/application (5.4ms)

the log doesnt seem to show any indication that it is applying proposal or proposal id filters to the package_bips.

This doesnt make any sense to me, because my routes are only available with a prefix of either organisation or proposal:

rake routes | grep package_bip
            organisation_package_bips GET      /organisations/:organisation_id/package/bips(.:format)                  package/bips#index
         new_organisation_package_bip GET      /organisations/:organisation_id/package/bips/new(.:format)              package/bips#new
        edit_organisation_package_bip GET      /organisations/:organisation_id/package/bips/:id/edit(.:format)         package/bips#edit
             organisation_package_bip GET      /organisations/:organisation_id/package/bips/:id(.:format)              package/bips#show
                proposal_package_bips GET      /proposals/:proposal_id/package/bips(.:format)                          package/bips#index
             new_proposal_package_bip GET      /proposals/:proposal_id/package/bips/new(.:format)                      package/bips#new
            edit_proposal_package_bip GET      /proposals/:proposal_id/package/bips/:id/edit(.:format)                 package/bips#edit
                 proposal_package_bip GET      /proposals/:proposal_id/package/bips/:id(.:format)                      package/bips#show

Further still, when I try to use the show path in the last linked post, in my views/package/bips/index.html.erb as:

 <% @bips.each do |ip| %>
    <%= link_to polymorphic_path([@ipable, ip]) %>

I then get an error which says:

undefined method `package_bip_path' for #<#<Class:0x007f9e8ac29248>:0x007f9e939c9508>

TARYN'S SUGGESTIONS FOR SEARCHING FOR PROBLEMS

Background. In my proposal controller, my proposals are defined as:

def set_proposal
   @proposal = Proposal.find(params[:id])
end

In the console, I try:

params = {:proposal_id => 15}
 => {:proposal_id=>15} 


2.3.1p112 :031 > @proposal = Proposal.find(params[:proposal_id])
  Proposal Load (0.7ms)  SELECT  "proposals".* FROM "proposals" WHERE "proposals"."id" = $1 LIMIT $2  [["id", 15], ["LIMIT", 1]]
 => #<Proposal id: 15, user_id: 4, title: "testing filter", description: "testing filter", byline: "testing filter", nda_required: true, created_at: "2016-11-08 00:59:09", updated_at: "2016-11-08 00:59:09"> 

2.3.1p112 :033 > @proposal.bips
  Package::Bip Load (0.7ms)  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", description: nil, conditions: "testing filter", ipable_id: 15, ipable_type: "Proposal", created_at: "2016-11-08 00:59:09", updated_at: "2016-11-08 00:59:09", title: "testing filter", status: nil, classification: "Patent">, #<Package::Bip id: 21, identifier: "dd", description: nil, conditions: "dd", ipable_id: 15, ipable_type: "Proposal", created_at: "2016-11-10 22:47:54", updated_at: "2016-11-10 22:47:54", title: "ldsjflkjklsdjfl", status: nil, classification: "Design">]> 
2.3.1p112 :034 > @proposal.bips.count
   (6.3ms)  SELECT COUNT(*) FROM "package_bips" WHERE "package_bips"."ipable_id" = $1 AND "package_bips"."ipable_type" = $2  [["ipable_id", 15], ["ipable_type", "Proposal"]]
 => 2 

This gives the correct result.

In the proposal controller, show action, I have:

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

When I try this, I can explore the proposal params:

params
<ActionController::Parameters {"controller"=>"proposals", "action"=>"show", "id"=>"15"} permitted: false>
(byebug) proposal_params
*** ActionController::ParameterMissing Exception: param is missing or the value is empty: proposal

nil

Others who have this problem attribute it to the 'require' statement in their strong params method.

My proposal_params is defined as:

def proposal_params
      params.require(:proposal).permit(:title, :description, :byline, :nda_required, :randd_maturities_list,
        #Package
        bips_attributes:            [:id, :status, :classification, :identifier, :conditions, :title, :_destroy,
          tenor_attributes:           [:id, :express_interest, :commencement, :expiry, :enduring, :repeat, :frequency, :_destroy]

        ],


       )
    end

I think its correct.

These messages are odd though, because I can also go through each whitelisted item in the bye bug statement and get the right response. For example:

 @proposal.id
15

When I try exploring with bye bug in the Package::bips controller, index action, I can see:

params.inspect
"<ActionController::Parameters {\"controller\"=>\"package/bips\", \"action\"=>\"index\", \"proposal_id\"=>\"15\"} permitted: false>"

The permitted: false attribute appears to be a problem all over my code.

That's my next line of enquiry.

Armure answered 10/11, 2016 at 1:58 Comment(26)
Ok, so you're not currently showing us how you're setting up @proposal in your controller. (it may not be relevant, but we'll see). In your console, it'd be good if you literally do exactly what your controller would do - eg instead of p = Proposal.last do params = {:proposal_id => 0} then @proposal = Proposal.find(params[:proposal_id]) (or whatever is in your controller). Then try @proposal.bips.Phile
(Note: some databases do funny things when the id is 0 (or even one) which is why I've deliberately used that as an example - it can assume you mean 'all" instead of an actual record with an id of 0).Phile
Next - add a lot of debugging statements to your controller code to check what code is being run and what values are there at each point eg puts "I'm in the show controller with @proposal of: #{@proposal.inspect} and @bips: #{@bips.inspect}" - this is how you'll spot inconsistencies.Phile
Cool - trying this nowArmure
I've not been able to figure this out. But, I have found this: #20651346 Maybe it's not possible to use namespaced routes with polymorphic associations at all.Armure
hmmm could be. though that question was asked in 2013 and has a suggested bugfix... which is likely to have been merged into the rails codebase by now. It looks like they're using "polymorphic_path_for" which I've never heard of (but then I have't done a lot of polymorphic links) - might be worth googling how that works to see how the routing is done?Phile
I've been trying to use it. It doesnt give any errors but doesnt filter the bips. It still returns all instances and doesnt seem to identify them by reference to the proposal/organisation.Armure
Ok, so looking at the log when you use the polymorphic path the first time (ie with an actual proposal), the routing part works. The params are coming through correctly - so that part isn't wrong when you specify exactly which kind of ipable you use. Agreed the sql indicates that it is not addin gthe type-filter to the Bip-fetch... so... what line of code is generating that? Can you do a puts/log just before that line to make it clera which part of the code you are getting to?Phile
For that, I'm assuming you are using params[:proposal_id] in order to determine that you should fetch out a proposal's-worth of bips... (if you're passing in the id of a specific proposal, then you'd expect to get that proposal out, then get that proposal's bips out).Phile
As to this part: <% @bips.each do |ip| %> <%= link_to polymorphic_path([@ipable, ip]) %> yeah that's plausibly not going to work... what's in @ipable? why not use: ` <%= link_to polymorphic_path([ip.ipable, ip]) %>` ?Phile
For the last suggestion - that was daft of me. You're right. But when I try using ip.ipable - the error says the same thing. : undefined method `package_bip_path' for #<#<Class:0x007fd5fa181500>:0x007fd5f8a6bd70>Armure
I'll do some more logging in the controllers and try to find the source of bip fetch. Thanks for the tips.Armure
re "undefined method package_bip_path" well that's odd... might be worth doing a puts "for #{ip.id} we have: #{ip.ipable.inspect}" on that too - so we can see what it's borking on...Phile
@TarynEast - what would be your next move in terms of trying to figure this out? Do you know any books that go deeper than the basics that you could recommend? I have lots of books but they don't get into much detail.Armure
This is about debugging, rather than basics... I'd be putting a billionty putss in until I figure out where exactly the code goes and where it goes wrong (or using a debugger to the same effect)... Until we know which line of code is the broken one (and likely there's more than one) we can't know what we need to look at to fix it.Phile
i think it has something to do with the 'permitted: false' in the params. I thought maybe the way I set up the structure might be giving rise to that issue.Armure
That's worth a try.Phile
I'll start a new app (this will be my 6th). I have plenty of tutorials for how to use polymorphic associations to pick from, but they all seem to lead me to the same place. I'm feeling like it might be worth making 2 separate resources for proposal_bips and organisation_bips and give up on trying to learn how to use these associations. I get the sense that not many people use them.Armure
ok, observations from the most recent stuff. 1) You shouldn't need/use proposal_params in the show action - that's used for create/update. 2) the proposal params in the ProposalsController has :id instead of :proposal_id so when you fetch out the @proposal you need params[:id] instead. In BipsController you need @proposal = proposal.find(params[:proposal_id] if params[:proposal_id].present? or similar. 3) I don't know what permitted: false is or where it's coming from...Phile
I'm still not clear how you are getting into the BipsController? which link takes you there? are you sure you are passing in and then using the same params (eg if you're using proposal_id in the link but ipable_type in the controller - there's a mis-match and it won't work)Phile
I get all the same responses when I use :id instead of :proposal_id. I'm not sure what explains that. My proposal show view has this line in it: <%= link_to polymorphic_path([@proposal, Package::Bip]) do %> I think that is what takes me to the bips controller.Armure
Im giving up on this. I'll make 2 separate resources for proposal/organisation bips and move on. It's far too hard to figure this out and Im not going to get lost in another black whole if I stick with trying to learn. Thanks so much for trying to help me just the same.Armure
Fair enough - at some point drawing a line under it makes a lot of sense. Good luck :)Phile
@TarynEast - I can barely believe it, but I found the answer. It's in this post - you can probably make more sense of the load_commentable method that I can. Thanks so much again for all of your help with this.rubyplus.com/articles/3901-Polymorphic-Association-in-Rails-5Armure
woohoo! \o/ congratulations on finding it... sometimes these processes can be so gruelling - but always good to get a win at the end :)Phile
I hate giving up on things. :)Armure
A
2

The answer is here: https://rubyplus.com/articles/3901-Polymorphic-Association-in-Rails-5

The trick seems to be in the load_commentable method shown in the article. It's too complicated for me to make sense of it, but it works.

Feeling very grateful for how much help there is from people happy to share what they know. Thank you Taryn & Bala.

Armure answered 14/11, 2016 at 22:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.