Merge Rails objects, only keep present values - ruby-on-rails

I have an array of objects that I want to merge into one:
[
#<User firstname: 'John', middlename: '', lastname: nil>
#<User firstname: '', middlename: '', lastname: 'Doe'>
#<User firstname: nil, middlename: 'W.', lastname: nil>
]
This should become:
#<User firstname: 'John', middlename: 'W.', lastname: 'Doe'>
Is there an easy way for this or do I have to loop through all objects, look at the .attributes and build a new one?
Update: My current state of code
master = nil
my_array.each do |user|
if !master
master = user
else
user.attributes.each do |k, v|
if v.present? && !master.send(k).present?
master.send(:"#{k}=", v)
end
end
end
end
Well, it works, but the code doesn't look very clean...

Related

Move one object in collection of objects in rails

Here is the scenario, I have these objects. Let's assume that this is a User:
The object came from:
#user = User.all
User Object
[<#User id: 1, firstname: "John", lastname: "Pond">,<#User id: 2, firstname: "Paul", lastname: "Rich">,<#User id: 3, firstname: "Jasmine", lastname: "Ong">]
How can I move one object up, for example I want to move User.id == 2? The result I want is shown below.
[<#User id: 2, firstname: "Paul", lastname: "Rich">,<#User id: 1, firstname: "John", lastname: "Pond">,<#User id: 3, firstname: "Jasmine", lastname: "Ong">]
I already got the answer. Here is what I made to made my question above worked.
#users = User.all
user_ids = User.pluck(:id)
user_ids.delete(2)
new_user_ids = [2]
user_ids.each do |id|
new_user_ids << id
end
#users.sort_by { |user| new_user_ids.index(user.id) }
And this made perfect!
We can also do it in a way like this:
Add a new method to Array. lib/rails_extensions.rb
class Array
def swap!(a, b = a - 1)
self[a], self[b] = self[b], self[a]
self
end
end
Then add this in config/environment.rb
require 'rails_extensions'
So we can use the method swap! for arrays and it will swap the object with the one before it. We can do something like this:
#users = User.all #[<#User id: 1>, <#User id: 2>]
user_id = #users.rindex {|user| user.id == 2}
#users = #users.swap!(user_id) #[<#User id: 2>, <#User id: 1>]
is this too ugly?
hash = [{ id: 1}, {id: 2}, {id: 3}]
hash.unshift(hash.delete(hash.select {|h| h[:id] == 2 }.first))
=> [{:id=>2}, {:id=>1}, {:id=>3}]

Different behavior of build method

I was trying to implement a first_or_build method and I encounter a problem when saving my parent : the children were missing.
Everything is working fine when I call my method on the relation like parent.childs.first_or_build(name: 'Foo'); parent.save! whereas nothing happen when I do parent.childs.where(name: 'Foo').first_or_build; parent.save!.
The main objective was to propose a similar behavior than .first_or_create applied to the result of a query for example. (Don't tell me about .first_or_initialize !)
Any idea?
Examples :
# this is not working :(
2.times { |i| parent.childs.where(name: "child #{i}").build { |c| c.age = 42 } } ; parent.childs
=> #<ActiveRecord::Associations::CollectionProxy []>
# while this is
2.times { |i| parent.childs.build { |c| c.name = "#{child #{i}"; c.age = 42 } } ; parent.childs
=> #<ActiveRecord::Associations::CollectionProxy [#<Child name: "child 0", age: 42>, #<Child name: "child 1", age: 42>]>
Sorry, I don't quit understand the part about first_or_build method, so I will just talk about the examples there.
First of all, we know that parent.childs.where(name: "child #{i}") and parent.childs are in different class
parent.children.where(name: "child").class
#=> Child::ActiveRecord_AssociationRelation
parent.children.class
#=> Child::ActiveRecord_Associations_CollectionProxy
so it's clear why their :build method are different, the doc are here
ActiveRecord_Associations_CollectionProxy
ActiveRecord_AssociationRelation
I will try to express my view here.
When you use ActiveRecord_AssociationRelation to build a new child, it will initialize a new Child object, and set its parent_id, but it is just an Child object. In this time, when you execute parent.children, the result is empty.
parent.children.where(name: "child1").build({age: 1})
#=> <Child id: nil, name: "child1", age: 1, parent_id: 1, created_at: nil, updated_at: nil>
parent.children
#=> <ActiveRecord::Associations::CollectionProxy []>
parent.save #=> true
parent.children.reload
#=> <ActiveRecord::Associations::CollectionProxy []>
But when you use ActiveRecord_Associations_CollectionProxy, it will initialize a new Child object, and it will also attach itself to parent, so then when you execute parent.children, the result is not empty.
parent.children.build({name: "child2", age: 2})
#=> <Child id: nil, name: "child2", age: 2, parent_id: 1, created_at: nil, updated_at: nil
parent.children
#=> <ActiveRecord::Associations::CollectionProxy [#<Child id: nil, name: "child2", age: 2, parent_id: 1, created_at: nil, updated_at: nil>]>
parent.save #=> true
parent.children.reload
#=> <ActiveRecord::Associations::CollectionProxy [#<Child id: 3, name: "child2", age: 2, parent_id: 1, created_at: "2015-05-28 17:02:39", updated_at: "2015-05-28 17:02:39">]>
In the second way, parent know it has children, so when it save, it will save its children.I think this is it.

callback before_save seems not working

I need execute the method name before the record is saved in the database, but it seems not working
class Guest < ActiveRecord::Base
before_save :name
has_and_belongs_to_many :checkins
validates_presence_of :FirstName
validates_presence_of :LastName
validates_format_of :FirstName, with: /^[A-Za-z\s]+$/
validates_format_of :LastName, with: /^[A-Za-z\s]+$/
def name
self.FirstName.titleize + " " + self.LastName.titleize
end
end
console
=> Guest(id: integer, FirstName: string, LastName: string, age: integer, sex: string, photo: string, address: text, mobile: integer, email: string, birthdate: date, created_at: datetime, updated_at: datetime)
1.9.3-p547 :008 > f=Guest.new
=> #<Guest id: nil, FirstName: nil, LastName: nil, age: nil, sex: nil, photo: nil, address: nil, mobile: nil, email: nil, birthdate: nil, created_at: nil, updated_at: nil>
1.9.3-p547 :009 > f.FirstName=" fernando "
=> " fernando "
1.9.3-p547 :010 > f.LastName=" suarez"
=> " suarez"
1.9.3-p547 :011 > f.save
=> true
1.9.3-p547 :012 > Guest.last
=> #<Guest id: 9, FirstName: " fernando ", LastName: " suarez", age: nil, sex: nil, photo: nil, address: nil, mobile: nil, email: nil, birthdate: nil, created_at: "2015-04-26 00:16:38", updated_at: "2015-04-26 00:16:38">
You need to change
self.FirstName.titleize + " " + self.LastName.titleize
to
self.FirstName = self.FirstName.titleize
self.LastName = self.LastName.titleize
Explanation: You didn't actually change anything, but concatenate the values.
But probably you want to use the name method for your name-representation. So I'd suggest a new method for the validation.
It would look like this:
def titlelize_names
self.FirstName = self.FirstName.titleize
self.LastName = self.LastName.titleize
end
And the before_save must call :titlelize_names, of course.

RSpec Matchers When Working With ActiveRecord::Relation

So here's the method I want to test:
def self.by_letter(letter)
where("lastname LIKE ?", "#{letter}%").order(:lastname)
end
Quick question here, what exactly does the percent sign after #{letter} do? Something to do with formatting?
Here's part of the spec that tests that method:
context 'method "by_letter"' do
it 'returns and ordered list by letter' do
theon = Contact.create!(
firstname: "Theon",
lastname: "Greyjoy",
email: "tgreyjoy#ironprice.com"
)
rob = Contact.create!(
firstname: "Rob",
lastname: "Stark",
email: "rstark#winterfell.com"
)
tyrion = Contact.create!(
firstname: "Tyrion",
lastname: "Lannister",
email: "tlannister#kingslanding.com"
)
result = Contact.by_letter("S")
expect(result).to include("Snow")
end
end
And here's the logs I get for an output after running said test (oh, bare in mind, earlier in the spec I created a "Jon Snow", and he should pop up before "Stark" alphabetically):
Failures:
1) Contact method "by_letter" returns and ordered list by letter
Failure/Error: expect(result).to include("Snow")
expected #<ActiveRecord::Relation [#<Contact id: 1, firstname: "Jon", lastname: "Snow", email: "lordcommander#nightswatch.com", created_at: "2014-11-14 17:17:55", updated_at: "2014-11-14 17:17:55">, #<Contact id: 3, firstname: "Rob", lastname: "Stark", email: "rstark#winterfell.com", created_at: "2014-11-14 17:17:56", updated_at: "2014-11-14 17:17:56">]> to include "Snow"
Diff:
## -1,2 +1,3 ##
-["Snow"]
+[#<Contact id: 1, firstname: "Jon", lastname: "Snow", email: "lordcommander#nightswatch.com", created_at: "2014-11-14 17:17:55", updated_at: "2014-11-14 17:17:55">,
+ #<Contact id: 3, firstname: "Rob", lastname: "Stark", email: "rstark#winterfell.com", created_at: "2014-11-14 17:17:56", updated_at: "2014-11-14 17:17:56">]
What am I missing? Shouldn't my test pass because I return a collection that includes a string I specified? Is there some complication because it's not a regular array but some sort of proxy array? What do I need to do to get my test to pass?
Your result is an ActiveRecord::Relation object. So you should do as below :-
expect(result).to include(rob)
rob has the last name as "Stark", thus Contact.by_letter("S") will include rob in the filtered list.
Try expect(result.first).to include("Snow")
You can also say (preferably):
expect(result.first.lastname).to eq("Snow")

Association clears when reloading a record in test

I am only familiar with Rspec, but now I need to fix a library which uses Test::Unit.
I find out that when I reload the record, the association becomes nil
For example:
test "accepts nested attributes from subscription to user ()" do
#user = User.create(name:'Ash')
#subscription = #user.create_subscription()
puts "user: " << #user.inspect
puts "subscription: " << #user.subscription.inspect
#subscription.update_attributes({ :user_attributes => { :notification_comment => true } })
#user.reload
puts "user: " << #user.inspect
puts "subscription: " << #user.subscription.inspect
assert #user.subscription, "should have an subscription"
end
Outputs:
user: #<User id: 815823837, account_id: nil, name: "Ash", email: nil, settings: {}, created_at: "2013-02-07 04:28:51", updated_at: "2013-02-07 04:28:51">
subscription: #<Subscription id: 1, user_id: 815823837, created_at: "2013-02-07 04:28:51", updated_at: "2013-02-07 04:28:51">
user: #<User id: 815823837, account_id: nil, name: "Ash", email: nil, settings: {}, created_at: "2013-02-07 04:28:51", updated_at: "2013-02-07 04:28:51">
subscription: nil
Why does this happen? I did check and I can confirm that I can Subscription.find(#subscription.id) so it is saved in the database.

Resources