Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ActiveStorage reflection #33018

Merged
merged 3 commits into from Jun 1, 2018
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
31 changes: 16 additions & 15 deletions activerecord/lib/active_record/reflection.rb
Expand Up @@ -14,24 +14,25 @@ module Reflection # :nodoc:
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

reflection = klass.new(name, scope, options, ar)
reflection = reflection_class_for(macro).new(name, scope, options, ar)
options[:through] ? ThroughReflection.new(reflection) : reflection
end

def self.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

def self.add_reflection(ar, name, reflection)
ar.clear_reflections_cache
name = name.to_s
Expand Down
5 changes: 5 additions & 0 deletions activestorage/CHANGELOG.md
@@ -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
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
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
63 changes: 63 additions & 0 deletions activestorage/lib/active_storage/reflection.rb
@@ -0,0 +1,63 @@
# 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this one should be :nodoc: like all methods in Reflection are.

def reflection_class_for(macro)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method should be private

case macro
when :has_one_attached
HasOneAttachedReflection
when :has_many_attached
HasManyAttachedReflection
else
super
end
end

def add_attachment_reflection(ar, name, reflection)
ar.attachment_reflections.merge!(name.to_s => reflection)
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
@@ -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