module ActiveRecord::JsonAssociations

Constants

FIELD_INCLUDE_SCOPE_BUILDER_PROC
VERSION

Public Instance Methods

belongs_to_many(many, class_name: nil, touch: nil) click to toggle source
# File lib/active_record/json_associations.rb, line 14
def belongs_to_many(many, class_name: nil, touch: nil)
  one = many.to_s.singularize
  one_ids = :"#{one}_ids"
  one_ids_equals = :"#{one_ids}="
  many_equals = :"#{many}="
  many_eh = :"#{many}?"

  class_name ||= one.classify

  serialize one_ids, JSON

  if touch
    after_commit do
      method = respond_to?(:saved_changes) ? :saved_changes : :previous_changes
      old_ids, new_ids = send(method)[one_ids.to_s]
      ids = Array(send(one_ids)) | Array(old_ids) | Array(new_ids)
      scope = class_name.constantize.where(self.class.primary_key => ids)

      if scope.respond_to?(:touch) # AR 6.0+
        scope.touch_all
      elsif self.class.respond_to?(:touch_attributes_with_time) # AR 5.1+
        scope.update_all self.class.touch_attributes_with_time
      else # AR 5.0
        attributes = timestamp_attributes_for_update_in_model.inject({}) do |attributes, key|
          attributes.merge(key => current_time_from_proper_timezone)
        end
        scope.update_all attributes
      end
    end
  end

  extend Module.new {
    define_method :"#{one_ids}_including" do |id|
      raise "can't query for a record that does not have an id!" if id.blank?
      if id.is_a?(Hash)
        Array(id[:any]).inject(none) do |context, id|
          context.or(FIELD_INCLUDE_SCOPE_BUILDER_PROC.call(self, one_ids, id))
        end
      else
        FIELD_INCLUDE_SCOPE_BUILDER_PROC.call(self, one_ids, id)
      end
    end
  }

  include Module.new {
    define_method one_ids do
      super().tap do |value|
        return send(one_ids_equals, []) if value.nil?
      end
    end

    define_method one_ids_equals do |ids|
      super Array(ids).select(&:present?).map(&:to_i)
    end

    define_method many do
      klass = class_name.constantize
      scope = klass.all

      ids = send(one_ids)
      scope.where!(klass.primary_key => ids)

      fragments = []
      fragments += ["#{klass.primary_key} NOT IN (#{ids.map(&:to_s).join(",")})"] if ids.any?
      fragments += ids.reverse.map { |id| "#{klass.primary_key}=#{id}" }
      order_by_ids = fragments.join(", ")
      scope.order!(Arel.sql(order_by_ids))
    end

    define_method many_equals do |collection|
      send one_ids_equals, collection.map(&:id)
    end

    define_method many_eh do
      send(one_ids).any?
    end
  }
end
has_many(many, scope = nil, **options, &extension) click to toggle source
Calls superclass method
# File lib/active_record/json_associations.rb, line 93
def has_many many, scope = nil, **options, &extension
  unless (scope.is_a?(Hash) && scope[:json_foreign_key]) || (options.is_a?(Hash) && options[:json_foreign_key])
    return super
  end

  if scope.is_a?(Hash)
    options = scope
    scope   = nil
  end

  one = many.to_s.singularize
  one_ids = :"#{one}_ids"
  one_ids_equals = :"#{one_ids}="
  many_equals = :"#{many}="
  many_eh = :"#{many}?"
  build_one = :"build_#{one}"
  create_one = :"create_#{one}"
  create_one_bang = :"create_#{one}!"

  class_name = options[:class_name] || one.classify
  klass = class_name.constantize

  foreign_key = options[:json_foreign_key]
  foreign_key = :"#{model_name.singular}_ids" if foreign_key == true

  include Module.new {
    define_method one_ids do
      send(many).pluck(:id)
    end

    define_method one_ids_equals do |ids|
      normalized_ids = Array(ids).select(&:present?).map(&:to_i)
      send many_equals, klass.find(normalized_ids)
    end

    define_method many do
      FIELD_INCLUDE_SCOPE_BUILDER_PROC.call(klass, foreign_key, id)
    end

    define_method many_equals do |collection|
      collection.each do |record|
        new_id_array = Array(record.send(foreign_key)) | [id]
        raise "FIXME: Cannot assign during creation, because no id has yet been reified." if new_id_array.any?(&:nil?)
        record.update foreign_key => new_id_array
      end
    end

    define_method many_eh do
      send(many).any?
    end

    define_method build_one do |attributes={}|
      klass.new attributes.merge!(foreign_key => [id])
    end

    define_method create_one do |attributes={}|
      klass.create attributes.merge!(foreign_key => [id])
    end

    define_method create_one_bang do |attributes={}|
      klass.create! attributes.merge!(foreign_key => [id])
    end
  }
end