如何在Ruby on Rails中通过GraphQL返回ActiveStorage图像的变体?

huangapple go评论59阅读模式
英文:

How can I return variants of an ActiveStorage image over GraphQL in Ruby on Rails?

问题

以下是翻译好的部分:

摘要

对于针对 Ruby on Rails 7 端点的 GraphQL 查询:

query {
	posts(limit: 1) {
		id
		caption
		image {
			web # <- 这是一个变体名称
			thumb # <- 另一个变体名称
		}
	}
}

我希望得到一个类似这样的响应:

{
	"data": {
		"posts": [
			{
				"id": "8d59b87c-c33b-4c29-b18f-a4d6aa2abf45",
				"caption": "Nam ad incidunt. Nisi sequi quibusdam. Nihil quis veritatis. Beatae consequatur excepturi. Et at sapiente. Delectus deleniti unde. Id a quo. Qui aut quod. Est qui quo. Dolores beatae facilis. Sed error vero. Est est at. Alias aut sit. Consequuntur consequ."
				"image": {
					"web": "some-url-here",
					"thumb": "another-url-here"
				}
			}
		]
	}
}

其中 object 具有一个带有多个 variantsimage 属性。

详细信息

我正在使用 Rails 7、graphql-rails 以及我对 Ruby 的中级知识来处理特定的 GraphQL 查询端点,但我遇到了一个障碍。

我有一个项目,允许用户上传照片,然后以 ActiveStorage 变体的形式接收多个修改。用户每次发布帖子时只包括一张附加的图像以进行处理。上传后,后台作业根据一些模型指定的尺寸生成 5 种不同的变体。

为了简化起见,这是 Post 模型的相关部分:

class Post < ApplicationRecord
  belongs_to :user
  has_many :comments, dependent: :destroy
  has_one_attached :image, dependent: :destroy do |attachable|
    attachable.variant :web, resize_to_fill: [1200, 1200, { crop: :centre }]
    attachable.variant :opengraph, resize_to_fill: [1200, 630, { crop: :attention }]
    attachable.variant :twitter, resize_to_fill: [1024, 512, { crop: :attention }]
    attachable.variant :mobile, resize_to_fill: [600, 600, { crop: :centre }]
    attachable.variant :thumb, resize_to_fill: [100, 100, { crop: :centre }]
  end

...

GraphQL 的 Post 类型设置为返回一个名为 image 的字段,其类型为 VariantType,我已经在下面的当前版本中包含了它:

module Types
  class VariantType < Types::BaseObject
    field :web, String, null: false
    field :opengraph, String, null: false
    field :twitter, String, null: false
    field :mobile, String, null: false
    field :thumb, String, null: false
  end

  def web
    url_for(:web)
  end

  def opengraph
    url_for(:opengraph)
  end

  def twitter
    url_for(:twitter)
  end

  def mobile
    url_for(:mobile)
  end

  def thumb
    url_for(:thumb)
  end

  ...(为了清晰起见,省略了 url_for 的方法定义)
end

当我尝试查询端点时,而不是上面的 JSON 响应,我收到了以下错误:

未能实现 Variant.web,尝试了:

  - `Types::VariantType#web`,但不存在
  - `ActiveStorage::Attached::One#web`,但不存在
  - 在 `<ActiveStorage::Attached::One:0x000000012511c5f8>` 上查找散列键 `:web` 或 `"web"`,但它不是 Hash

要实现此字段,请定义其中一个上述方法(并检查是否有拼写错误)

更新 1

url_for 方法定义如下:

  def url_for(variant) # rubocop:disable Metrics/MethodLength
    img = object.image.variant(variant)

    # 如果 CDN_HOST 没有协议,请添加协议
    cdn_host = ENV.fetch('CDN_HOST', nil)
    cdn_host = "https://#{cdn_host}" unless cdn_host.nil? || cdn_host.start_with?('http')
    if cdn_host.nil?
      blob_type = if img.is_a?(ActiveStorage::Variant) || img.is_a?(ActiveStorage::VariantWithRecord)
                    :rails_representation
                  else
                    :rails_blob
                  end

      Rails.application.routes.url_helpers.route_for(blob_type, img, only_path: true)
    else
      File.join(cdn_host, img.key) # 返回 blob 的 CDN 路径
    end
英文:

Summary

For a given GraphQL query against a Ruby on Rails 7 endpoint:

query {
	posts(limit: 1) {
		id
		caption
		image {
			web # <- this is a variant name
			thumb # <- another variant name
		}
	}
}

I want a response that looks like this:

{
	"data": {
		"posts": [
			{
				"id": "8d59b87c-c33b-4c29-b18f-a4d6aa2abf45",
				"caption": "Nam ad incidunt. Nisi sequi quibusdam. Nihil quis veritatis. Beatae consequatur excepturi. Et at sapiente. Delectus deleniti unde. Id a quo. Qui aut quod. Est qui quo. Dolores beatae facilis. Sed error vero. Est est at. Alias aut sit. Consequuntur consequ."
				"image": {
					"web": "some-url-here",
					"thumb": "another-url-here"
				}
			}
		]
	}
}

Where the object has an image property with multiple variants defined.

Details

I'm working on a specific GraphQL query endpoint using Rails 7, the graphql-rails, and my intermediate knowledge of Ruby, but I've happened upon a stumbling block.

I have a project that accepts user uploads of photos, which then receive multiple modifications in the form of ActiveStorage variants. Each post a user makes includes one (and only one) attached image to be processed. After upload, a background job generates 5 different variants based on some model-specified dimensions.

