Which rails model do I put a multiple table query in - ruby-on-rails

I have two tables called
Product (prodID: integer, prodName: string, userID: FK)
and
User(userID:integer,userName:string).
The user can have many products. I want to write a query that gets me all the products for userID=10. I don't however understand which model I should put this in- the user or the product model or does it not matter? Presumably the output of the model will be fed to the controller it relies on so I should put it in the model that relates to the view I want to show it in? Is this correct?

You can directly use association method, no need of writing model method for fetching user's products.
In user.rb:
has_many :products
In product.rb
belongs_to :user
and from controller
User.where('id = ?', params[:id]).first.try(:products)
So, above query will fetch products if user with given id is found.

In your controller:
#user = User.find(params[:id])
#products = User.of_products(params[:id])
If you don't want to use #user in your action then you can avoid calculating #user.
In user.rb:
has_many :products
def self.of_products(user_id)
User.includes(:products).where(id: user_id)
end
This will give you all products of #user

Related

Rails: How to use sum and multiple models to get the total order value

I would like to display #amount_spent and #amount_received by each user. I've already finished the first part with #amount_spent = Reservation.where(user_id: #user.id).sum(:total)
Now I would also like to display the #amount_received, since I use Stripe Connect.
I've tried something similar to:
#user = User.find(params[:id])
#products = #user.products
#amount_received = #products.reservations.sum(:total)
But when I try to use reservations with #products I get an #<Product::ActiveRecord_Associations_CollectionProxy:0x00007f50b8709fa8> error.
Is there a better way or another way, probably with scopes?
User Model:
has_many :products
has_many :reservations
Product Model:
has_many :reservations
Reservation Model:
belongs_to :user
belongs_to :product
You can't call #products.reservations to get all the reservations, because #products is a relationship (a collection) and you can call has_many methods on a single object. Instead you need to use SQL JOIN (implemented in ActiveRecord joins) to get all the relevant reservations and the to sum their total:
#products.joins(:reservations).sum('reservations.total')
See more:
https://guides.rubyonrails.org/active_record_querying.html#joins
https://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-joins
http://www.sql-join.com/sql-join-types
The reason you get the exception is because .reservations can only be called upon a single product. Not on a collection of products. The following should work:
#amount_received = Reservation.where(product_id: #products).sum(:total)
Normally you would specify the column to select, but this defaults to the id. Meaning .where(product_id: #products) produces the same result as .where(product_id: #products.select(:id)).

How to access a model from another model?

I have two models in ROR, one which is Note and another one which is Access. Each Access has a Note field and a user field. In my index action of the notes controller I want to filter notes owned by the user (done) as well as notes accessible to the user, which I named #accessible_notes.
The following code gives me the correct notes owned by the user, however I cannot get the notes accessible to the user.
Basically, I need to find all the Accesses in which the user is involved and then fetch the corresponding notes. How can I do that?
def index
#notes = Note.where(user: current_user)
#personal_access = Access.where("user_id = ?",current_user.id)
#accessible_notes = []
#personal_access.each do |accessible|
tnote = Note.find(accessible.note_id)
#accessible_notes += tnote if tnote
end
end
class User < ActiveRecord::Base
has_many :accessible_notes, :through => :accesses, :source => :notes
end
#accessible_notes = current_user.accessible_notes
How about
#personal_access.each do |accessible|
#accessible_notes << accessible.note
end
#accessible_notes.flatten!
There might be a faster way using Active Record queries.
And that faster way is in depa's answer.

rails how to reference current object in Model

This is a continuation of another question, but as it's different, I though I had better repost it as a new question:
Old Question
I'm adding quiz functionality to the twitter app from the Hartl tutorial and have these Models:
User is nearly the same as the tutorial:
class User < ActiveRecord::Base
has_many :followed_users, through: :relationships, source: :followed
has_many :takens, dependent: :destroy
has_many :questions, through: :takens
end
Taken is a table of Question ids to User ids:
class Taken < ActiveRecord::Base
belongs_to :user
belongs_to :question
end
nothing interesting in Question:
class Question < ActiveRecord::Base
attr_accessible :category, :correct, :option1, :option2, :option3, :qn
end
I want to be able to show followed_users and followers in order of the number of tests they have taken. In the console this can be had through:
User.find_by_id(1).question_ids.count
Then I can do something like:
User.find_by_id(1).followers.first.question_ids.count
in the console to get the count for a single follower.
I feel like I'm almost there.
How do I sort the followers and followed_users through their 'takens' count? (I was also looking at cache_count, which at first seemed promising, but might not be what I need...)
End Old Question
This is the answer from the other question: rails order through count on other table
and I went with a method like this in User.rb:
def users_sort_by_taken
User.find_by_sql("SELECT users.*
SELECT users.*
FROM users INNER JOIN takens
ON users.id = takens.user_id
GROUP BY users.id
ORDER BY count(takens.user_id) DESC")
end
which gets called in the users_controller.rb like so:
def following
require 'will_paginate/array'
#title = "Following"
#user = User.find(params[:id])
##users = #user.followed_users.paginate(page: params[:page])
#users = #user.users_sort_by_taken.paginate(page: params[:page])
render 'show_follow'
end
(For reference, the commented out line is from the Hartl tutorial)
all well and good, but now the current user is contained in the list of following (because of the above SQL). I need a way to eliminate the current user from the users_sort_by_taken.
I thought this might work:
WHERE (#{#current_user.id})
in the method,but I get this error:
Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id
I suppose I could pass it as an argument...
but don't I already have the user as #user in the following line?
#users = #user.users_sort_by_taken.paginate(page: params[:page])
Why can't I reference the current user from a method in the User.rb model?
Or another way, can I pass the current_user (or #user or user) to the SQL to exclude the current_user from the SQL results?
Any help appreciated.
Every object has its own set of instance variables - the fact that #user or #current_user is set in one object means nothing to another object.
The receiver of a method (in this case your user) is always available as self, so self.id gets you the user's id
The self is actually implicit - most of the time you won't need it and just writing id would result in the same thing (as long as you're in an instance method of that user)
To reference #user's id in the model, you can simply use:
self.id

Scoping a class method to current_user

I'm working on implementing a tagging system and I'm having problem querying for tagged objects with a scope.
For example, I would like to find all the user's items with a certain tag. With a class method I can currently find all the objects:
def self.tagged_with(name)
Tag.find_by_name(name).items
end
However, this has a problem. If I were to do something like: current_user.items.tagged_with(name) won't this existing method return ALL the items and not just items owned by the current_user? I suppose this is a simply querying issue but I can't figure out how to change a class method into something called on a collection. I have tried going the opposite way, to get a the collection through the tags, something like... tag.items.where(:user_id => current_user.id) but in this case, it's a many-to-many relationship and I haven't been able to get on thumb on this either.
What's the proper way to restrict a query like this?
Create an association on your User class that points to your Tag class.
class User < ActiveRecord::Base
has_many :tags
end
Then you can do:
current_user.tags.where(...)
If you don't already have an association in place, you'll need to create a migration to have the tags table reference your users table with a foreign key.
I think this will help you:
class Account < ActiveRecord::Base
has_many :people do
def find_or_create_by_name(name)
first_name, last_name = name.split(" ", 2)
find_or_create_by_first_name_and_last_name(first_name, last_name)
end
end
end
person = Account.first.people.find_or_create_by_name("David Heinemeier Hansson")
person.first_name # => "David"
person.last_name # => "Heinemeier Hansson"
So, basically you can define your method tagged_with directly into the association!
This example is took from the documentations ActiveRecord::Associations

nested form & habtm

I am trying to save to a join table in a habtm relationship, but I am having problems.
From my view, I pass in a group id with:
<%= link_to "Create New User", new_user_url(:group => 1) %>
# User model (user.rb)
class User < ActiveRecord::Base
has_and_belongs_to_many :user_groups
accepts_nested_attributes_for :user_groups
end
# UserGroups model (user_groups.rb)
class UserGroup < ActiveRecord::Base
has_and_belongs_to_many :users
end
# users_controller.rb
def new
#user = User.new(:user_group_ids => params[:group])
end
in the new user view, i have access to the User.user_groups object, however when i submit the form, not only does it not save into my join table (user_groups_users), but the object is no longer there. all the other objects & attributes of my User object are persistent except for the user group.
i just started learning rails, so maybe i am missing something conceptually here, but i have been really struggling with this.
Instead of using accepts_nested_attributes_for, have you considered just adding the user to the group in your controller? That way you don't need to pass user_group_id back and forth.
In users_controller.rb:
def create
#user = User.new params[:user]
#user.user_groups << UserGroup.find(group_id_you_wanted)
end
This way you'll also stop people from doctoring the form and adding themselves to whichever group they wanted.
What does your create method look like in users_controller.rb?
If you're using the fields_for construct in your view, for example:
<% user_form.fields_for :user_groups do |user_groups_form| %>
You should be able to just pass the params[:user] (or whatever it is) to User.new() and it will handle the nested attributes.
Expanding on #jimworm 's answer:
groups_hash = params[:user].delete(:groups_attributes)
group_ids = groups_hash.values.select{|h|h["_destroy"]=="false"}.collect{|h|h["group_id"]}
That way, you've yanked the hash out of the params hash and collected the ids only. Now you can save the user separately, like:
#user.update_attributes(params[:user])
and add/remove his group ids separately in one line:
# The next line will add or remove items associated with those IDs as needed
# (part of the habtm parcel)
#user.group_ids = group_ids

Resources