Ruby object mass assignment - ruby

Is there any better way to do the below code?
user.name = "abc"
user.email = "abc#test.com"
user.mobile = "12312312"
Something like this will do:
user.prepare do |u|
u.name = "abc"
u.email = "abc#test.com"
u.mobile = "12312312"
end

tap let's you do exactly that:
user.tap do |u|
u.name = "abc"
u.email = "abc#test.com"
u.mobile = "12312312"
end

Alternative option when your attributes come in the form of a hash:
attrs = {
name: "abc",
email: "abc#test.com",
mobile: "12312312"
}
attrs.each { |key, value| user.send("#{key}=", value) }

You could do the following as well:
user.instance_eval do
#name = "abc"
#email = "abc#test.com"
#mobile = "12312312"
end
You can access the instance variables of user inside the block given to instance_eval
You could use the below code if you wish to invoke the accessor methods instead of directly manipulating the instance variables.
user.instance_eval do
self.name = "xyz"
self.email = "abc#test.com"
self.mobile = "12312312"
end
or
user.instance_eval do |o|
o.name = "xyz"
o.email = "abc#test.com"
o.mobile = "12312312"
end

With ActiveRecord objects you can use .assign_attributes or the update
methods:
user.assign_attributes( name: "abc", email: "abc#test.com", mobile: "12312312")
# attributes= is a shorter alias for assign_attributes
user.attributes = { name: "abc", email: "abc#test.com", mobile: "12312312" }
# this will update the record in the database
user.update( name: "abc", email: "abc#test.com", mobile: "12312312" )
# or with a block
user.update( name: "abc", mobile: "12312312" ) do |u|
u.email = "#{u.name}#test.com"
end
.update accepts a block, while assign_attributes does not. If you are simply assigning a hash of literal values - such as those passed by a user in the params then there is no need to use a block.
If you have a plain old ruby object which you want to spice up with mass assignment you can do:
class User
attr_accessor :name, :email, :mobile
def initialize(params = {}, &block)
self.mass_assign(params) if params
yield self if block_given?
end
def assign_attributes(params = {}, &block)
self.mass_assign(params) if params
yield self if block_given?
end
def attributes=(params)
assign_attributes(params)
end
private
def mass_assign(attrs)
attrs.each do |key, value|
self.public_send("#{key}=", value)
end
end
end
This will let you do:
u = User.new(name: "abc", email: "abc#test.com", mobile: "12312312")
u.attributes = { email: "abc#example.com", name: "joe" }
u.assign_attributes(name: 'bob') do |u|
u.email = "#{u.name}#example.com"
end
# etc.

Assuming that 'user' is a class that you control, then you can just define a method to do what you want. For example:
def set_all(hash)
#name, #email, #mobile = hash[:name], hash[:email], hash[:mobile]
end
And then in the rest of your code:
user.set_all(name: "abc", email: "abc#test.com", mobile: "12312312")
If 'user' is an instance of, say, an ActiveRecord model, then I'm a little shaky on the details of how you would get this to work. But the principal still applies: DRY up code by moving the responsibility for the complexity to the receiver.

Related

Parse json to ruby object

