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}]
Related
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...
I have a PORO TutorProfileHandler that has a function json that returns a hash.
class TutorProfileHandler
def initialize(opts)
#profile = opts[:tutor_profile]
end
def json
tutor = #profile.tutor
return {
id: tutor.id,
first_name: tutor.first_name,
last_name: tutor.last_name.first + '.',
school: #profile.school,
avatar: #profile.avatar.url,
bio: #profile.bio,
academic_level: #profile.academic_level,
headline: #profile.headline,
major: #profile.major,
rate: #profile.rate,
rating: #profile.rating,
courses: JSON.parse(#profile.courses),
video_url: #profile.video_url
}
end
end
In my index_tutor_profiles.json.jbuilder, I would like to generate
{
tutor_profile: [{id: 1, ...}, {id: 2, ...}, ...],
tutor_sum: 20
}
However when I do this
json.tutor_profiles (#tutor_profiles) do |profile|
TutorProfileHandler.new({tutor_profile: profile}).json
end
json.tutor_sum #tutor_sum
It gives me an empty array for tutor_profiles.
However if I move everything from TutorProfileHandler.json to the jbuilder file, it works. How do I explicitly include the hash returned by TutorProfileHandler.json in the jbuilder array?
Note: This returns an array, but it creates a new key-value pair array:
json.tutor_profiles json.array(#tutor_profiles) do |profile|
TutorProfileHandler.new({tutor_profile: profile}).json
end
Result:
{
array: [{id: 1, ...}, {id: 2, ...}, ...],
tutor_profile: [],
tutor_sum: 20
}
There is a ugly approach:
json.tutor_profiles #tutor_profiles do |profile|
tmp_json = TutorProfileHandler.new({tutor_profile: profile}).json
json.(tmp_json, *(tmp_json.keys))
end
I think the best practise is directly nesting inside model. You can get more information from the its github page.
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.
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")
I have two arrays:
#all_genres = [#<Genre id: 1, name: "Action", created_at: "2013-03-01 07:44:51", updated_at: "2013-03-01 07:44:51">,
#<Genre id: 2, name: "Adventure", created_at: "2013-03-01 07:44:51", updated_at: "2013-03-01 07:44:51">,
#<Genre id: 3, name: "Animation", created_at: "2013-03-01 07:44:51", updated_at: "2013-03-01 07:44:51">]
#genres = ["Action", "Animation"]
I am trying to find the Genre.id from #genres compared to the #all_genres table. For example my result should be:
#genre_ids = [1, 3]
I have tried this:
#all_genres.each do |g|
if g.name.include?((#genres.each {|g| g}).to_s)
#genre_ids << g.id
end
end
I tried this in my console and it seemed to work but when I put it into my app it returns:
#genre_ids = []
A more rail-sy version:
#genre_ids = Genre.where(name: #genres).pluck(:id)
Or you could try this one-liner:
#genre_ids = #all_genres.select{|g| #genres.include? g.name }.map(&:id)
I'm assuming that you're populating your #genres array with a call to Genre.all.
You could simply do something like this:
Genre.where("name IN (?)", %w[name action]).collect { |x| x.id }
If you want to retrieve the ids for the Genres with those names.