How to access a different parent of a model's child - ruby-on-rails

I am adding a feature to an old app that was not made by me, this along with being relatively new to RoR is leading to some confusion for me.
I have models called reponse, activity_point, and report
response has two parents, it belongs_to activity_point and report.
I am trying to access activity_points for a do block like so:
report.responses.activity_points.activity.each do |a|
Obviously that isn't working. I am getting the error message:
undefined method `activity_points' for []:ActiveRecord::Relation
Thanks to anyone who can help me with this little problem.

Or you can add something like this to your Report model
has_many :responses
has_many :activity_points, :through => :responses
has_many :activities, :through => :activity_points
then you can do this
report.activities.each do |a|
Another way to do this kind of thing, add a method to Report and joins from the other side (to get activity objects)
def activities
Activity.joins(:activity_points => :responses).where('responses.report_id = ?', id)
end
The point of doing all this, you don't want to create Ruby objects if you don't need to. Nested loops are also a potential problem with unique items and sorting.

Each response have several activity_points so you should iterate through responses. Also each activity_point has several activities, so:
report.responses.each do |r|
r.activity_points.each do |ap|
ap.activity.each do |a|
# Do your thing
end
end
end

First, when you write report.responses, this will return an ActiveRecord array. Since activity_points is an undefined method for arrays, you can't call it. So to call this method there is two conditions:
You have to tell your app which element of the array will call the method. For instance, report.responses.first.activity_points or report.responses.second.activity_points ...
Response model has to have a has_many: activity_points to call this method.

You could still also use a loop, but that will take multiple DB calls. Therefore, my solution involves direct database call for efficiency.
Activity.includes(activity_point: {responses: :report}).where(reports: {id: report.id}).each do |a|
#...
#...
end

Related

Cycle through object has_many association and remove certain one

Im creating an object (recipe_change) that has many ingredient_changes. However I want to run my own validation of sort and prevent the creation of ingredient_changes if they dont pass. I started with a validation in the ingredient_changes but with that if I added an error the valid? would prevent the change from being submitted if one of the many ingredient_changes wasnt valid...which I dont want. So then I tried the following in my create method in the recipe_change controller and this almost did the job except once it deleted one it would break the loop and not do the rest:
#recipe_change.ingredient_changes.each do |change|
if ...long conditional statement...
#recipe_change.ingredient_changes.delete(change)
end
end
Is there a function like the array .reject! that works for associations like this. At this point in the code they are not saved to the database and Im trying to not save them to the database if they dont meet my condition.
You can use reject_if to filter out invalid ingredient_changes in recipe_change model like this:
class RecipeChange < ActiveRecord::Base
has_many :ingredient_changes, reject_if: :invalid_ingredient_change?
def invalid_ingredient_change?(attributes)
end
end
See: http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html

Rails: conditions on *last* associated object for a collection of parent objects

I've got the following setup:
class User
has_many :posts
end
class Post
attr_accessible :post_type
end
What I would like to do, is to find all Users, whose last post is of a certain type.
Put differently, I'd like to do the equivalent of:
User.all.select do |u|
u.posts.last.post_type == x
end
Where "x" is the post_type I'm looking for. Of course, this code snippet is ridiculously slow, so I'm looking for an ActiveRecord query that is much more performant.
Hope somebody can help me with this. Thanks in advance!

Referring to instance in has_many (Rails)

I have a Game model which has_many :texts. The problem is that I have to order the texts differently depending on which game they belong to (yes, ugly, but it's legacy data). I created a Text.in_game_order_query(game) method, which returns the appropriate ordering.
My favourite solution would have been to place a default scope in the Text model, but that would require knowing which game they're part of. I also don't want to create separate classes for the texts for each game - there are many games, with more coming up, and all the newer ones will use the same ordering. So I had another idea: ordering texts in the has_many, when I do know which game they're part of:
has_many :texts, :order => Text.in_game_order_query(self)
However, self is the class here, so that doesn't work.
Is there really no other solution except calling #game.texts.in_game_order(#game) every single time??
I had a very similar problem recently and I was convinced that it wasn't possible in Rails but that I learned something very interesting.
You can declare a parameter for a scope and then not pass it in and it will pass in the parent object by default!
So, you can just do:
class Game < ActiveRecord
has_many :texts, -> (game) { Text.in_game_order_query(game) }
Believe or not, you don't have to pass in the game. Rails will do it magically for you. You can simply do:
game.texts
There is one caveat, though. This will not work presently in Rails if you have preloading enabled. If you do, you may get this warning:
DEPRECATION WARNING: The association scope 'texts' is instance dependent (the scope block takes an argument). Preloading happens before the individual instances are created. This means that there is no instance being passed to the association scope. This will most likely result in broken or incorrect behavior. Joining, Preloading and eager loading of these associations is deprecated and will be removed in the future.
Following up using PradeepKumar's idea, I found the following solution to work
Assuming a class Block which has an attribute block_type, and a container class (say Page), you could have something like this:
class Page
...
has_many :blocks do
def ordered_by_type
# self is the array of blocks
self.sort_by(&:block_type)
end
end
...
end
Then when you call
page.blocks.ordered_by_type
you get what you want - defined by a Proc.
Obviously, the Proc could be much more complex and is not working in the SQL call but after there result set has been compiled.
UPDATE:
I re-read this post and my answer after a bunch of time, and I wonder if you could do something as simple as another method which you basically suggested yourself in the post.
What if you added a method to Game called ordered_texts
def ordered_texts
texts.in_game_order(self)
end
Does that solve the issue? Or does this method need to be chainable with other Game relation methods?
Would an Association extension be a possibility?
It seems that you could make this work:
module Legacy
def legacy_game_order
order(proxy_association.owner.custom_texts_order)
end
end
class Game << ActiveRecord::Base
includes Legacy
has_many :texts, :extend => Legacy
def custom_texts_order
# your custom query logic goes here
end
end
That way, given a game instance, you should be able to access instance's custom query without having to pass in self:
g = Game.find(123)
g.texts.legacy_game_order
Here is a way where you can do it,
has_many :texts, :order => lambda { Text.in_game_order_query(self) }
This is another way which I usually wont recommend(but will work),
has_many :texts do
def game_order(game)
find(:all, :order => Text.in_game_order_query(game))
end
end
and you can call them by,
game.texts.game_order(game)
Im not sure what your order/query looks like in the in_game_order_query class method but i believe you can do this
has_many :texts, :finder_sql => proc{Text.in_game_order_query(self)}
Just letting you know that I have never used this before but I would appreciate it if you let me know if this works for you or not.
Check out http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many for more documentation on :finder_sql
I think if you want runtime information processed you should get this done with:
has_many :texts, :order => proc{ {Text.in_game_order_query(self)} }

Active Record Association with Memcached

Hi for rails model association, i know i can do this:
For example a model class Page.
class Page < ActiveRecord::Base
has_many :parts
end
I can do this:
Page.first.parts.find_by_name('body')
Page.first.parts.class actually returns Array. How can it activate methods for Part model? I found the similar post on How do rails association methods work?
My question is that when i try to use memcache to cache the response for parts methods. Then when i call Page.first.parts.find_by_name('body'), it tells me that the Array doesn't have method find_by_name. How do i solve this problem? I need to have the cache as this is one heavily used methods.
class Page
def parts_with_cache
Rails.cache.fetch("parts_for_page_#{id}", {:expires_in => 1.minutes}) do
parts_without_cache
end
end
alias_method_chain :parts, :cache
end
Since you are getting back an array of Parts objects associated to the Page object unfiltered by part name, just do an Array select method on the result set.
body_parts = Page.first.parts.select{ |part| part.name == 'body' }

Rails autosave association in controller action

I have the following one to many associations. Document has many Sections and Section has many Items.
class Document < ActiveRecord::Base
has_many :document_sections, :dependent => :destroy, :autosave => true
has_many :document_items, :through => :document_sections
end
class DocumentSection < ActiveRecord::Base
belongs_to :document
has_many :document_items, :dependent => :destroy, :autosave => true
end
class DocumentItem < ActiveRecord::Base
belongs_to :document_section
end
Here is the params hash:
-
Parameters: {"commit"=>"Submit Document", "authenticity_token"=>"4nx2B0pJkvavDmkEQ305ABHy+h5R4bZTrmHUv1setnc=", "id"=>"10184", "document"=>{"section"=>{"10254"=>{"seqnum"=>"3", "item"=>{"10259"=>{"comments"=>"tada"}}}}, "comment"=>"blah"}}
I have the following update method...
# PUT /documents/1
# PUT /documents/1.xml
def update
#document = Document.find(params[:id])
# This is header comment
#document.comment = params[:document][:comment]
params[:document][:section].each do |k,v|
document_section = #document.document_sections.find_by_id(k)
if document_section
v[:item].each do |key, value|
document_item = document_section.feedback_items.find_by_id(key)
if document_item
# This is item comments
document_item.comments = value[:comments]
end
end
end
end
#document.save
end
When I save the document it only updates the document header comments. It does not save the document_item comments. Shouldn't the autosave option also update the associations.
In the log only the following DML is registered:
UPDATE documents SET updated_at = TO_DATE('2010-03-09 08:35:59','YYYY-MM-DD HH24:MI:SS'), comment = 'blah' WHERE id = 10184
How do I save the associations by saving the document.
I think I see what the problem is. I'm pretty sure that you cannot do the following:
# Triggers a database call
document_section = #document.document_sections.find_by_id(k)
And expect ActiveRecord to keep the association for autosaves. Instead, you should save the loaded records individually. Which of course would not be atomic.
I believe for autosave to work like you are thinking, you want to do something like this:
# untested...
#document.document_sections.collect { |s| s.id == k }.foo = "bar"
Notice that here I'm actually modifying a fake param foo in the array, instead of calling find_by_id, which will re-query the database and return a new object.
A third option you have is that you could of course, do what you had originally planned, but handle all the transactions yourself, or use nested transactions, etc, to get the atmoic saves. This would be necessary if your data was too large for array manipulation to work since autosave by it's natures triggers a load of all associated data into memory.
It all depends on your application.
Some clarifications on the underlying problem:
If you run the find_by_id method, you are asking ActiveRecord to return to you a new set of objects that match that query. The fact that you executed that method from an instance (document_sections) is really just another way of saying:
DocumentSection.find_by_id(k)
Calling it from an object instance I think is just some syntactic niceness that rails is adding on the top of things, but in my mind it doesn't make a lot of sense; I think it could be handy in some application, I'm not sure.
On the other side, collect is a Ruby Array method that offers a way to "slice" an array using a block. Basically a fancy foreach loop. :) By interacting with the document_sections array directly, you are changing the same objects already loaded into the containing object (#document), which will then be committed when you save with the special autosave flag set.
HTH! Glad you are back and running. :)

Resources