Update activerecord relation given a hash of multiple entries - activerecord

I'm quite new to Rails, so be gentle :)
I have the following models set-up:
class User
has_many :it_certificates, :class_name => 'UserCertificate'
class UserCertificate
belongs_to :skill
Given the following input (in JSON)
{
"certificates":[
{ // update
"id":1,
"name":"Agile Web Dev 2",
"entity":"Agile Masters!",
"non_it":false,
"date_items":{
"month":10,
"year":2012
},
"skill": {
"id":57
}
},
{ // create
"name":"Agile Web Dev 1",
"entity":"Agile Masters!",
"non_it":false,
"date_items":{
"month":10,
"year":2011
},
"skill": {
"id":58
}
}
]
}
How's the easiest way to update the information for the relation it_certificates?
I've been looking to update_all but it doesn't match my needs (it only updates given fields with the same value).
So I've been struggling around with the approach of iterating over each of these records and then update them one-by-one.
I mean struggling because it looks to me there are lots of things I have to care of when the idea of Rails is the opposite.
Thanks in advance!

So, here's my solution for now:
def self.update_from_hash(data, user_id)
self.transaction do
data.each do |certificate|
if certificate[:id] == nil
# create
if !self.create(
:name => certificate[:name],
:entity => certificate[:entity],
:user_id => user_id,
:non_it => certificate[:non_it],
:skill_id => certificate[:skill][:id],
:date => self.build_date_from_items(certificate[:date_items][:month], certificate[:date_items][:year])
)
raise ActiveRecord::Rollback
end
else
# update
if !self.update(certificate[:id], {
:name => certificate[:name],
:entity => certificate[:entity],
:non_it => certificate[:non_it],
:skill_id => certificate[:skill][:id],
:date => self.build_date_from_items(certificate[:date_items][:month], certificate[:date_items][:year])
})
raise ActiveRecord::Rollback
end
end
end
end
return true
end
It works, but I'm still expecting a more elegant solution :)

Related

How to organize hashes by property

