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.
CodePudding user response:
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.