For the sake of simplicity, here's the relevant part of the Post model:

class Post < ApplicationRecord
  belongs_to :user
  has_many :comments, dependent: :destroy
  has_one_attached :image, dependent: :destroy do |attachable|
    attachable.variant :web, resize_to_fill: [1200, 1200, { crop: :centre }]
    attachable.variant :opengraph, resize_to_fill: [1200, 630, { crop: :attention }]
    attachable.variant :twitter, resize_to_fill: [1024, 512, { crop: :attention }]
    attachable.variant :mobile, resize_to_fill: [600, 600, { crop: :centre }]
    attachable.variant :thumb, resize_to_fill: [100, 100, { crop: :centre }]
  end

...

The Post type for GraphQL is set to return a field named image of type VariantType, which I've included below in my current, inelegant version:

module Types
  class VariantType < Types::BaseObject
    field :web, String, null: false
    field :opengraph, String, null: false
    field :twitter, String, null: false
    field :mobile, String, null: false
    field :thumb, String, null: false
  end

  def web
    url_for(:web)
  end

  def opengraph
    url_for(:opengraph)
  end

  def twitter
    url_for(:twitter)
  end

  def mobile
    url_for(:mobile)
  end

  def thumb
    url_for(:thumb)
  end

  ... (method definition for url_for removed for clarity)
end

When I attempted to query the endpoint, instead of the JSON response above, I got the below error:

Failed to implement Variant.web, tried:
     
  - `Types::VariantType#web`, which did not exist
  - `ActiveStorage::Attached::One#web`, which did not exist
  - Looking up hash key `:web` or `"web"` on `<ActiveStorage::Attached::One:0x000000012511c5f8>`, but it wasn't a Hash
     
To implement this field, define one of the methods above (and check for typos)

Update 1

The url_for method is defined as:

  def url_for(variant) # rubocop:disable Metrics/MethodLength
    img = object.image.variant(variant)

    # Add protocol if CDN_HOST doesn't have it
    cdn_host = ENV.fetch('CDN_HOST', nil)
    cdn_host = "https://#{cdn_host}" unless cdn_host.nil? || cdn_host.start_with?('http')
    if cdn_host.nil?
      blob_type = if img.is_a?(ActiveStorage::Variant) || img.is_a?(ActiveStorage::VariantWithRecord)
                    :rails_representation
                  else
                    :rails_blob
                  end

      Rails.application.routes.url_helpers.route_for(blob_type, img, only_path: true)
    else
      File.join(cdn_host, img.key) # Returns CDN path for blob
    end

It's a hack, I know, in lieu of a better approach to having my asset URLs appear as https://cdn.example.com/asset.jpg instead of the ugly ActiveStorage proxy URLs.

答案1

得分: 1

以下是您要翻译的内容:

修复此问题的一种方法是使用ActiveStorage提供的variant方法来检索所需的变体。您可以将变体名称作为符号传递给variant方法,如下所示:

def web
  object.image.variant(:web).service_url
end

def opengraph
  object.image.variant(:opengraph).service_url
end

def twitter
  object.image.variant(:twitter).service_url
end

def mobile
  object.image.variant(:mobile).service_url
end

def thumb
  object.image.variant(:thumb).service_url
end

这将检索您想要的变体并使用service_url方法返回其URL。

另外,您可以像这样定义您的VariantType:

module Types
  class VariantType < Types::BaseObject
    field :web, String, null: false, method: :web_url
    field :opengraph, String, null: false, method: :opengraph_url
    field :twitter, String, null: false, method: :twitter_url
    field :mobile, String, null: false, method: :mobile_url
    field :thumb, String, null: false, method: :thumb_url

    def web_url
      object.image.variant(:web).service_url
    end

    def opengraph_url
      object.image.variant(:opengraph).service_url
    end

    def twitter_url
      object.image.variant(:twitter).service_url
    end

    def mobile_url
      object.image.variant(:mobile).service_url
    end

    def thumb_url
      object.image.variant(:thumb).service_url
    end
  end
end

这样,您可以使用method选项定义字段,并指定要调用以解析字段值的方法。

英文:

One way to fix this issue would be to use the variant method provided by ActiveStorage to retrieve the variant you want. You can pass the variant name as a symbol to the variant method, like this:

def web
  object.image.variant(:web).service_url
end

def opengraph
  object.image.variant(:opengraph).service_url
end

def twitter
  object.image.variant(:twitter).service_url
end

def mobile
  object.image.variant(:mobile).service_url
end

def thumb
  object.image.variant(:thumb).service_url
end

This will retrieve the variant you want and return its URL using the service_url method.

Alternatively, you could define your VariantType like this:

module Types
  class VariantType < Types::BaseObject
    field :web, String, null: false, method: :web_url
    field :opengraph, String, null: false, method: :opengraph_url
    field :twitter, String, null: false, method: :twitter_url
    field :mobile, String, null: false, method: :mobile_url
    field :thumb, String, null: false, method: :thumb_url

    def web_url
      object.image.variant(:web).service_url
    end

    def opengraph_url
      object.image.variant(:opengraph).service_url
    end

    def twitter_url
      object.image.variant(:twitter).service_url
    end

    def mobile_url
      object.image.variant(:mobile).service_url
    end

    def thumb_url
      object.image.variant(:thumb).service_url
    end
  end
end

This way, you can define your fields with the method option, which allows you to specify a method to be called to resolve the field's value.

huangapple
  • 本文由 发表于 2023年1月6日 12:21:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/75026893.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定