2

Given:

class Foo
  has_one :bar

  def bar_name
    bar.name
  end
end

class Bar
  belongs_to :foo
end

In the console or in a view, I can @foo.bar_name to get 'baz'.

I'm aware that I can @foo.as_json(methods: :bar_name) to get {"id"=>"abc123", "bar_name"=>"baz"}.

I could also denormalize the attribute and make it non-virtual, but I would rather not do that in this case.

Is it possible to automatically return the model with the virtual attribute included?

#<Foo id: "abc123", bar_name: "baz">

I want to do this because I am constructing a large object with nested collections of models, and the as_json call is abstracted away from me.

Micah Alcorn
  • 2,363
  • 2
  • 22
  • 45
  • I don't understand , you want to send `#` to clients or you want `Foo` objects to be printed like that in console(e.g. `puts fooobj # => #` ) ? – niceman May 09 '17 at 15:28
  • Let's say that I'm not sending anything to a client. I just want the result of the method to be shown in the console without explicitly calling the method. – Micah Alcorn May 09 '17 at 15:34
  • 2
    Do you want this in the `as_json` response? If so you can just redefine `as_json` in the model itself. If you want this in the object representation you can redefine `inspect` – engineersmnky May 09 '17 at 16:00

2 Answers2

3

Not 100% sure I understand if your concern is related to as_json but if so this will work

class Foo
  has_one :bar

  def bar_name
    bar.name
  end
  def as_json(options={})
    super(options.merge!(methods: :bar_name))
  end
end

Now a call to @foo.as_json will by default include the bar_name like your explicit example does.

Ugly would not recommend but you could change the inspection of foo e.g. #<Foo id: "abc123", bar_name: "baz"> as follows

class Foo
  def inspect
    base_string = "#<#{self.class.name}:#{self.object_id} "
    fields = self.attributes.map {|k,v| "#{k}: #{v.inspect}"}
    fields << "bar_name: #{self.bar_name.inspect}"
    base_string << fields.join(", ") << ">"
  end
end

Then the "inspection notation" would show that information although I am still unclear if this is your intention and if so why you would want this.

engineersmnky
  • 25,495
  • 2
  • 36
  • 52
  • Yes, that actually solves my real case that I mentioned at the end of my question. Someone actually posted that solution earlier and then deleted it. But I did still want to know whether or not it was possible to do prior to rendering in a single call. @mmichael seems to have confirmed that it is not. – Micah Alcorn May 09 '17 at 16:05
  • 1
    What do you mean "prior to rendering" if you want it in the object notation you would have to redefine `inspect` – engineersmnky May 09 '17 at 16:06
  • I guess I've learned that whether I'm in the console, a view, or receiving json, the inclusion of the virtual attribute is purely a presentational concern. Otherwise, there is no point in "automatically" including it in the return value when I can just make the method call. So the correct answer is something like, "yes, it's possible, but not worth doing when all that I need is to override the `as_json`". Thank you. – Micah Alcorn May 09 '17 at 16:12
0

You could use attr_accessor - per the Rails docs:

Defines a named attribute for this module, where the name is symbol.id2name, creating an instance variable (@name) and a corresponding access method to read it. Also creates a method called name= to set the attribute.

vich
  • 11,836
  • 13
  • 49
  • 66
  • Thanks, but that and `attr_reader` don't seem to automatically include the virtual attribute when the object is retrieved. I'm purely asking if I can get that console output without making an explicit call to the method. – Micah Alcorn May 09 '17 at 15:33
  • 1
    It's not possible to include the virtual attribute without a call to the method. There's also `model.instance_methods`, but that's just a slightly less explicit call. Short of monkey-patching Rails' built-in `attr_accessor`, I'm afraid you're going to have to make that method call. – vich May 09 '17 at 15:41