I would like an idea to create an issue about add alias_attribute to method "attributes"

attributes= can use aliases. but, attributes doesn’t return aliases, so you can’t get aliases.

e.g.

Define ModelA and ModelB as follows:

class ModelA
  include ActiveModel::Model
  include ActiveModel::Attributes

  attribute :common_1
  attribute :common_2
  attribute :model_a_column
end
class ModelB
  include ActiveModel::Model
  include ActiveModel::Attributes

  attribute :common_1
  attribute :common_2
  attribute :model_b_column
  alias_attribute :model_a_column, :model_b_column
end

I want to use alias “model_a_column”, but model_b.attributes doesn’t return “model_a_column”.

% rails console
Loading development environment (Rails 7.0.4.3)
irb(main):001:0> model_a = ModelA.new
=> 
#<ModelA:0x00007fb499a76e58
...
irb(main):002:0> model_b = ModelB.new
=> 
#<ModelB:0x00007fb4978dcfe0
...
irb(main):003:0> attr = { "common_1" => "c1", "common_2" => "c2", "model_a_column" => "a_col" }
=> {"common_1"=>"c1", "common_2"=>"c2", "model_a_column"=>"a_col"}
irb(main):004:0> model_a.attributes = attr
=> {"common_1"=>"c1", "common_2"=>"c2", "model_a_column"=>"a_col"}
irb(main):005:0> model_a.attributes
=> {"common_1"=>"c1", "common_2"=>"c2", "model_a_column"=>"a_col"}
irb(main):006:0> model_b.attributes = attr
=> {"common_1"=>"c1", "common_2"=>"c2", "model_a_column"=>"a_col"}
irb(main):007:0> model_b.attributes
=> {"common_1"=>"c1", "common_2"=>"c2", "model_b_column"=>"a_col"}
irb(main):008:0> model_a.attributes.slice(*attr.keys)
=> {"common_1"=>"c1", "common_2"=>"c2", "model_a_column"=>"a_col"}
irb(main):009:0> model_b.attributes.slice(*attr.keys)
=> {"common_1"=>"c1", "common_2"=>"c2"}

I expect model_b.attributes to return ailias, like this:

irb(main):011:0> model_b.attributes.merge(model_b.attribute_aliases.transform_values { |v| model_b.send(v) })
=> {"common_1"=>"c1", "common_2"=>"c2", "model_b_column"=>"a_col", "model_a_column"=>"a_col"}
irb(main):012:0> model_b.attributes.merge(model_b.attribute_aliases.transform_values { |v| model_b.send(v) }).slice(*attr.keys)
=> {"common_1"=>"c1", "common_2"=>"c2", "model_a_column"=>"a_col"}

System configuration

Rails version: 7.0.4.3

Ruby version: 3.2.2

1 Like

I like this idea since I’ve faced the same issue when I’m looking to export specific attributes for an API, using aliased keys. You might want to consider an option to either replace or include the alias attributes. Here’s what I’ve used.

  def attributes_with_aliases(replace: false)
    attrs = attributes.clone
    attribute_aliases.each do |k, v|
      attrs[k] = replace ? attrs.delete(v) : send(v)
    end
    attrs
  end

I benchmarked the two methods on a model with 197 attributes and the efficiency is negligible.

#<Benchmark::Tms:0x00007fcf40de20a8 @cstime=0.0, @cutime=0.0, @label="", @real=0.004953999996359926, @stime=0.0002630000000000132, @total=0.003952000000000067, @utime=0.0036890000000000533>
#<Benchmark::Tms:0x00007fcf3ff392b8 @cstime=0.0, @cutime=0.0, @label="", @real=0.0044419999976526015, @stime=0.00017299999999997873, @total=0.003754999999999953, @utime=0.003581999999999974>
1 Like

Thanks for the good idea! I really liked the suggestion to define another method. I also really liked the method name.

If replace is true , the suggested code would be:

irb(main):067:0> attrs = model_b.attributes.clone
=> {"common_1"=>"c1", "common_2"=>"c2", "model_b_column"=>"a_col"}
irb(main):068:0> model_b.attribute_aliases.each { |k, v| attrs[k] = attrs.delete(k) }
irb(main):069:0> attrs
=> {"common_1"=>"c1", "common_2"=>"c2", "model_b_column"=>"a_col", "model_a_column"=>nil}

Is the correct code “attrs.delete(v)”?

irb(main):070:0> attrs = model_b.attributes.clone
=> {"common_1"=>"c1", "common_2"=>"c2", "model_b_column"=>"a_col"}
irb(main):071:0> model_b.attribute_aliases.each { |k, v| attrs[k] = attrs.delete(v) }
irb(main):072:0> attrs
=> {"common_1"=>"c1", "common_2"=>"c2", "model_a_column"=>"a_col"}
2 Likes

Yes, that’s right. :sweat_smile: Silly mistake. Corrected, thank you!

1 Like

thank you. I will create an issue by the end of this month.

1 Like

Something else to consider would be applying this on a Class level as well for attribute_names

  def self.attribute_names_with_aliases(replace: false)
    attrs = attribute_names.map(&:clone)
    attribute_aliases.each do |k, v|
      next unless (i = attrs.find_index(v))

      attrs[replace ? i : attrs.length] = k
    end
    attrs
  end

The conditional next would ignore any associations that are aliased.

I created issue.

1 Like

Would you be opposed to giving me co-author credit on GitHub for my method contributions?

Sorry if I didn’t understand correctly. Is this correct?

I would like to credit you as a co-author, of course, because you have contributed so much.

1 Like

I think that’s the right protocol for co-authoring ?? Either way, than

I’ve invited you to be a collaborator on this repository for when you want to commit.