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?
@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 tryinverse_of :bip
on the Bips association-definition... I'm not sure it works for polymorphic associations (though maybe it does). but worth trying. – Chameleonclass_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 ofclass_name: Package::Bip
tryclass_name: "Package::Bip"
) and see if that makes any difference. – Chameleonproposal.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. – Chameleoninverse_of
and also tried theclass_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. – Chameleonbips
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 (egPackage::Bip.where(ipable_type: 'Proposal')
) to see what you get? – Chameleon@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 egif params[:organisation_id].present?
– Chameleon@bips = Package::Bip.where(ipable_type: 'Proposal')
does this work in your console? – Chameleonif 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) – Chameleonipable_type
isProposal
or not... it just tests ifipable_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 :) – ChameleonProposal
orOrganisation
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 anipable_type
of "Banana" :) – Chameleon