<commit_before>require "active_support"
require "active_record"

require "view_model"
require "view_model/record"

require "lazily"
require "concurrent"

class ViewModel::ActiveRecord < ViewModel::Record
  # Defined before requiring components so components can refer to them at parse time

  # for functional updates
  FUNCTIONAL_UPDATE_TYPE = "_update"
  ACTIONS_ATTRIBUTE      = "actions"
  VALUES_ATTRIBUTE       = "values"
  BEFORE_ATTRIBUTE       = "before"
  AFTER_ATTRIBUTE        = "after"

  require 'view_model/active_record/collections'
  require 'view_model/active_record/association_data'
  require 'view_model/active_record/update_data'
  require 'view_model/active_record/update_context'
  require 'view_model/active_record/update_operation'
  require 'view_model/active_record/visitor'
  require 'view_model/active_record/association_manipulation'

  include AssociationManipulation

  class << self
    attr_reader   :_list_attribute_name
    attr_accessor :synthetic

    delegate :transaction, to: :model_class

    def should_register?
      super && !synthetic
    end

    # Specifies that the model backing this viewmodel is a member of an
    # `acts_as_manual_list` collection.
    def acts_as_list(attr = :position)
      @_list_attribute_name = attr

      @generated_accessor_module.module_eval do
        define_method("_list_attribute") do
          model.public_send(attr)
        end

        define_method("_list_attribute=") do |x|
          model.public_send(:"#{attr}=", x)
        end
      end
    end

    def _list_member?
      _list_attribute_name.present?
    end

    # Specifies an association from the model to be recursively serialized using
    # another viewmodel. If the target viewmodel is not specified, attempt to
    # locate a default viewmodel based on the name of the associated model.
    # TODO document harder
    # - +through+ names an ActiveRecord association that will be used like an
    #   ActiveRecord +has_many:through:+.
    # - +through_order_attr+ the through model is ordered by the given attribute
    #   (only applies to when +through+ is set).
    def association(association_name,
                    viewmodel: nil,
                    viewmodels: nil,
                    shared: false,
                    optional: shared,
                    through: nil,
                    through_order_attr: nil,
                    as: nil)

      if through
        model_association_name = through
        through_to             = association_name
      else
        model_association_name = association_name
        through_to             = nil
      end

      vm_association_name    = (as || association_name).to_s

      reflection = model_class.reflect_on_association(model_association_name)

      if reflection.nil?
        raise ArgumentError.new("Association #{model_association_name} not found in #{model_class.name} model")
      end

      viewmodel_spec = viewmodel || viewmodels

      association_data = AssociationData.new(vm_association_name, reflection, viewmodel_spec, shared, optional, through_to, through_order_attr)

      _members[vm_association_name] = association_data

      @generated_accessor_module.module_eval do
        define_method vm_association_name do
          _read_association(vm_association_name)
        end

        define_method :"serialize_#{vm_association_name}" do |json, serialize_context: self.class.new_serialize_context|
          associated = self.public_send(vm_association_name)
          json.set! vm_association_name do
            case
            when associated.nil?
              json.null!
            when association_data.through?
              json.array!(associated) do |through_target|
                self.class.serialize_as_reference(through_target, json, serialize_context: serialize_context)
              end
            when shared
              self.class.serialize_as_reference(associated, json, serialize_context: serialize_context)
            else
              self.class.serialize(associated, json, serialize_context: serialize_context)
            end
          end
        end
      end
    end

    # Specify multiple associations at once
    def associations(*assocs, **args)
      assocs.each { |assoc| association(assoc, **args) }
    end

    ## Load instances of the viewmodel by id(s)
    def find(id_or_ids, scope: nil, lock: nil, eager_include: true, serialize_context: new_serialize_context)
      find_scope = self.model_class.all
      find_scope = find_scope.lock(lock) if lock
      find_scope = find_scope.merge(scope) if scope

      ViewModel::Utils.wrap_one_or_many(id_or_ids) do |ids|

        models = find_scope.where(id: ids).to_a

        if models.size < ids.size
          missing_ids = ids - models.map(&:id)
          if missing_ids.present?
            raise ViewModel::DeserializationError::NotFound.new(
                    "Couldn't find #{self.model_class.name}(s) with id(s)=#{missing_ids.inspect}",
                    missing_ids.map { |id| ViewModel::Reference.new(self, id) } )
          end
        end

        vms = models.map { |m| self.new(m) }
        ViewModel.preload_for_serialization(vms, lock: lock, serialize_context: serialize_context) if eager_include
        vms
      end
    end
<commit_msg>Force predictable lock ordering when locking in `ViewModel#find`<commit_after>require "active_support"
require "active_record"

require "view_model"
require "view_model/record"

