Rails 5 - using polymorphic associations - renderi

2019-02-13 21:01发布

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.

1条回答
聊天终结者
2楼-- · 2019-02-13 21:35

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.

查看更多
登录 后发表回答