9

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!

My Koryto
  • 657
  • 1
  • 4
  • 16
  • You should change the definition of `Label::PersonalInformationsController` to use explicit nesting as using the scope resolution operator will lead to unexpected constant lookups. I'm not saying its the cause of your current woes but it is a bunch of bugs waiting to happen. https://github.com/rubocop-hq/ruby-style-guide#namespace-definition – max Jul 26 '20 at 13:12
  • Hey Max. Thank you for your advice. I'm changing it now in my whole app according to the style guide you attached. – My Koryto Jul 26 '20 at 13:17
  • 1
    If you are using [Rubocop](https://github.com/rubocop-hq/rubocop) there is a cop named [ClassAndModuleChildren](https://www.rubydoc.info/gems/rubocop/RuboCop/Cop/Style/ClassAndModuleChildren) which can help you with that. – max Jul 26 '20 at 13:20
  • Having same issue, but with HABTM association. Have traced it down to associations.rb: `extensions |= reflection.scope_for(klass.unscoped, owner).extensions` – theschmitzer Jul 29 '20 at 00:40
  • Hey theschmitzer! Could you please elaborate on what you know about your bug? In my case, the error is raised by the named.rb file (line 63) under active_record -> scoping directory (rails 6.0.3.1) – My Koryto Jul 29 '20 at 14:30
  • 1
    Done @MyKoryto mine was similar symptom but different cause – theschmitzer Jul 29 '20 at 15:23

3 Answers3

16

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.

theschmitzer
  • 12,190
  • 11
  • 41
  • 49
  • I'm sure this would be useful for many users. Just posted what was the answer to my problem as I just solved it an hour ago. Do you know if there is any part in the rails documentation which point out to problems like the ones we encountered? I've been searching for a while now but with no luck. – My Koryto Jul 29 '20 at 15:26
  • This was my exact issue! – Vantalk Sep 01 '23 at 11:00
1

The solution was quite simple after all. It seems like there has been a change in scoping sections between rails 5.2 and rails 6.0.

Rails 5.2 still supported a syntax which was going toward deprecation back in rails 3 while rails 6.0 no longer supports that kind of syntax.

At link.rb (my link model) there was a problem at line 15 where I defined the scope:

  default_scope { {order: ordered_links} }

The new syntax which worked for me is the following:

  default_scope { order(ordered_links) }

The information was taken from here.

My Koryto
  • 657
  • 1
  • 4
  • 16
1

Mr Koryto's answer does not look equivalent - when is setting default ORDER BY, the other default WHERE. Maybe you should try

default_scope { order(ordered_links) }
theschmitzer
  • 12,190
  • 11
  • 41
  • 49
  • It doesn't change anything in my app yet it does make much more sense SQL-wise. I'll update it on my marked answer as well. – My Koryto Jul 31 '20 at 11:37