Sean McCleary
Sean McCleary

Reputation: 3820

ActiveRecord Virtual Attributes treaded as a record attributes

I am running into a problem with to_json not rendering my virtual attributes

class Location < ActiveRecord::Base
    belongs_to :event
    before_create :generate_oid
    validates_associated :event

    attr_accessor :event_oid

    def event_oid
      @event_oid = event.oid
    end
end

event_oid is not part of the array returned by:

Location.first.attributes

This is particularly a problem for me when using to_json that automatically serializes record attributes to jason. to_json omits my virtual attribute.

How can you make a virtual attribute treated as an actual instance attribute?

Edit:

to_json is just an example of a method where having my virtual attribute treated as an actual attribute would be nice.

Upvotes: 11

Views: 5648

Answers (6)

peter
peter

Reputation: 42182

An old question but what OP asked is now possible and simple once you know how. Here a complete runnable example but everything you need is in the class Location. The attribute statement expands the attributes of the model and the after_initialize takes care of the assignment of the value.

require 'active_record'  

ActiveRecord::Base.establish_connection(  
  :adapter=> "sqlite3",  
  :database=> ":memory:"  
)  

ActiveRecord::Schema.define do
  create_table :events do |table|
    table.column :name, :string
  end
  create_table :locations do |table|
    table.column :name, :string
    table.column :event_id, :integer
  end
end

class Event < ActiveRecord::Base
  has_one :location
end

class Location < ActiveRecord::Base
  belongs_to :event
  attribute :event_name, :string

  after_initialize do
    self.event_name = event.name
  end
end

Event.create(name: 'Event1')
Location.create(name: 'Location1', event_id: 1)
p Model.attribute_names
p Event.first
p Event.first.location

#["id", "name", "event_id", "event_name"]
#<Event id: 1, name: "Event1">
#<Location id: 1, name: "Location1", event_id: 1, event_name: "Event1">

Upvotes: 1

Iwan B.
Iwan B.

Reputation: 4166

location.to_json(:methods => :event_oid)

As explained here: http://apidock.com/rails/ActiveRecord/Serialization/to_json

In a controller simply use:

format.json { render json: @location.to_json(:methods => :event_oid) }

Upvotes: 0

webdevguy
webdevguy

Reputation: 985

I tried François Beausoleil's answer and got it to work after modifying to_json to as_json

def as_json(options={})
  options[:methods] ||= []
  options[:methods] << :class_to_s
  super(options)
end

Upvotes: 0

EmFi
EmFi

Reputation: 23450

You want to modify the attributes hash. There's a little extra code here to ensure the attributes you care about are ready to be used with to_json or another method that depends on attributes on object load.

class Location < ActiveRecord::Base
    belongs_to :event
    before_create :generate_oid
    validates_associated :event

    after_save :event_oid

    attr_accessor :event_oid

    def event_oid
      @event_oid = @attributes["event_oid"] = event.oid if event.nil?
    end       

    def after_initialize
      event_oid
    end


end

to_json and a lot of other methods that generate lists of things based on an objects attributes. Which is populated on object initialization with database tables and names, unfortunately instance variables do not update this hash.

P.S. this isn't very DRY if you have a number of attributes you want to use in this manner. You could use an Array of symbols, deterministic method names and a class_eval block to apply this process to multiple symbols at once.

Warning

We're messing with rails internals here. There's no telling how it could cause other things to fail. I haven't tested more than save and to_json, both of which work when the attribute hash contains keys that are not also column names. So use it at your own risk.

Upvotes: 6

Fran&#231;ois Beausoleil
Fran&#231;ois Beausoleil

Reputation: 16515

Simply implement #to_json yourself then:

class Location < ActiveRecord::Base
  def to_json(options={})
    options[:methods] ||= []
    options[:methods] << :event_oid
    super(options)
  end
end

Upvotes: 3

Omar Qureshi
Omar Qureshi

Reputation: 9093

how about to_json(:methods => [:event_oid]), does that work?

Upvotes: 3

Related Questions