2

Say I have a simple model like this with a field called "name" and an attribute called "aliased_name":

class User < ActiveRecord::Base
   attr_accessor :aliased_name
end

User.create(name: "Faye Kname")

I can do:

user=User.select(:id, :name)
user.name # Faye Kname

But how can I use select to populate the aliased_name attribute.

user=User.select(:id, "name AS aliased_name")
user.aliased_name # nil
user[:aliased_name] # Faye Kname

I can access on the :aliased_name symbol, but the attribute is not assigned. I'd like to not have to do

user.aliased_name = user[:aliased_name]

I'm actually doing a more complex join on another table and I'm trying to select a field from the join table into the alias, but figured this would be a simpler example.

typeoneerror
  • 55,990
  • 32
  • 132
  • 223

3 Answers3

5

Typically I do these kinds of aliases with methods instead of attr_accessors. Something like

def aliased_name
  has_attribute?(:aliased_name) ? read_attribute(:aliased_name) : self.name
end

The has_attribute? is there in case you didn't load the attribute with your query, so you can have a default value.

kddeisz
  • 5,162
  • 3
  • 21
  • 44
1

So the attr_accessor is looking for the instance variable @aliased_name which I don't think is being set in your code. You can set it with @aliased_name = "some value" or using the attr_accessor aliased_name = "some value", but it's not going to be set with the initial query that returns the object, or in the second SELECT query, at least as it's written now.

One route that might make sense would be to use both a separate method and attr_writer. Something like this

    attr_writer :aliased_name

    def aliased_name
      @aliased_name ||= self.name
    end

This sets the instance variable the first time it's called and leaves you free to change it with the attr_writer. I'm not sure how this fits in with the more complex join, but this is a fairly simple way to solve the problem you describe initially.

1

You may be better using alias_attribute:

#app/models/user.rb
class User < ActiveRecord::Base
  alias_attribute :aliased_name, :name
end

Although it will only take user.name data & put it into user.alias_attribute


I'm trying to select a field from the join table into the alias

Done this before:

You have two options. Either use an SQL ALIAS column, or access the proxy_association method in your model. I have worked extensively with both:

--

SQL Alias

#app/models/parent.rb
class Parent < ActiveRecord::Base
  has_many :joins
  has_many :children, -> { select("#{Parent.table_name}.*, #{Join.table_name}.attr AS alias_name") }, through: :joins, dependent: :destroy
end

This will give you...

@parent.children.each do |child|
  child.alias_name
end

--

Association Extensions

The next method is a lot more complicated; more efficient:

#app/models/parent.rb
class Parent < ActiveRecord::Base
   has_many :joins
   has_many :children, through: :joins, -> { extending AliasAttribute }
end

#app/models/concerns/alias_attribute.rb
module PlayerPermission

        #Load
        def load
          alias_names.each do |permission|
              proxy_association.target << permission
          end
        end

        #Private
        private

        #Names
        def names
           return_array = []
           through_collection.each_with_index do |through,i|
              associate = through.send(reflection_name)
              associate.assign_attributes({name: items[i]})
              return_array.concat Array.new(1).fill( associate )
           end
           return_array
        end

        #######################
        #      Variables      #
        #######################

        #Association
        def reflection_name
           proxy_association.source_reflection.name
        end

        #Foreign Key
        def through_source_key
           proxy_association.reflection.source_reflection.foreign_key
        end

        #Primary Key
        def through_primary_key
           proxy_association.reflection.through_reflection.active_record_primary_key
        end

        #Through Name
        def through_name
           proxy_association.reflection.through_reflection.name
        end

        #Through
        def through_collection
           proxy_association.owner.send through_name
        end

        #Captions
        def items
            through_collection.map(&:name)
        end

        #Target
        def target_collection
            #load_target
            proxy_association.target
        end

end

Each time you call an association, you have access to the .association object for it. Within the association itself, you have access to proxy_association objects; all of which can be manipulated to insert the aliased data into your parent data.

The above will allow you to use:

@parent = Parent.find x
@parent.children.each do |child|
  child.alias_name
end 

I can provide support if required.

Community
  • 1
  • 1
Richard Peck
  • 76,116
  • 9
  • 93
  • 147