In ruby how can i parse a json to an array of objects?
Example: i have 2 classes:
class Person
attr_accessor :name, :address, :email, :address
end
And:
class Address
attr_accessor :street, :city, :state, :person
end
When i make a request i get the following json:
{
"data": [
{
"id": 9111316,
"name": "Mason Lee",
"email": "normanodonnell#biospan.com",
"address": {
"state": "American Samoa",
"street": "Cameron Court",
"city": "Wakulla"
}
},
{
"id": 500019,
"name": "Stella Weeks",
"email": "hansenwhitfield#candecor.com",
"address": {
"state": "Nevada",
"street": "Lake Street",
"city": "Wacissa"
}
}
]
}
This json should be parsed into an array of Person.
For now i'm doing:
#json gem
require 'json'
#...
#parse the json and get the 'data'
parsed_json = JSON.parse json
json_data = parsed_json['data']
objects = Array.new
if json_data.kind_of?(Array)
#add each person
json_data.each { |data|
current_person = Person.new
data.each { |k, v|
current_person.send("#{k}=", v)
}
objects.push(current_person)
}
end
#return the array of Person
objects
I have a lot of objects like the above example and do this parse manually is not desirable. There is an automated way to do this?
By "automated way" i mean something like in java with jackson:
ObjectMapper mapper = new ObjectMapper();
List<Person> myObjects = mapper.readValue(json, mapper.getTypeFactory().constructCollectionType(List.class, Person.class));
You can initialize the Person with the hash:
json_data = JSON.parse(json)['data']
json_data.map do |data|
Person.new data
end
class Person
attr_accessor :name, :email, :address
def initialize params
params.each { |k,v| klass.public_send("#{k}=",v) }
end
end
If you want to choose the class dynamically, you can use:
json_data.map do |data|
klass = 'Person'
klass.get_const.new data
Why not just make the method yourself? Example:
require 'json'
def parse_json_to_class_array(data,root_node,to_klass)
json_data = JSON.parse(data)[root_node]
if json_data.is_a?(Array)
objects = json_data.map do |item|
klass = to_klass.new
item.each { |k,v| klass.public_send("#{k}=",v) }
klass
end
end
objects ||= []
end
Then for your example you could call it like so
json ="{\"data\":[
{\"id\":9111316,
\"name\":\"Mason Lee\",
\"email\":\"normanodonnell#biospan.com\",
\"address\":{
\"state\":\"American Samoa\",
\"street\":\"Cameron Court\",
\"city\":\"Wakulla\"
}
},
{\"id\":500019,
\"name\":\"Stella Weeks\",
\"email\":\"hansenwhitfield#candecor.com\",
\"address\":{
\"state\":\"Nevada\",
\"street\":\"Lake Street\",
\"city\":\"Wacissa\"
}
}
]
}"
class Person
attr_accessor :id, :name,:email, :address
end
parse_json_to_class_array(json,'data',Person)
#=>[#<Person:0x2ede818 #id=9111316, #name="Mason Lee", #email="normanodonnell#biospan.com", #address={"state"=>"American Samoa", "street"=>"Cameron Court", "city"=>"Wakulla"}>,
#<Person:0x2ede7a0 #id=500019, #name="Stella Weeks", #email="hansenwhitfield#candecor.com", #address={"state"=>"Nevada", "street"=>"Lake Street", "city"=>"Wacissa"}>]
Obviously you can expand this implementation to support single objects as well as overwrite Person#address= to perform the same operation and turn the address Hash into an Address object as well but this was not shown in your example so I did not take it this far in my answer.
A more dynamic example can be found Here

I have a class with some parameters that I'd like to be filled using data from a JSON file

I am trying to create a monopoly manager for fun and I have the data for all the tiles in a JSON file which I'd like to use to iterate over and use for the data. I currently have this code:
require 'json'
file = File.read('monopoly-data.json')
data_hash = JSON.parse(file)
data = data_hash['properties'].sort_by{ |e| e['id'].to_i }
class Property
attr_accessor :id, :group, :colour, :name, :price, :rent, :house_price, :mortage
def initialize(params = {})
#id = params[:id]
#group = params[:group]
#colour = params[:colour]
#name = params[:name]
#price = params[:price]
#rent = params[:rent]
#house_price = params[:house_price]
#mortage = params[:mortage]
end
end
And the JSON file is in this gist.
I haven't been able to figure out how to use the JSON data to make more properties, I have played around with for loops on the data, data.each trying to make something like this automatically:
parkveien = Property.new(
id: 1,
group: 'property',
colour: 'brown',
name: 'Parkveien',
price: 1200,
rent: [40, 200, 600, 1800, 3200, 5000],
house_price: 1000,
mortage: 600
)
However I just can't wrap my head around how to do that, any help would be highly appreciated!
You can use each to generate the Property objects and collect them into an array.
properties = []
data.each { |property| properties << Property.new(property) }
You can then access individual properties from the array by using select as needed.
properties.select { |property| property.name == "Parkveien" }
Also, since the keys in your data hash are strings, you need to change your initialize to use strings instead of symbols:
def initialize(params = {})
#id = params["id"]
#group = params["group"]
#colour = params["colour"]
#name = params["name"]
#price = params["price"]
#rent = params["rent"]
#house_price = params["house_price"]
#mortage = params["mortage"]
end

create ruby object if all attributes are defined

