Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Model associations problem: NoMethodError: undefined method `extensions' for #<Hash...>

I'm currently upgrading my rails 5.2 app to rails 6.0 while following the upgrading guide.

Everything seems to work perfectly fine until I've encountered an error when one of my user classes (label or artist) interacts with the links model.

When I try to sign up either as an artist or as a label, I receive the following error when I get to the point where I need to define links to the user's social media or website:

Completed 500 Internal Server Error in 139ms (ActiveRecord: 7.3ms | Allocations: 14518)

    NoMethodError - undefined method `extensions' for #<Hash:0x000000000bdd4ec8>
    Did you mean?  extend:
      app/controllers/label/personal_informations_controller.rb:8:in `edit'

Here is my personal_informations_controller: (the error is at line 8 which I'll mark so it will be clear)

class Label::PersonalInformationsController < LabelController
  layout "signup"
  skip_before_action :ensure_approved

  def edit
    prepare_country_options
    @label = current_label
    @label.links.build(links_attributes) #***ERROR OCCURS HERE***#
  end

  def update
    @label = current_label
    if @label.update_attributes(label_params)
      redirect_to edit_label_genres_path
    else
      update_error
    end
  end

  private

  def update_error
    prepare_country_options
    @label.links.build(links_attributes)
    render :edit
  end

  def label_params
    params.require(:label).permit(:logo, :city, :country_code, :profile, links_attributes: [:id, :source, :url])
  end

  def prepare_country_options
    @country_options ||= Countries.prioritized.map { |country| [country.name, country.code] }
  end

  def links_attributes
    link_sources.map { |source| {source: source } }
  end

  def link_sources
    Link.sources - @label.links.map(&:source)
  end

end

So far, I've tried 3 solutions:

  1. Checking if this bug has anything to do with the change in the default autoloader to Zeitwerk, after checking it for a while I've came to the conclusion that it doesn't have anything to do with it.
  2. Checking if this bug has anything to do with the fact I'm using the Discogs API in order to automatically extract links if possible for the newcomer user. But when I mentioned @label.links to specifically point to my Discogs file in my services directory, it messed up everything as it was no longer pointing to the 'Link' model as it should have.
  3. Adding belongs_to :label association in my link.rb model. Unfortunately, it didn't change anything.

I'm attaching my Link model below:

class Link < ApplicationRecord


  LINKS_ORDERED = ['website', 'facebook', 'twitter', 'youtube', 'soundcloud', 'lastfm']

  def self.ordered_links
    ret = "CASE"
    LINKS_ORDERED.each_with_index do |p, i|
      ret << " WHEN source = '#{p}' THEN #{i}"
    end
    ret << " END"
  end

  validates :url, presence: true
  default_scope { {order: ordered_links} }

  def self.website
    where(source: "website" )
  end

  def self.sources
    ["website", "facebook", "twitter", "youtube", "soundcloud", "lastfm"]
  end

  def self.icons
    ["globe", "facebook", "twitter", "youtube", "soundcloud", "lastfm"]
  end

  def to_partial_path
    "links/#{source}"
  end

  def account_name
    is_social_account? ? url.split("/").last : domain
  end

  def source
    read_attribute(:source) # guessed_source || ...
  end

  def icon
    self.class.icons[self.class.sources.index(source)]
  end

  def url=(url)
    url = "http://#{url}" unless url =~ /^https?\:\/\//
    write_attribute(:url, url)
  end

  private

  def is_social_account?
    (self.class.sources - ["website"]).include? source
  end

  def guessed_source
    self.class.sources.find { |source| url =~ /#{source}/ }
  end

  def domain
    url.gsub(/https?\:\/\//, "").split("/").first
  end

end

And my Label model:

class Label < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable, :omniauthable
  include PgSearch::Model

  has_attached_file :logo, :s3_protocol => :https,
                    styles: { medium: "350x360#", thumb: "40x40#" },
                    default_url: ":class/:attachment/missing_:style.jpg"
  validates_attachment_content_type :logo, content_type: %r{\Aimage/.*\Z}
  alias_method :avatar, :logo

  has_and_belongs_to_many :genres
  has_many :feedback_requests
  has_many :payment_types, as: :payee
  has_many :links, as: :linkable, dependent: :destroy
  has_many :releases
  has_many :label_artists
  has_many :sent_messages, as: :from, class_name: "Message"
  has_many :messages, through: :conversations
  has_many :conversations
  has_many :songs, through: :feedback_requests, source: :song
  has_many :artist_contacts, through: :songs, source: :artist
  has_many :contracts, through: :conversations

  accepts_nested_attributes_for :payment_types
  accepts_nested_attributes_for :links, reject_if: ->(attributes) { attributes[:url].blank? }

  validates :name, :real_name, presence: true
  validates :city, :country_code, :profile, presence: true, on: :update

  delegate :name, to: :country, prefix: true, allow_nil: true

  pg_search_scope :search,
                  against: :name,
                  associated_against: { genres: :name },
                  using: {
                    tsearch: { dictionary: :english }
                  }

  pg_search_scope :by_relevance,
                  associated_against: { genres: :name },
                  using: {
                    tsearch: { dictionary: :english, any_word: true }
                  }

  def self.approved
    where("approved_at IS NOT NULL")
  end

  def approved?
    approved_at.present?
  end

  def payment_type
    payment_types.order("created_at").last
  end

  def display_name
    name
  end

  def country
    Country.new(code: country_code)
  end

  def location
    [city, country_name].compact.join(", ")
  end

  def logo_url(style = :medium)
    logo.url(style)
  end

  # The profit the label has earned (deducting Sendemo's cut)
  def balance_net
    ((raw_balance.to_i/100.0) * (1-Payment.sendemo_fee_fraction)).round(2) if raw_balance.present?
  end

  # A label's balance is the entire balance charged through the label including Sendemo's fee.
  # This is for backwards compatibility and should be changed
  def balance=(new_balance)
     write_attribute(:balance, (new_balance.to_f*100).to_i) if new_balance.present?
  end

  def balance
    (raw_balance.to_i/100.0).round(2) if raw_balance.present?
  end

  def raw_balance
    read_attribute(:balance)
  end

  def unread_messages_count
    messages.unread.count
  end

  def unread_messages?
    unread_messages_count > 0
  end
end

It is important to mention that when I'm trying to write in the rails console to see if my current_label has any other property it should have as mentioned in my Label model, the answer is always either positive or nil. The only problem which raises an error is when checking for @label.links

For example:

>> @label.conversations
=> #<ActiveRecord::Associations::CollectionProxy []>

>> @label.name
=> "Test Label"

>> @label.links
!! #<NoMethodError: undefined method `extensions' for #<Hash:0x000000001001f1e8>
Did you mean?  extend>

Thank you for assisting me!

like image 993
My Koryto Avatar asked Mar 02 '23 05:03

My Koryto


1 Answers

I had a similar issue, and mine was this:

class Foo < ApplicationRecord
  has_and_belongs_to_many :bars, -> { uniq }
end

When I call .bars on a Foo, I got the undefined_method: extensions error.

The solution for me was to change -> { uniq } to -> { distinct } during Rails 6 migration.

Hope this helps someone.

like image 195
theschmitzer Avatar answered Mar 05 '23 16:03

theschmitzer