require "lazily"
require "concurrent"

class ViewModel::ActiveRecord < ViewModel::Record
  # Defined before requiring components so components can refer to them at parse time

  # for functional updates
  FUNCTIONAL_UPDATE_TYPE = "_update"
  ACTIONS_ATTRIBUTE      = "actions"
  VALUES_ATTRIBUTE       = "values"
  BEFORE_ATTRIBUTE       = "before"
  AFTER_ATTRIBUTE        = "after"

  require 'view_model/active_record/collections'
  require 'view_model/active_record/association_data'
  require 'view_model/active_record/update_data'
  require 'view_model/active_record/update_context'
  require 'view_model/active_record/update_operation'
  require 'view_model/active_record/visitor'
  require 'view_model/active_record/association_manipulation'

  include AssociationManipulation

  class << self
    attr_reader   :_list_attribute_name
    attr_accessor :synthetic

    delegate :transaction, to: :model_class

    def should_register?
      super && !synthetic
    end

    # Specifies that the model backing this viewmodel is a member of an
    # `acts_as_manual_list` collection.
    def acts_as_list(attr = :position)
      @_list_attribute_name = attr

      @generated_accessor_module.module_eval do
        define_method("_list_attribute") do
          model.public_send(attr)
        end

        define_method("_list_attribute=") do |x|
          model.public_send(:"#{attr}=", x)
        end
      end
    end

    def _list_member?
      _list_attribute_name.present?
    end

    # Specifies an association from the model to be recursively serialized using
    # another viewmodel. If the target viewmodel is not specified, attempt to
    # locate a default viewmodel based on the name of the associated model.
    # TODO document harder
    # - +through+ names an ActiveRecord association that will be used like an
    #   ActiveRecord +has_many:through:+.
    # - +through_order_attr+ the through model is ordered by the given attribute
    #   (only applies to when +through+ is set).
    def association(association_name,
                    viewmodel: nil,
                    viewmodels: nil,
                    shared: false,
                    optional: shared,
                    through: nil,
                    through_order_attr: nil,
                    as: nil)

      if through
        model_association_name = through
        through_to             = association_name
      else
        model_association_name = association_name
        through_to             = nil
      end

      vm_association_name    = (as || association_name).to_s

      reflection = model_class.reflect_on_association(model_association_name)

      if reflection.nil?
        raise ArgumentError.new("Association #{model_association_name} not found in #{model_class.name} model")
      end

      viewmodel_spec = viewmodel || viewmodels

      association_data = AssociationData.new(vm_association_name, reflection, viewmodel_spec, shared, optional, through_to, through_order_attr)

      _members[vm_association_name] = association_data

      @generated_accessor_module.module_eval do
        define_method vm_association_name do
          _read_association(vm_association_name)
        end

        define_method :"serialize_#{vm_association_name}" do |json, serialize_context: self.class.new_serialize_context|
          associated = self.public_send(vm_association_name)
          json.set! vm_association_name do
            case
            when associated.nil?
              json.null!
            when association_data.through?
              json.array!(associated) do |through_target|
                self.class.serialize_as_reference(through_target, json, serialize_context: serialize_context)
              end
            when shared
              self.class.serialize_as_reference(associated, json, serialize_context: serialize_context)
            else
              self.class.serialize(associated, json, serialize_context: serialize_context)
            end
          end
        end
      end
    end

    # Specify multiple associations at once
    def associations(*assocs, **args)
      assocs.each { |assoc| association(assoc, **args) }
    end

    ## Load instances of the viewmodel by id(s)
    def find(id_or_ids, scope: nil, lock: nil, eager_include: true, serialize_context: new_serialize_context)
      find_scope = self.model_class.all
      find_scope = find_scope.order(:id).lock(lock) if lock
      find_scope = find_scope.merge(scope) if scope

      ViewModel::Utils.wrap_one_or_many(id_or_ids) do |ids|

        models = find_scope.where(id: ids).to_a

        if models.size < ids.size
          missing_ids = ids - models.map(&:id)
          if missing_ids.present?
            raise ViewModel::DeserializationError::NotFound.new(
                    "Couldn't find #{self.model_class.name}(s) with id(s)=#{missing_ids.inspect}",
                    missing_ids.map { |id| ViewModel::Reference.new(self, id) } )
          end
        end

        vms = models.map { |m| self.new(m) }
        ViewModel.preload_for_serialization(vms, lock: lock, serialize_context: serialize_context) if eager_include
        vms
      end
    end

{'fraction_non_alphanumeric': 0.07195121951219512, 'fraction_numerical': 0.0, 'mean_word_length': 2.6902042229145033, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1859140', 'n_tokens_mistral': 3045, 'n_tokens_neox': 2959, 'n_words': 731}