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
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)
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
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.
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.
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.