Here is the hashes that will be processed:
{
"flatiron school bk" => {
:location => "NYC"
},
"flatiron school" => {
:location => "NYC"
},
"dev boot camp" => {
:location => "SF"
},
"dev boot camp chicago" => {
:location => "Chicago"
},
"general assembly" => {
:location => "NYC"
},
"Hack Reactor" => {
:location => "SF"
}
}
I need to organize these hashes by location, like this:
{ "NYC"=>["flatiron school bk", "flatiron school", "general assembly"],
"SF"=>["dev boot camp", "Hack Reactor"],
"Chicago"=>["dev boot camp chicago"]}
}
You can use each_with_object to combine in into new hash:
hash.each_with_object({}) do |(name, data), res|
(res[data[:location]] ||= []) << name
end
Explanation:
each_with_object
Iterates the given block for each element with an arbitrary object given, and returns the initially given object.
In this case name and data is key and value of each element in given hash.
In (res[data[:location]] ||= []) << name you get location, create array in result hash for given location (if it doesn't exist), then put key of input hash to it.

Creating a nested hash from ActiveRecord object in ruby

require "active_record"
ActiveRecord::Base.establish_connection(
:adapter => 'mysql2',
:database => '<db_name>',
:username => '<username>',
:password => '<password>',
:host => 'localhost')
ActiveRecord::Base.pluralize_table_names = false
class Location < ActiveRecord::Base
has_many :location_channels
has_many :channels, :through => :location_channels
end
class Channel < ActiveRecord::Base
has_many :location_channels
has_many :locations, :through => :location_channels
end
class LocationChannel < ActiveRecord::Base
belongs_to :location
belongs_to :channel
end
locations = Location.all
hash = {} # hash initialization
locations.each do |location|
hash["location"] = location[:name]
puts "#{location[:name]} has #{location.channels.size} channels:"
location.channels.each do |channel|
puts "--> #{channel[:name]}"
end
puts
end
puts hash
Final goal is to create one JSON file.
So I decided it'll be easier to create JSON from Hash object.
As in the code above, I'm able to access nested documents via JOIN table called LocationChannel class and I'm trying to figure out how to create a Hash object what will look like:
{
["location" => "A", "channels" => {"1","2","3"}],
["location" => "B", "channels" => {"1","2"}],
["location" => "C", "channels" => {"4","5","6"}]
}
where "A", "B" and "C" - locations name and "1", "2", etc. - represents channels name.
And the current code prints out only the last record like:
{"location"=>"A"}
Please correct me how should Hash look like if the sample above is wrong.
UPDATE 1
Thanks to #jonsnow for point out the hash format.
Hash format should be:
{ :locations =>
[
{ name: a, channels: [1,2,3]},
{ name: b, channels: [1,2]},
{ name: c, channels: [4,5,6]}
]
}
Solution for your updated hash,
hash = { locations: [] } # hash initialization
locations.each do |location|
hash[:locations] << { name: location.name,
channels: location.channels.pluck(:name) }
end

Ruby Array of Hashes of Hashes Map

I have this hash,
[{ "player" => { "name" => "Kelvin" , "id" => 1 } , "player" => { "name" => "David",
"id" => 2 }]
I checked if each event contains the keys [id,name] with the following line in my Rspec,
json_response.map{|player| ["name","id"].all? {|attribute| player["player"].key?
(attribute)}}.should_not include(false)
which works perfectly. How can I simplify this and make it more efficient?
How about :
json_response.each do |event|
event['player'].should have_key('name')
event['player'].should have_key('id')
end
Much clearer IMHO
Edit : if you need to check a lot of columns :
json_response.each do |event|
['name', 'id', 'foo', 'bar', 'baz'].each do |column|
event['player'].should have_key(column)
end
end
According to the documentation you should be able to do this:
json_response.each do |event|
event['player'].should include('name', 'id')
end

using rescue in variable assignment

I have this
User.new ( gender: auth.extra.raw_info.gender.capitalize, ...)
auth is a hash that looks like this
auth => {:extra => { :raw_info => { :gender => ... , .. }, ..} ..}
sometimes, for whatever reason, the gender doesn't exist and I want to have a default value for it when I create a new user
If I try
gender: auth.extra.raw_info.gender.try(:capitalize) || "Male"
but the gender itself doesn't exist, and I can't try on gender
using gender: auth.extra.raw_info.gender.capitalize rescue "Male"
also doesn't work since it says I can't use capitalize on nil (which is gender)
Is there a way to do this without using a variable for this (since it will become messier)
I think the standard way to do this is to use reverse_merge:
auth.reverse_merge! {:extra => { :raw_info => { :gender => "Male" } } }

How to validate a complete form with RSpec and Capybara?

I'm writing a request test with RSpec and Capybara. I have a hash that maps form field names to expected values.
How can I check easily that each form field has the expected value?
So far, I'm doing this, but it's complex and unmaintainable. I'm also considering only two kind of input controls in this case (select boxes and the rest):
expected_data = {
"address" => "Fake st 123",
"city" => "Somewhere",
"email" => "whoknows#example.com",
"gender" => "Male",
"state" => "FL",
}
select_boxes = ["gender", "state"]
# check for the select boxes
expected_data.select {|k,v| select_boxes.include?(k)}.each do |name, expected_value|
page.has_select?(name, :selected_value => expected_value).should == true
end
# check for the input fields
expected_data.reject {|k,v| select_boxes.include?(k)}.values.each do |expected_value|
page.should have_css("input[value=\"#{expected_value}\"]")
end
Is there a gem or something to do this in one line?
I find the following far more maintainable:
describe "form" do
subject {page}
before { visit "/path/to/form" }
it { should have_field("address", :with => "Fake st 123") }
it { should have_select("gender", :selected => "Male") }
# And so on ...
end

Resources