Ruby (Rails) Delegate attributes to another model's method?

rails delegate scope
rails delegate if
active_support delegate
rails delegate has_one
rails delegate to hash
rails serializer delegate
rails delegate setter
rails delegate has_many

-EDIT-

After reading about the Delegate method from the first answer, my question is this, is it possible to delegate two different methods to another single method.

IE: I currently have: @photo.attachment.file.url, and @photo.attachment.height, and @photo.attachment.width

I'd like to be able to access all of these via @photo.file.url, @photo.file.height, @photo.file.width.

The reason for the syntax is Attachment is a model that uses Paperclip to manage files, Paperclip is generating the .file method (the model is called Attachment, the model uses Paperclip's has_attached_file :file).

-ORIGINAL QUESTION-

I was wondering about aliasing methods and attributes in Ruby (I think this is a general ruby question, although my application is in Rails 3):

I have two models: Photo has_one Attachment.

Attachment has "height" and "width" attributes, and a "file" method (from Paperclip).

So by default I can access bits of the Attachment model like so:

photo.attachment.width # returns width in px
photo.attachment.height # returns height in px
photo.attachment.file # returns file path
photo.attachment.file.url #returns url for the default style variant of the image
photo.attachment.file.url(:style) #returns the url for a given style variant of the image

Now, in my photo class I have created this method:

def file(*args)
    attachment.file(*args)
end

So, now I can simply use:

photo.file # returns file path
photo.file.url # returns file url (or variant url if you pass a style symbol)

My question is, I was able to direct photo.attachment.file to just photo.file, but can I also map height and width to photo.file, so that, for the sake of consistency, I could access the height and width attributes through photo.file.height and photo.file.width?

Is such a thing possible, and if so what does it look like?

So what you are asking is that

photo.file       --> photo.attachment.file
photo.file.url   --> photo.attachment.file.url
photo.file.width --> photo.attachment.width

You can't solve this with delegates, because you want that file to mean different things based on what follows next. To achieve this you would need to reopen paperclip, which i would not recommend (because i believe the api is good the way it is).

The only way i can think of to solve this, is to add eliminate the file level too. Like so:

photo.width      --> photo.attachment.width
photo.file       --> photo.attachment.file
photo.url        --> photo.attachment.file.url

This you could then solve by using a delegate for each of the wanted methods.

So you write

class Photo
  delegate :width, :height, :file, :to => :attachment
  delegate :url,   :to => :'attachment.file'
end

Hope this helps.

Using delegate in Rails - JiaHung Lin, Simply say, through delegation you can use public methods in other model directly. For example I have a QueueItem and a Video model. QueueItem <  In Ruby, every object inherits from the Object class by default. That’s why you get access to methods like puts, class & object_id. With composition a class creates (or is given) objects from another class… then it uses these objects to delegate work to them.

You can use Rails 'delegate' method. Have a look at my answer for this question:

What is a more Ruby-like way of doing this command?

delegate (Module), delegate. Importance_3. Ruby on Rails latest stable (v5.2.3) - 0 notes - Class: Module delegate(*methods, to: nil, prefix: nil, allow_nil: nil) public. Provides a  There are better ways to implement the delegation pattern in Ruby. The Ruby standard library contains a Delegate module that aims to provide support for the Delegation pattern. Sadly, I found it to be way more complex than the traditional approach and I never really used it. ActiveSupport Delegate module

The simplest way that comes to mind is to delegate url method in attachment to file:

class Attachment < ActiveRecord::Base
  delegate :url, :to => :file
end

This way you can call photo.attachment.url, photo.attachment.width, photo.attachment.height, which for me seems pretty consistent. You could optionally alias attachment to file - this way you'd get the exact method names you asked for (photo.file.width, photo.file.url), but I would not recommend that, because it seems confusing (calling an attachment "file").

class Photo < ActiveRecord::Base
  def file
    attachment
  end
end

How to delegate methods in Ruby? - Kamil Lelonek, 5 ways of forwarding your work to some other object in Ruby So let's begin our journey and try to model our sandwich shop. Delegate. If your project includes ActiveSupport (and every Rails project does) you have a more clean and easy  Delegate Attributes. Association attributes delegator plugin. It delegates association attributes accessors to a model. It also supports ActiveRecord::Dirty check, multiparamenter attributes (1i, 2i, 3i) assigning, auto saving for delegated attributes (see below).

With plain Ruby you can use Forwardable:

require 'forwardable'

class RecordCollection
  attr_accessor :records
  extend Forwardable
  def_delegator :@records, :[], :record_number
end

Understanding Ruby and Rails: Delegate, A small tutorial about the Ruby on Rails delegate method which For instance, consider a standard Post model which belongs to a User . If the value is true, the delegate methods are prefixed with the name of the object being delegated to. Person = Struct . new ( :name , :address ) class Invoice < Struct . new ( :client ) delegate :name , :address , to : :client , prefix : true end john_doe = Person . new (' John Doe ', ' Vimmersvej 13 ') invoice = Invoice . new ( john_doe ) invoice . client_name # => "John Doe" invoice . client_address # => "Vimmersvej 13"

Ruby on Rails Delegate: Don't break the Law of Demeter !, Ruby on Rails Delegate: Don't break the Law of Demeter ! If we put in another way, an object should not call methods through another The models will grow out of control if we define methods for each attribute like this. 4. Delegate. If your project includes ActiveSupport (and every Rails project does) you have a more clean and easy way to implement the delegation pattern — the Module#delegate extension. It provides delegate method that you can use in your class or your modules to delegate a specific method to an associate object.

Module, Extends the module object with class/module and instance accessors for class/​module attributes, just like the native attr* accessors for  Using delegate makes it really simple to call a method on one class from another. This can be useful to hide the implementation of a method in a related class. Especially in models with intricate relationships you may make a lot of calls from one class to another. Rails provides a delegate macro that allows you to do this easily.

Delegation patterns in Ruby, A typical example of a god object is the User model of many Rails So inheritance is delegation, but really any time you use another object to  GitHub is home to over 40 million developers working together to host and review code, manage projects, and build software together. Sign up Delegate ActiveRecord model attributes and associations.

Comments
  • Why not just delegate Photo#height to Attachment#height? Why bother with photo.file.height?
  • Just for the sake of consistency. Photo.height is fine, but if all file related commands are under Photo.file that's more fitting with Principal of Least Surprise, at least to me. Also, just curious if it can be done.
  • Why is this not accepted ? Rails delegate method is the answer here right ? What was the problem with that ?
  • Maybe I'm failing to understand something here, but what I want to know is if I can delegate @photo.attachment.height, where height is an attribute of the attachment model, to @photo.file.height, where file is a method provided by a plugin. I don't understand how to do that based on the answer given, and I wasn't able to deduce it for myself. So, if you can answer that question, you get the bounty :)
  • Regarding your edit, ruby doesn't have a separate notion of attributes compared to methods. They're all just methods.
  • "You can't solve this with delegates, because you want that file to mean different things based on what follows next." There's the answer. Thanks! I think your way is probably best, though it does involve a little rewriting. Thanks for the answer!
  • GREAT suggestion! I had never seen "delegate" before! Do you have an example of how I could delegate attachment to photo.file specifically, rather than just to photo ?
  • Right -- the issue is, I currently have photo.file pointing to photo.attachment.file, what I'd like is to keep that and also point photo.attachment.height and photo.attachment.width to photo.file so that photo.file.height (from photo.attachment.height) and photo.file.url (from photo.attachment.file.url ) would both work.