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
In Rails - Is There a Rails Method to Convert Newlines to <Br>
How to Validate Exits and Aborts in Rspec
Confusion About Passing Instance Variables to Redirect_To Method. as Seen in Rails Guides
In 'Require': No Such File to Load -- Iconv (Loaderror)
How to List Ruby Production Only Dependencies Using Gemfile.Lock and Lockfileparser Class
Any Success with Sinatra Working Together with Eventmachine Websockets
Running Pod Setup Gives Me "Bad Interpreter: No Such File or Directory" Error
How to Get the Class of a Basicobject Instance
How to Get the Current Time as 13-Digit Integer in Ruby
How to Compare Strings Ignoring the Case
How to Decompress Gzip String in Ruby
Always Getting 401 Unauthorized with New Install of Rails + Devise
Unexpected Output in Ruby on Rails