Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 28 additions & 24 deletions activerecord/lib/active_record/reflection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,33 +13,37 @@ module Reflection # :nodoc:
class_attribute :aggregate_reflections, instance_writer: false, default: {}
end

def self.create(macro, name, scope, options, ar)
klass = \
case macro
when :composed_of
AggregateReflection
when :has_many
HasManyReflection
when :has_one
HasOneReflection
when :belongs_to
BelongsToReflection
else
raise "Unsupported Macro: #{macro}"
end
class << self
def create(macro, name, scope, options, ar)
reflection = reflection_class_for(macro).new(name, scope, options, ar)
options[:through] ? ThroughReflection.new(reflection) : reflection
end

reflection = klass.new(name, scope, options, ar)
options[:through] ? ThroughReflection.new(reflection) : reflection
end
def add_reflection(ar, name, reflection)
ar.clear_reflections_cache
name = name.to_s
ar._reflections = ar._reflections.except(name).merge!(name => reflection)
end

def self.add_reflection(ar, name, reflection)
ar.clear_reflections_cache
name = name.to_s
ar._reflections = ar._reflections.except(name).merge!(name => reflection)
end
def add_aggregate_reflection(ar, name, reflection)
ar.aggregate_reflections = ar.aggregate_reflections.merge(name.to_s => reflection)
end

def self.add_aggregate_reflection(ar, name, reflection)
ar.aggregate_reflections = ar.aggregate_reflections.merge(name.to_s => reflection)
private
def reflection_class_for(macro)
case macro
when :composed_of
AggregateReflection
when :has_many
HasManyReflection
when :has_one
HasOneReflection
when :belongs_to
BelongsToReflection
else
raise "Unsupported Macro: #{macro}"
end
end
end

# \Reflection enables the ability to examine the associations and aggregations of
Expand Down
5 changes: 5 additions & 0 deletions activestorage/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
* Add the ability to reflect on defined attachments using the existing
ActiveRecord reflection mechanism.

*Kevin Deisz*

* Variant arguments of `false` or `nil` will no longer be passed to the
processor. For example, the following will not have the monochrome
variation applied:
Expand Down
12 changes: 12 additions & 0 deletions activestorage/lib/active_storage/attached/macros.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ def #{name}=(attachable)
else
before_destroy { public_send(name).detach }
end

ActiveRecord::Reflection.add_attachment_reflection(
self,
name,
ActiveRecord::Reflection.create(:has_one_attached, name, nil, { dependent: dependent }, self)
)
end

# Specifies the relation between multiple attachments and the model.
Expand Down Expand Up @@ -105,6 +111,12 @@ def purge_later
else
before_destroy { public_send(name).detach }
end

ActiveRecord::Reflection.add_attachment_reflection(
self,
name,
ActiveRecord::Reflection.create(:has_many_attached, name, nil, { dependent: dependent }, self)
)
end
end
end
9 changes: 9 additions & 0 deletions activestorage/lib/active_storage/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
require "active_storage/analyzer/image_analyzer"
require "active_storage/analyzer/video_analyzer"

require "active_storage/reflection"

module ActiveStorage
class Engine < Rails::Engine # :nodoc:
isolate_namespace ActiveStorage
Expand Down Expand Up @@ -95,5 +97,12 @@ class Engine < Rails::Engine # :nodoc:
end
end
end

initializer "active_storage.reflection" do
ActiveSupport.on_load(:active_record) do
include Reflection::ActiveRecordExtensions
ActiveRecord::Reflection.singleton_class.prepend(Reflection::ReflectionExtension)
end
end
end
end
64 changes: 64 additions & 0 deletions activestorage/lib/active_storage/reflection.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# frozen_string_literal: true

module ActiveStorage
module Reflection
# Holds all the metadata about a has_one_attached attachment as it was
# specified in the Active Record class.
class HasOneAttachedReflection < ActiveRecord::Reflection::MacroReflection #:nodoc:
def macro
:has_one_attached
end
end

# Holds all the metadata about a has_many_attached attachment as it was
# specified in the Active Record class.
class HasManyAttachedReflection < ActiveRecord::Reflection::MacroReflection #:nodoc:
def macro
:has_many_attached
end
end

module ReflectionExtension # :nodoc:
def add_attachment_reflection(ar, name, reflection)
ar.attachment_reflections.merge!(name.to_s => reflection)
end

private
def reflection_class_for(macro)
case macro
when :has_one_attached
HasOneAttachedReflection
when :has_many_attached
HasManyAttachedReflection
else
super
end
end
end

module ActiveRecordExtensions
extend ActiveSupport::Concern

included do
class_attribute :attachment_reflections, instance_writer: false, default: {}
end

module ClassMethods
# Returns an array of reflection objects for all the attachments in the
# class.
def reflect_on_all_attachments
attachment_reflections.values
end

# Returns the reflection object for the named +attachment+.
#
# User.reflect_on_attachment(:avatar)
# # => the avatar reflection
#
def reflect_on_attachment(attachment)
attachment_reflections[attachment.to_s]
end
end
end
end
end
29 changes: 29 additions & 0 deletions activestorage/test/models/reflection_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# frozen_string_literal: true

require "test_helper"

class ActiveStorage::ReflectionTest < ActiveSupport::TestCase
test "allows reflecting for all attachment" do
expected_classes =
User.reflect_on_all_attachments.all? do |reflection|
reflection.is_a?(ActiveStorage::Reflection::HasOneAttachedReflection) ||
reflection.is_a?(ActiveStorage::Reflection::HasManyAttachedReflection)
end

assert expected_classes
end

test "allows reflecting on a singular has_one_attached attachment" do
reflection = User.reflect_on_attachment(:avatar)

assert_equal :avatar, reflection.name
assert_equal :has_one_attached, reflection.macro
end

test "allows reflecting on a singular has_many_attached attachment" do
reflection = User.reflect_on_attachment(:highlights)

assert_equal :highlights, reflection.name
assert_equal :has_many_attached, reflection.macro
end
end