rails how to reference current object in Model - ruby-on-rails

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

Related

How do I replace an ActiveRecord association member in-memory and then save the association

I have a has_may through association and I'm trying to change records in the association in memory and then have all the associations updated in a single transaction on #save. I can't figure out how to make this work.
Here's a simplifiction of what I'm doing (using the popular Blog example):
# The model
class Users < ActiveRecord::Base
has_many :posts, through: user_posts
accepts_nested_attributes_for :posts
end
# The controller
class UsersController < ApplicationController
def update
user.assign_attributes(user_params)
replace_existing_posts(user)
user.save
end
private
def replace_existing_posts(user)
user.posts.each do |post|
existing = Post.find_by(title: post.title)
next unless existing
post.id = existing
post.reload
end
end
end
This is a bit contrived. The point is that if a post that the user added already exists in the system, we just assign the existing post to them. If the post does not already exist we create a new one.
The problem is, that when I call user.save it saves any new posts (and the user_post association) but doesn't create the user_post association for the existing record.
I've tried to resolve this by adding has_many :user_posts, autosave: true to the User model, but despite the documented statement "When :autosave is true all children are saved", that doesn't reflect the behavior I see.
I can make this work, with something hacky like this, but I don't want to save the association records separately (and removing and replacing all associations would lead to lots of callback I don't want to fire).
posts = user.posts.to_a
user.posts.reset
user.posts.replace(posts)
I've trolled through the ActiveRecord docs and the source code and haven't found a way to add records to a has_many through association that create the mapping record in memory.
I finally got this to work, just by adding the association records manually.
So now my controller also does this in the update:
user.posts.each do |post|
next unless post.persisted?
user.user_posts.build(post: post)
end
Posting this as an answer unless someone has a better solution.

How create userd_id for all users in another table

I have 3 models:
user: id
has_many :article_users
has_many :articles, through: :article_users
article: user_id
has_many :article_users
has_many :users, through: :article_users
article_user: user_id, article_id
belongs_to: users
belongs_to: articles
How to add all users id to user_id in article_user model when creating new article?
def create
#article = Article.new(article_params)
...
end
You can set a new article's user to the current user like so (assuming you have a current_user method)
#article = Article.new(article_params)
#article.user = current_user
I don't understand what you mean by "How to add all users id" - can you expand on what you actually mean by this please?
EDIT: to associate the article with all existing users, you could do something like
#article = Article.new(article_params)
#article.save
#article.users << User.all
However, this feels like an odd thing to do: it will create a lot of join table records, whereas really it could be summarised with one "bit" of information, eg "can this be accessed by all users?". What if you make a new user later - that user will not have access to this article. Do you want them to?
If you want an article to just be "available to all users" rather than "available to the following list of specific users, which might happen to be all users", then you could just add a boolean field to the Article's table called "available_to_all_users" or something. Then, when you want to find out which articles are available to a given user, you can have a method like this:
#in User
def available_articles
(Article.where(available_to_all_users: true).all + self.articles).uniq
end
There is no such way to add user_id to article_users table for existing records. You have to manually populate the user_id using a migration, but if you have user_id value in the articles table then there is a chance to add the user_id.

Which rails model do I put a multiple table query in

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

My rails app let user vote on posts, how to make user see "unvoted by his own" posts at index page?

My app has simple user/post/vote structure, user can up-vote/down-vote on each post. I want to let user to see "their own unvoted posts" at the index page, I tried where where.not but can't combine it to work.
I tried to do this in the posts_controller and my best try is to find all unvoted posts but don't know how to get related to users.
def index
if logged_in?
#posts = Post.where.not(id: Vote.all.map(&:voteable_id))
else
#posts = Post.all.sort_by{|x| x.total_votes}.reverse
end
end
The vote is polymorphic association, below are the 3 models:
Post: has_many :votes, as: :voteable,
Vote: belongs_to :voteable, polymorphic: true,
User: has_many :votes
I have logged_in? to judge if #current_user exists.
Here is my repo: https://github.com/Tim-Feng/ad-judge
I am not sure what you mean by "their own unvoted posts" since the example code you have is kind of confusing.
If you need to find the current user's posts which have no votes you should do the following:
current_user.posts.where.not(id: Vote.pluck(:voteable_id).uniq)
If on the other hand you need to find the posts that have not been voted by the current user, you should be using only the current user's votes like so:
Post.where.not(id: current_user.votes.pluck(:voteable_id).uniq)
Hope it helps
TIP: if you need to make your code more efficient you can make it run all in one query by using left joins instead of searching for the ids to except.

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

Resources