I have a hash that is defining some variables as such:
event_details = {
:title => title(event),
:desc => desc(event),
:url => url(event),
:datetime => datetime(event),
:address => address,
:lat => coords["lat"],
:lng => coords["lng"]
}
and I want to create an "event" object
event = Event.new(event_details)
Now I only want to create the object event if and only if all the attributes are defined and not nil, for example, the lat and lng variables are sometimes nil, and I do not want to create that event object.
I was thinking about having a rescue clause in my event class, but I am not sure how I only create the instance of event, and validate the presence of each attribute. any tips would be greatly appreciated
I suggest:
keys = [:title, :desc, :url, :datetime, :address, :lat, :lng]
event = Event.new(event_details) unless
(keys-event_details.keys].any? || event_details.values.any?(&:nil?)
or
if (keys-event_details.keys].any? || event_details.values.any?(&:nil?)
<..action..>
else
event = Event.new(event_details)
end
You can do:
class Event
attr_accessor :title, :desc
RequiredKeys = [:title, :desc]
def initialize(hash = {})
missing_keys = RequiredKeys - hash.keys
raise "Required keys missing: #{missing_keys.join(', ')}" unless missing_keys.empty?
hash.each do |key, value|
public_send("#{key}=", value);
end
end
end

Alias attribute ruby ActiveResource::Base

class ChatMessage < ActiveResource::Base
alias_attribute :user_id, :userId
alias_attribute :chat_id, :chatId
alias_attribute :message_text, :MessageText
...
I Have the problem that what I return from an API has attribute names that I don't like, e.g. see camelCaps. I don't want to do this to every model in my application. Is there some method missing magic I could apply?
Cheers
Thomas
You can do a little of metaprogramming here:
module JavaAliasing
def initialize(hash)
super(Hash[hash.map do |k,v|
[k.to_s.gsub(/[a-z][A-Z]/) { |s| s.split('').join('_') }.downcase.to_sym, v]
end])
end
end
Let me illustrate this:
class Instantiator
def initialize(hash)
hash.each { |k,v| instance_variable_set "##{k}", v }
end
end
Instantiator.new(asdf: 2).instance_variable_get('#asdf') #=> 2
class MyARModel < Instantiator
include JavaAliasing
end
MyARModel.new(asdfQWER: 2).instance_variable_get("#asdf_qwer") #=> 2
Here, a real life example (rails 4.0):
> Player.send :include, JavaAliasing
> Player.new(name: 'pololo', username: 'asdf', 'teamId' => 23)
=> #<Player id: nil, name: "pololo", username: "asdf", email: nil, type: "Player", created_at: nil, updated_at: nil, provider: nil, uid: nil, team_id: 23, last_login: nil, last_activity: nil>

parse json to object ruby

I looked into different resources and still get confused on how to parse a json format to a custom object, for example
class Resident
attr_accessor :phone, :addr
def initialize(phone, addr)
#phone = phone
#addr = addr
end
end
and JSON file
{
"Resident": [
{
"phone": "12345",
"addr": "xxxxx"
}, {
"phone": "12345",
"addr": "xxxxx"
}, {
"phone": "12345",
"addr": "xxxxx"
}
]
}
what's the correct way to parse the json file into a array of 3 Resident object?
Today i was looking for something that converts json to an object, and this works like a charm:
person = JSON.parse(json_string, object_class: OpenStruct)
This way you could do person.education.school or person[0].education.school if the response is an array
I'm leaving it here because might be useful for someone
The following code is more simple:
require 'json'
data = JSON.parse(json_data)
residents = data['Resident'].map { |rd| Resident.new(rd['phone'], rd['addr']) }
If you're using ActiveModel::Serializers::JSON you can just call from_json(json) and your object will be mapped with those values.
class Person
include ActiveModel::Serializers::JSON
attr_accessor :name, :age, :awesome
def attributes=(hash)
hash.each do |key, value|
send("#{key}=", value)
end
end
def attributes
instance_values
end
end
json = {name: 'bob', age: 22, awesome: true}.to_json
person = Person.new
person.from_json(json) # => #<Person:0x007fec5e7a0088 #age=22, #awesome=true, #name="bob">
person.name # => "bob"
person.age # => 22
person.awesome # => true
require 'json'
class Resident
attr_accessor :phone, :addr
def initialize(phone, addr)
#phone = phone
#addr = addr
end
end
s = '{"Resident":[{"phone":"12345","addr":"xxxxx"},{"phone":"12345","addr":"xxxxx"},{"phone":"12345","addr":"xxxxx"}]}'
j = JSON.parse(s)
objects = j['Resident'].inject([]) { |o,d| o << Resident.new( d['phone'], d['addr'] ) }
p objects[0].phone
"12345"
We recently released a Ruby library static_struct that solves the issue. Check it out.

Resources