0

I want to declare a custom accessor in a Rails model like this:

footnote_attrs :title, :body

Then I want to access footnote_attrs in a method inside the model. footnote_attrs need just to return an array containing the args passed to it. In this case, it will be [:title, :body]. What is the best way to implement this?

2
  • What is supposed to happen if you call footnote_attrs again with different arguments? Commented Jan 5, 2017 at 15:15
  • It's a declaration so it is supposed to be set once. If you call it with different arguments later, it may set the accessor with the new arguments. Commented Jan 5, 2017 at 15:21

3 Answers 3

3

footnote_attrs :title, :body is a method invocation. If you want to call the method in your class like this:

class Foo
  footnote_attrs :title, :body
end

You have to define the method accordingly:

class Foo
  def self.footnote_attrs(*args)
    @footnote_attrs = args unless args.empty?
    @footnote_attrs
  end

  # if footnote_attrs is to be accessed like an instance method
  def footnote_attrs
    self.class.footnote_attrs
  end

  footnote_attrs :title, :body
end

Foo.footnote_attrs #=> [:title, :body]
footnote_attrs #=> [:title, :body]

The implementation is very basic. If the method is called with arguments, it sets the instance variable accordingly. If it is called without arguments, it just returns it.

You might want to return an empty array if footnote_attrs has not been called yet (currently it would return nil). It might also be a good idea to return a copy (dup) instead of the actual array to prevent modification.

Sign up to request clarification or add additional context in comments.

1 Comment

As I'm going to put self.footnote_attrs(*args) in a concern module and the related method which calls footnote_attrs, I wont have access to Foo. So I implemented a method def footnote_attrs self.class.footnote_attrs end. Now I can call with simply footnote_attrs without Foo.
1

You can use class methods to do this:

class SomeModel
  def self.foot_notes=(*foot_notes)
    @foot_notes = foot_notes
  end

  def self.foot_notes
    @foot_notes
  end

  def foot_notes
    self.class.foot_notes
  end

  # With the above in place, you can then:

  foot_notes = :foo, :bar
end

SomeModel.foot_notes # outputs [:foo, :bar]

some_model = SomeModel.new
some_model.foot_notes # outputs [:foo, :bar]

I'd suggest putting the foot_note methods into a concern and then apply that concern to each model that needs this functionality.

Note that I've used separate methods for setting and getting, as I think using the same method to do both is a bad pattern.

Note also that foot_notes = needs to be called after the methods are defined - so if you use concerns, it needs to go after the include statement for the concern.

1 Comment

I actually wanted a declaration like footnote_attrs :foo, :bar rather than assignment by footnote_attrs = :foo, :bar
0

This is not what attr_accessors are for.

class Foo
  attr_accessor :bar
end

# is equivalent to
class Foo
  def bar=(value)
    @bar = value
  end

  def bar
    @bar
  end
end

So your spec, having a method footnote_attrs return an array of the arguments passed to it, is very different from other attrs. What you really want is a variable or constant:

class Foo
  FootnoteAttrs = [:title, :body]
end

Which you can then utilize wherever…

class Foo
  def footnote
    FootnoteAttrs.map { |attr| send(attr) }.join("\n")
  end
end

If you were so inclined, you could use a cattr_accessor to make footnote_attrs have a set of accessor methods on the class definition object (that is, you would have a footnote_attrs= method you could use within class Foo; … end), but that's just polish.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.