I’m working on some migrations to remove columns from a table - and I’d like to first exclude the columns from the returned datasets (like the ignored_columns method in ActiveRecord, as recommended by strong_migrations). This is so I can deploy a change to have the column ignored, then later actually remove the column from the database (via migrations) without impacting the running code.
I see that a schema object has an exclude method - but I’m not quite sure of the best place to use it. I was hoping it’d be available in the schema block in a relation, along the lines of:
schema(infer: true) do
associations do
# ...
end
exclude :useless_column
end
But that’s not the case. Is something like this possible in some other manner? (I really don’t want to remove the infer option ).
Unfortunately exclude within the dataset block is for a negated where.
As an alternative, I tried the following:
dataset do
select(*(columns - %I[ignored_column_a ignored_column_b]))
end
… but as best as I can tell, this doesn’t get used when the schema infers attributes - so the result is the attributes still exist, but then aren’t populated when querying the database, which understandably raises an exception.
I’ve also looked into creating a schema plugin for this purpose, but plugins are invoked before the inferrer, so there’s no attributes to remove.
My next thought was to change the attributes inferrer on the schema, which… well, this works, but it’s far from elegant, and it hooks into the filter_columns method which is tagged as private:
class ExcludingInferrer < ROM::SQL::Schema::AttributesInferrer
option :excluded
def filter_columns(schema)
super.reject { |name, _definition| excluded.include?(name) }
end
end
schema_inferrer schema_inferrer.with(
attributes_inferrer: -> (schema, gateway, options) do
builder = ROM::SQL::Schema::TypeBuilder[gateway.database_type]
inferrer = ExcludingInferrer.new(type_builder: builder, excluded: %I[ignored_column_a ignored_column_b], **options)
inferrer.(schema, gateway)
end
)
I don’t suppose there’s something I’ve missed about tweaking a schema just before it’s finalised? I’m not against submitting a PR, I’m just not sure what a preferred interface around something like this would be.
Okay, have found a neater way with a relation plugin:
class ExcludeAttributes
def self.apply(relation, names:)
relation.schema_inferrer Inferrer.new(
excluded_attributes: names,
**relation.schema_inferrer.options
)
end
class Inferrer < ROM::SQL::Schema::Inferrer
option :excluded_attributes, default: -> { [] }
def call(...)
super(...).tap do |hash|
hash[:attributes].reject! do |attribute|
excluded_attributes.include?(attribute.name)
end
end
end
end
end
Registered as so:
ROM.plugins do
adapter :sql do
register :exclude_attributes, ExcludeAttributes, type: :relation
end
end
And then in the relation definition:
class Users < ROM::Relation[:sql]
schema :users, infer: true do
# ... associations, etc
end
use :exclude_attributes, names: %i[first_name last_name]
# ...
end
I think I’m happy enough with this approach, but if there’s suggestions on improvements, please do let me know
Thanks! We could add it in 6.1. Currently there’s laser-focus on getting 6.0 done for Hanami 2.0 with essential changes/improvements done exclusively. Everything extra will come after 6.0.