ActiveModel attributes
Asked Answered
C

5

12

How do I get ActiveRecord attributes method functionality? I have this class:

class PurchaseForm
  include ActiveModel::Validations
  include ActiveModel::Conversion
  extend ActiveModel::Naming

  attr_accessor :name,
                :surname,
                :email

  validates_presence_of :name

  validates_format_of :email, :with => /^[-a-z0-9_+\.]+\@([-a-z0-9]+\.)+[a-z0-9]{2,4}$/i

  def initialize(attributes = {}, shop_name)
    if not attributes.nil?
      attributes.each do |name, value|
      send("#{name}=", value)
    end
  end

  def persisted?
    false
  end
end

What I need to do, to have an attributes method to list all names and values from PurchaseForm object?

Chivalry answered 28/3, 2011 at 17:4 Comment(2)
In view of the hacky existing answers, the correct answer seems to be "there's no Rails module for that". Personally I'd avoid mimicking the attributes API.Brest
Maybe the ActiveModel::Attributes module is what you were looking for?Dev
C
7

I've managed to solve problem with this code:

class PurchaseForm
  include ActiveModel::Validations
  include ActiveModel::Conversion
  extend ActiveModel::Naming

  attr_accessor :attributes,
                :name,
                :surname,
                :email

  validates_presence_of :name

  validates_format_of :email, :with => /^[-a-z0-9_+\.]+\@([-a-z0-9]+\.)+[a-z0-9]{2,4}$/i

  def initialize(attributes = {})
    @attributes = attributes
  end

  def persisted?
    false
  end
end
Chivalry answered 28/3, 2011 at 18:8 Comment(3)
Why did you add :attributes to the attr_accessor call ?Orazio
So I can call an attributes method from view...I'm pretty much newbie on Rails so if you have better solution, please share :)Chivalry
This solution has drawbacks. If you won't pass all attributes to .new, .attributes would return incorrect hash with only specified attributes. And second, if you try will to change instance after initialization, say @purchase_form.name = 'Other name' and then call @purchase_form.attributes the hash would contain old :name provided to .new.Cribriform
C
17

Here is the refactored variant:

class PurchaseForm
  include ActiveModel::Model

  def self.attributes
    [:name, :surname, :email]
  end

  attr_accessor *self.attributes

  # your validations

  def to_hash
    self.class.attributes.inject({}) do |hash, key|
      hash.merge({ key => self.send(key) })
    end
  end  
end

Now you can easily work with this class:

irb(main):001:0> a = PurchaseForm.new({ name: 'Name' })
=> #<PurchaseForm:0x00000002606b50 @name="Name">
irb(main):002:0> a.to_hash
=> {:name=>"Name", :surname=>nil, :email=>nil}
irb(main):003:0> a.email = '[email protected]'
=> "[email protected]"
irb(main):004:0> a
=> #<PurchaseForm:0x00000002606b50 @name="Name", @email="[email protected]">
irb(main):005:0> a.to_hash
=> {:name=>"Name", :surname=>nil, :email=>"[email protected]"}

Even more, if you want to make this behaviour reusable, consider extraction of .attributes and #to_hash methods into separate module:

module AttributesHash
  extend ActiveSupport::Concern

  class_methods do
    def attr_accessor(*args)
      @attributes = args
      super(*args)
    end

    def attributes
      @attributes
    end
  end

  included do
    def to_hash
      self.class.attributes.inject({}) do |hash, key|
        hash.merge({ key => self.send(key) })
      end
    end
  end
end

Now, just include it to your model and you're done:

class PurchaseForm
  include ActiveModel::Model
  include AttributesHash

  attr_accessor :name, :surname, :email

  # your validations
end
Cribriform answered 28/8, 2015 at 21:35 Comment(1)
May I suggest using @attributes ? @attributes += args : @attributes = args instead of just @attributes = args in the concern? This would allow attr_accessor to be called multiple times in the class.Decosta
C
15

#instance_values could do the job:

class PurchaseForm
  attr_accessor :name, :email

  def attributes
    instance_values
  end
end

Output sample:

purchase.attributes #=> {"name"=>"John", "email"=>"[email protected]"}
Caswell answered 18/11, 2016 at 15:6 Comment(1)
Documentation here: api.rubyonrails.org/classes/…Chloechloette
C
7

I've managed to solve problem with this code:

class PurchaseForm
  include ActiveModel::Validations
  include ActiveModel::Conversion
  extend ActiveModel::Naming

  attr_accessor :attributes,
                :name,
                :surname,
                :email

  validates_presence_of :name

  validates_format_of :email, :with => /^[-a-z0-9_+\.]+\@([-a-z0-9]+\.)+[a-z0-9]{2,4}$/i

  def initialize(attributes = {})
    @attributes = attributes
  end

  def persisted?
    false
  end
end
Chivalry answered 28/3, 2011 at 18:8 Comment(3)
Why did you add :attributes to the attr_accessor call ?Orazio
So I can call an attributes method from view...I'm pretty much newbie on Rails so if you have better solution, please share :)Chivalry
This solution has drawbacks. If you won't pass all attributes to .new, .attributes would return incorrect hash with only specified attributes. And second, if you try will to change instance after initialization, say @purchase_form.name = 'Other name' and then call @purchase_form.attributes the hash would contain old :name provided to .new.Cribriform
F
6

Let's try this

self.as_json

=> {:name=>"Name", :surname=>nil, :email=>"[email protected]"}

Facet answered 16/8, 2016 at 14:23 Comment(0)
H
-3

would it not be better to use

include ActiveModel::Serialization

def attributes
  JSON.parse(self.to_json)
end
Halve answered 18/3, 2013 at 14:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.