Different behavior of build method - ruby-on-rails

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.

Related

Bring the user name when query an item in Rails

I have an endpoint the returns all the comments on a blog. I would like to have the name of the user that made the comment. Is there a way to bring it all together when hitting that comments endpoint or do I have to make another query for each comment?
def comments
#comments = #blog.comments
render json: { comments: #comments }
end
This is what doing #blog.comments returns
[#<Comment id: 1, content: "This is a very good post", created_at: "2020-09-11 01:55:56", updated_at: "2020-09-11 01:55:56", blog_id: 3, user_id: 1>, #<Comment id: 2, content: "I agree with all of this", created_at: "2020-09-11 01:55:56", updated_at: "2020-09-11 01:55:56", blog_id: 3, user_id: 1>, #<Comment id: 3, content: "I don't agree with all of this", created_at: "2020-09-11 01:55:56", updated_at: "2020-09-11 01:55:56", blog_id: 3, user_id: 1>]>
Assuming you have a users table with a field users.name, and a belongs_to :user relationship on your model comment:
Just replace the line
#comments = #blog.comments
with
#comments = #blog.comments.select('comments.*, users.name').joins(:user)

How to get the current index during recursive call

I use the gem ancestry to create comments.
Now, I can list all comments.
But I want to push serial number to each comment.
For example, if there were 3 comments, the first comment is annotated by 1, the next annotated by 2,..
I have no idea how to do it?
show.html.haml
- if notice
%p.alert.alert-success= notice
= nested_comments(#comment.subtree.arrange(:order => :created_at))
helper
def nested_comments(comments)
if comments.respond_to? :map
comments.map do |comment, sub_comments|
render(comment) + content_tag(:div, nested_comments(sub_comments), :class => "nested_comments")
end.join.html_safe
end
end
each_with_index won't work on recursive
if I have 4 comments, I want to show 0,1,2,3 for each comment
But each_with_index can not make it because it's a recursive call.
comments.each_with_index.map do |(comment, sub_comments), i|
comments
=> {#<Comment id: 2, user_id: 1, ip: nil, content: "I'm id2 the second floor VIVOTEK Releases New Vers...", commentable_id: nil, commentable_type: nil, created_at: "2014-11-07 03:59:38", updated_at: "2014-11-07 06:56:12", ancestry: nil>=>
{#<Comment id: 4, user_id: 1, ip: nil, content: "lala", commentable_id: nil, commentable_type: nil, created_at: "2014-11-07 05:22:41", updated_at: "2014-11-07 05:22:41", ancestry: "2">=>
{#<Comment id: 5, user_id: 1, ip: nil, content: "son of 4", commentable_id: nil, commentable_type: nil, created_at: "2014-11-07 06:38:04", updated_at: "2014-11-07 06:38:04", ancestry: "2/4">=>
{},
#<Comment id: 6, user_id: 1, ip: nil, content: "dild last 252", commentable_id: nil, commentable_type: nil, created_at: "2014-11-07 06:52:15", updated_at: "2014-11-07 06:52:15", ancestry: "2/4">=>
{}}}}
You can use with_index with map
comments.map.with_index do |comment, sub_comments, index|
Every enumerable instance in ruby has a method each_with_index, providing an_enumerator. So in your case I would suggest to use:
- comments.map do |comment, sub_comments|
+ comments.each_with_index.map do |idx, comment, sub_comments|
Hope it helps.
I don't know of an elegant solution. But you could pass a counter into your nested_comments function, and deal with the problem manually -- which might well mean without map at all. Ugly, I know.
To take a simpler example, should you need one:
def nested_foo(result, string, index)
index += 1
result << "\n#{index}: #{string}"
if index >= 10
return result
else
return nested_foo(result, string, index)
end
end

active record all query from instance

Maybe somebody has an idea how I could solve following problem:
I have a Model and want to query it.
def MyModel < ActiveRecord::Base
# instance method
def all
my_models = MyModel.all
my_models.?? # order my_models ActiveRecord::Relation, that the instance which calls the .all instance method is at first position, and the rest is sorted somehow,..whatever.
end
end
How could I solve that?
EDIT:
Example:
MyModel has a name.
I have four instances of MyModel
MyModel.all => #<ActiveRecord::Relation [#<MyModel id: 1, name: "name1">, #<MyModel id: 2, name: "name2">, #<MyModel id: 3, name: "name3">, #<MyModel id: 4, name: "name4">]>
And I want now:
MyModel.find(1).all => #<ActiveRecord::Relation [#<MyModel id: 1, name: "name1">, #<MyModel id: 2, name: "name2">, #<MyModel id: 3, name: "name3">, #<MyModel id: 4, name: "name4">]>
MyModel.find(2).all => #<ActiveRecord::Relation [#<MyModel id: 2, name: "name2">, #<MyModel id: 1, name: "name1">, #<MyModel id: 3, name: "name3">, #<MyModel id: 4, name: "name4">]>
MyModel.find(3).all => #<ActiveRecord::Relation [#<MyModel id: 3, name: "name3">, #<MyModel id: 1, name: "name1">, #<MyModel id: 2, name: "name2">, #<MyModel id: 4, name: "name4">]>
I believe this accomplishes what you're asking.
def all
self.class.order("case when id = #{id} then 0 else id end")
end
A possible solution would be taking advantage of the fact that an ActiveRecord::Relation instance responds to many of the Array instance methods:
def all
ary = self.class.order(:id)
ary = ary.unshift(self)
ary.uniq
end
However this returns an instance of Array so you can't keep appending additional scopes. Up to you to decide whether that's acceptable in your case.

Compare two arrays using include?

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.

.first returning wrong object type

If you look at the four method calls below, Service.first returns a Service object, Salon.first returns a Salon object, etc. But TransactionItem.first returns a Service object. Why could this be?
ruby-1.8.7-p334 :001 > Service.first
=> #<Service id: 147, name: "Fub", salon_id: 2, created_at: "2011-08-10 18:00:07", updated_at: "2011-08-10 18:00:12", price: nil, active: true, archived: true>
ruby-1.8.7-p334 :002 > Salon.first
=> #<Salon id: 1, name: "The Cheeky Strut", created_at: nil, updated_at: nil, address_id: nil, email: nil>
ruby-1.8.7-p334 :003 > Product.first
=> #<Product id: 1, name: "Herbal Essences Shampoo", retail_price: #<BigDecimal:10305f1f0,'0.1E2',9(18)>, wholesale_price: nil, sku: "", salon_id: 2, created_at: "2011-07-08 01:35:48", updated_at: "2011-07-08 01:35:48", archived: false>
ruby-1.8.7-p334 :004 > TransactionItem.first
=> #<Service id: 63, created_at: "2011-08-30 20:05:57", updated_at: "2011-08-30 20:05:57", price: #<BigDecimal:10303eba8,'0.18E2',9(18)>>
ruby-1.8.7-p334 :005 >
This is what my app/models/transaction_item.rb looks like:
class TransactionItem < ActiveRecord::Base
belongs_to :transaction
belongs_to :stylist
end
I blew away the TransactionItem table via a migration, then created a brand new migration to re-create it. That seems to have fixed the problem.

Resources