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:
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
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.
@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 ofp = Proposal.last
doparams = {:proposal_id => 0}
then@proposal = Proposal.find(params[:proposal_id])
(or whatever is in your controller). Then try@proposal.bips
. – Phileputs "I'm in the show controller with @proposal of: #{@proposal.inspect} and @bips: #{@bips.inspect}"
- this is how you'll spot inconsistencies. – Phileputs
/log
just before that line to make it clera which part of the code you are getting to? – Phileparams[: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<% @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]) %>` ? – Philepackage_bip_path
" well that's odd... might be worth doing aputs "for #{ip.id} we have: #{ip.ipable.inspect}"
on that too - so we can see what it's borking on... – Phileputs
s 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. – Phileproposal_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 needparams[:id]
instead. InBipsController
you need@proposal = proposal.find(params[:proposal_id] if params[:proposal_id].present?
or similar. 3) I don't know whatpermitted: false
is or where it's coming from... – Phileproposal_id
in the link butipable_type
in the controller - there's a mis-match and it won't work) – Phile