How to Cleanly Initialize Attributes in Ruby with New

How to cleanly initialize attributes in Ruby with new?

def initialize(params)
params.each do |key, value|
instance_variable_set("@#{key}", value)
end
end

Is there a clean way to remove the common ruby object initialization code?

If you want the hash keys matches the attribute name, one possibility is to inherit a struct:

class Foo < Struct.new(:bar, :baz, keyword_init: true) # Note the "keyword_init" parameter
end

puts Foo.new(bar: 42, baz: :foo).bar
# => 42

Struct will also create public writers, so if you want them not to be public you have to set them private

FOO_ATTRS = [:bar, :baz]
class Foo < Struct.new(*FOO_ATTRS, keyword_init: true) # Note the "keyword_init" parameter
# Make method 'my_attribute=' private if needed
private *FOO_ATTRS.map { |attr| "#{attr}=" }
end

puts Foo.new(bar: 42, baz: :foo).bar

If you want to rename hash keys for a different attribute name:

class Foo
INITIAL_ATTRS = {
foo: :@bar,
bar: :@baz
}

attr_reader *(INITIAL_ATTRS.values.map { |k| k.to_s.delete('@').to_sym })

def initialize attrs
attrs.slice(*INITIAL_ATTRS.keys).each do |var, value|
instance_variable_set(INITIAL_ATTRS[var], value)
end
end
end

EDIT

Another possibility is to use private attr_writer so you don't have to deal with @ tricks

class Foo
INITIAL_ATTRS = {
foo: :bar,
bar: :baz
}

attr_reader *(INITIAL_ATTRS.values)

def initialize attrs
attrs.slice(*INITIAL_ATTRS.keys).each do |var, value|
send("#{INITIAL_ATTRS[var]}=", value)
end
end

private

attr_writer *(INITIAL_ATTRS.values)
end

Note that if you don't want to translate foo to bar you can use an array instead of a hash for INITIAL_ATTRS. This would simplify the code by removing .values and .keys

class Foo
INITIAL_ATTRS = [:foo, :bar]

attr_reader *INITIAL_ATTRS

def initialize attrs
attrs.slice(*INITIAL_ATTRS).each do |var, value|
send("#{var}=", value)
end
end

private

attr_writer *INITIAL_ATTRS
end

Rails: How to initialize an object with the attributes in strings?

Here is several ways to fix this:

hash_map = {"Pizza."=>"pizza_pie","PastaBowl"=>"pasta_bowl","tacos"=>"hard_shell_taco","IceCream"=>"ice_cream","PopTarts"=>"pop_tart"}

attributes.each do |attribute, element|
message.send((attribute + '=').to_sym, hash_map[element])
end

or like this:

class Example
attr_reader :Pizza, :PastaBowl #...

def initialize args
args.each do |k, v|
instance_variable_set("@#{k}", v) unless v.nil?
end
end
end

for more details click here

Ruby class initialization

I would try using a hash for your constructor like the code below adapted from DRY Ruby Initialization with Hash Argument

class Example
attr_accessor :id, :status, :dateTime

def initialize args
args.each do |k,v|
instance_variable_set("@#{k}", v) unless v.nil?
end
end
end

That way setting each of your properties in the constructor becomes optional. As the instance_variable_set method will set each property if the has contains a value for it.

Which means you could support any number of ways to construct your object. The only downside is you might have to do more nil checking in your code but without more information it is hard to know.

Creating a new Object - Usage Examples

To create a new object with this technique all you need to do is pass in a hash to your initialiser:

my_new_example = Example.new :id => 1, :status => 'live'
#=> #<Example: @id=1, @status='live'>

And its flexible enough to create multiple objects without certain properties with one constructor:

my_second_new_example = Example.new :id => 1
#=> #<Example: @id=1>

my_third_new_example = Example.new :status => 'nonlive', :dateTime => DateTime.new(2001,2,3)
#=> #<Example: @id=1, @dateTime=2001-02-03T00:00:00+00:00>

You can still update your properties once the objects have been created:

my_new_example.id = 24

Ruby initialize method: setting instance variable with a hash key

I believe this implementation is the closest one to the Java one.

class Model
attr_accessor :var1, :var2, :state

def initialize (x, y, key)
@var1 = x
@var2 = y
@every_state = {
:A => SataeA.new,
:B => StateB.new,
:C => StateC.new,
:D => StateD.new
}
@state = select_state key
end

def select_state(key)
@every_state[key]
end
end

rails constructor def initialize with attributes: correct way to pass in model

You could try (pseudocode/untested):

def initialize(business)
puts 'inside Listing.initialize'

@attributes.merge(business.attributes)

puts 'Created a new Listing'

end

Initialize a Ruby class from an arbitrary hash, but only keys with matching accessors

This is what I use (I call this idiom hash-init).

 def initialize(object_attribute_hash = {})
object_attribute_hash.map { |(k, v)| send("#{k}=", v) }
end

If you are on Ruby 1.9 you can do it even cleaner (send allows private methods):

 def initialize(object_attribute_hash = {})
object_attribute_hash.map { |(k, v)| public_send("#{k}=", v) }
end

This will raise a NoMethodError if you try to assign to foo and method "foo=" does not exist. If you want to do it clean (assign attrs for which writers exist) you should do a check

 def initialize(object_attribute_hash = {})
object_attribute_hash.map do |(k, v)|
writer_m = "#{k}="
send(writer_m, v) if respond_to?(writer_m) }
end
end

however this might lead to situations where you feed your object wrong keys (say from a form) and instead of failing loudly it will just swallow them - painful debugging ahead. So in my book a NoMethodError is a better option (it signifies a contract violation).

If you just want a list of all writers (there is no way to do that for readers) you do

 some_object.methods.grep(/\w=$/)

which is "get an array of method names and grep it for entries which end with a single equals sign after a word character".

If you do

  eval("@#{opt} = \"#{val}\"")

and val comes from a web form - congratulations, you just equipped your app with a wide-open exploit.

Can I Populate / update a new record after using find_or_initialize_by with one line like when using new()?

You can pass a block to set the attributes to the new records. This is useful when you want to create them with some values and you don't want to override the existing records.

modal = Modal.find_or_initialize_by(col1: col1, col3: col3) do |record|
record.col4 = col4
record.col5 = col5
# etc
end

You can play with its syntax to oneline it, using #attributes=:

modal = Modal.find_or_initialize_by(col1: col1, col3: col3) { |r| r.attributes = { col4: col4, col5: col5 } }

Note that #attributes= will only set the values, you have to manually save. If you use .find_or_create_by instead of .find_or_initialize_by, it will validate and save the new record after the block's execution.

If you want to set the attributes the record, regardless if it's new or not:

modal = Modal.find_or_initialize_by(col1: col1, col3: col3)
modal.attributes = { col4: col4, col5: col5 }

You can also use #update_attributes, but your record will be validated and saved.

modal = Modal.find_or_initialize_by(col1: col1, col3: col3)
modal.update_attributes(col4: col4, col5: col5)


Related Topics



Leave a reply



Submit