act_as_votable rails Counting votes on answers emited by specific user - sql

I am close to new at Ruby on Rails (but love it) and I cannot managge to filter acuratelly.
Mi goal is to retrieve all the votes puted on answers created by one user. And I am using act_as_votable_gem
On answer.rb I have:
class Answer < ActiveRecord::Base
acts_as_votable
belongs_to :user
belongs_to :letter
...
end
At user.rb I have:
class User < ActiveRecord::Base
acts_as_voter
has_many :answers, dependent: :destroy
...
end
At users_controller.rb comes the trick because I have the letter form on the user show action:
def show
#user = User.find(params[:id])
#letter = Letter.new(params[:letter])
#letters = #user.letters.all
#answers = #user.answers
...
#emitedupvotes = #user.votes.up.count
#emiteddownvotes = #user.votes.down.count
#totalemitedvotes = #emitedupvotes + #emiteddownvotes
#receivedupvotes = ????????
#receiveddownvotes = ???????
...
end
I have tryed:
a) (the recomended one) #receivedupvotes = #answer.votes.up.count but this one needs #answer = Answer.find(params[:id]) and I am having => "ActiveRecord::RecordNotFound at /users/1 Couldn't find Answer with id=1"
Or, if I do: Answer.find(params[:answer_id]) => "Couldn't find Answer without an ID".
b) (the most logical) #receivedupvotes = #answers.votes.up.count => "undefined method `votes' for nil:NilClass"
c) (crazy one) #receivedupvotes = #answers.user.votes.up.count => "undefined method `user'"
d) #receivedupvotes = #answers.votes.up.where("voter = #user").count => "undefined method `votes' for nil:NilClass".
So, I tryed joins (never done before):
e) #receivedupvotes = #answers.joins(:votes).votes.up.count => "undefined method `votes' for..."
f) #receivedupvotes = #answers.joins(:votes).up.count => "undefined method `up' for..."
Any help? Thanks.

I don't know if you're still stuck on this, but if so..
What gets voted on? Seems like Answer gets voted on.
If User has_many :answers, then that means #user.answers will return you an array of Answer objects. However, judging by your point b) it looks like #answers is nil, so you'll have to dig into that to find out why.
So getting #user.answers to return an actual array of Answer and not nil is your first step.
Once you get that far, #answers.votes won't work because again, #answers is an array. Each Answer has votes, so you would call votes on an instance of Answer, not on the array.
Getting back to your main goal which is to tally up all the up and down votes a user received. Since User doesn't get voted on, but Answer does get voted on, you'll want to tally up all the votes for each of the user's answers.
Here's one way:
#receivedupvotes = 0
#receiveddownvotes = 0
#user.answers.each do |answer|
#receivedupvotes = #receivedupvotes + answer.votes.up
#receiveddownvotes = #receiveddownvotes + answer.votes.down
end
Also, you should really use underscores in your variable names, like #received_up_votes. It's the recommended way for Ruby and is more readable.

Jeff. Thank you very much. It is working now as it should.
This is the answer for others to read:
#user.answers is working as it should.
An this is the working implementation for counting votes on user answers with act_as_votable:
#user_answers_received_upvotes = 0
#user_answers_received_downvotes = 0
#user.answers.each do |answer|
#user_answers_received_upvotes = #user_answers_received_upvotes + answer.votes.up.size
#user_answers_received_downvotes = #user_answers_received_downvotes + answer.votes.down.size
end
#user_answers_total_received_votes = #user_answers_received_upvotes + #user_answers_received_downvotes
I will upvote your answer when I can.
Thanks again.

Related

how can I hide resources?

I want to create a page where it shows the resource created by other users but hide the resources created by current_user. is there a method or certain way in which I can do so?
class ExamplesController < ApplicationController
def index
#examples = Example.all.order("created_at DESC")
#creator_examples = Example.where(creator: current_user).order("created_at DESC") <---hide this!!!
end
You can simply manipulate your where clause into something like this:
def index
#examples = Example.all.order("created_at DESC")
#creator_examples = #examples.where.not(id: current_user.id)
end
This is for rails 4, if you're using rails 3
#creator_examples = Example.where("id != ?", current_user.id)
Note -> Example.all in rails 3 returns an array so you can't chain it with where

Rails 3 ActiveRecord Query questions

I've implemented "following" function. Showing "people user A is following" was simple, but showing "people who are following user A" is giving me troubles.
I have follow_links, which have id, user_id, and follow_you_id column. When user A begins following user B, the columns will be like (user_id = A, follow_you_id = B).
To show users that A(#user) is following, I can simply do
#follow_yous = #user.follow_yous
But I'm not sure how to show users who are following A(#user)
To do this, I first found all the related links.
#follow_links = FollowLink.where(:follow_you_id => #user.id)
Now I thought I could just do #follow_mes = #follow_links.users, but it says user is an undefined method. So I guess I can either call user.follow_yous or follow_you.users.
My next approach was
#follow_links = FollowLink.where(:follow_you_id => #user.id)
#follow_mes = User.where(:id => #user.id, :include => #follow_links)
I intended to find all the User objects that had the provided #follow_links objects, but I think the syntax was wrong. I couldn't find a decent solution after a bit of research. I'd appreciate any help.
Update:
FollowLink model
belongs_to :user
belongs_to :follow_you, :class_name => "User"
You can use joins like this:
#users = User.joins(:follow_links).where(:follow_links => { :follow_you_id => #user.id })
you can use following:
#follow_links = FollowLink.where(:follow_you_id => #user.id)
#follow_links.collect(&:user) # :user should be the name of your relation to user in your followlink model
=> [User1, User2,...]

Rails - avoid autosave in association

My models and its associations are:
class Post < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :post
validates :commenter, :presence => true
end
Case1: Automatically save method is called when I tried below code.
#post = Post.find(3)
#comments = #post.comments
p #comments #=> []
p #comments.class #=> Array
if #comments.empty?
3.times do
#comments << #post.comments.build
end
end
p #comments.first.errors #=>{:commenter=>["can't be blank"]}
Case2: if I manually initialize same empty array to the #comments, auto save is not calling. for instance,
p #comments #=> []
p #comments.class #=> Array
if #comments.empty?
#comments = []
p #comments #=> []
3.times do
#comments << #post.comments.build
end
end
p #comments.first.errors #=>{}
What is the best solution to avoid auto save and please any one explain why the above code behave differently?
Rails extensively uses monkey-patching, so rails Array is not the same thing as pure Ruby array. (Compare output from irb > [].methods and rails c > [].methods
According to the documentation << method of has_many collection
instantly fires update sql without waiting for the save or update call
on the parent object
So most likely Rails have an "observer" of the collection events, and fires validation when you try to add new object.
In second snippet you use pure array (not has_many collection), so the update action is not fired.
To avoid update action you don't need << at all
#post = Post.find(3)
#comments = #post.comments
if #comments.empty?
3.times do
#post.comments.build
end
end
p #comments.size
=> 3
Autosave is defined in the Post model. Read here about Autosave. If I understand your question correctly, then it should be enough to define :autosave => false.

FactoryGirl: why does attributes_for omit some attributes?

I want to use FactoryGirl.attributes_for in controller testing, as in:
it "raise error creating a new PremiseGroup for this user" do
expect {
post :create, {:premise_group => FactoryGirl.attributes_for(:premise_group)}
}.to raise_error(CanCan::AccessDenied)
end
... but this doesn't work because #attributes_for omits the :user_id attribute. Here is the difference between #create and #attributes_for:
>> FactoryGirl.create(:premise_group)
=> #<PremiseGroup id: 3, name: "PremiseGroup_4", user_id: 6, is_visible: false, is_open: false)
>> FactoryGirl.attributes_for(:premise_group)
=> {:name=>"PremiseGroup_5", :is_visible=>false, :is_open=>false}
Note that the :user_id is absent from #attributes_for. Is this the expected behavior?
FWIW, my factories file includes definitions for :premise_group and for :user:
FactoryGirl.define do
...
factory :premise_group do
sequence(:name) {|n| "PremiseGroup_#{n}"}
user
is_visible false
is_open false
end
factory :user do
...
end
end
Short Answer:
By design, FactoryGirl's attribues_for intentionally omits things that would trigger a database transaction so tests will run fast. But you can can write a build_attributes method (below) to model all the attributes, if you're willing to take the time hit.
Original answer
Digging deep into the FactoryGirl documentation, e.g. this wiki page, you will find mentions that attributes_for ignores associations -- see update below. As a workaround, I've wrapped a helper method around FactoryGirl.build(...).attributes that strips id, created_at, and updated_at:
def build_attributes(*args)
FactoryGirl.build(*args).attributes.delete_if do |k, v|
["id", "created_at", "updated_at"].member?(k)
end
end
So now:
>> build_attributes(:premise_group)
=> {"name"=>"PremiseGroup_21", "user_id"=>29, "is_visible"=>false, "is_open"=>false}
... which is exactly what's expected.
update
Having absorbed the comments from the creators of FactoryGirl, I understand why attributes_for ignores associations: referencing an association generates a call to the db which can greatly slow down tests in some cases. But if you need associations, the build_attributes approach shown above should work.
I think this is a slight improvement over fearless_fool's answer, although it depends on your desired result.
Easiest to explain with an example. Say you have lat and long attributes in your model. On your form, you don't have lat and long fields, but rather lat degree, lat minute, lat second, etc. These later can converted to the decimal lat long form.
Say your factory is like so:
factory :something
lat_d 12
lat_m 32
..
long_d 23
long_m 23.2
end
fearless's build_attributes would return { lat: nil, long: nil}. While the build_attributes below will return { lat_d: 12, lat_m: 32..., lat: nil...}
def build_attributes
ba = FactoryGirl.build(*args).attributes.delete_if do |k, v|
["id", "created_at", "updated_at"].member?(k)
end
af = FactoryGirl.attributes_for(*args)
ba.symbolize_keys.merge(af)
end
To further elaborate on the given build_attributes solution, I modified it to only add the accessible associations:
def build_attributes(*args)
obj = FactoryGirl.build(*args)
associations = obj.class.reflect_on_all_associations(:belongs_to).map { |a| "#{a.name}_id" }
accessible = obj.class.accessible_attributes
accessible_associations = obj.attributes.delete_if do |k, v|
!associations.member?(k) or !accessible.include?(k)
end
FactoryGirl.attributes_for(*args).merge(accessible_associations.symbolize_keys)
end
Here is another way:
FactoryGirl.build(:car).attributes.except('id', 'created_at', 'updated_at').symbolize_keys
Limitations:
It does not generate attributes for HMT and HABTM associations (as these associations are stored in a join table, not an actual attribute).
Association strategy in the factory must be create, as in association :user, strategy: :create. This strategy can make your factory very slow if you don't use it wisely.
The accepted answer seems outdated as it did not work for me, after digging through the web & especially this Github issue, I present you:
A clean version for the most basic functionality for Rails 5+
This creates :belongs_to associations and adds their id (and type if :polymorphic) to the attributes. It also includes the code through FactoryBot::Syntax::Methods instead of an own module limited to controllers.
spec/support/factory_bot_macros.rb
module FactoryBot::Syntax::Methods
def nested_attributes_for(*args)
attributes = attributes_for(*args)
klass = args.first.to_s.camelize.constantize
klass.reflect_on_all_associations(:belongs_to).each do |r|
association = FactoryBot.create(r.class_name.underscore)
attributes["#{r.name}_id"] = association.id
attributes["#{r.name}_type"] = association.class.name if r.options[:polymorphic]
end
attributes
end
end
this is an adapted version of jamesst20 on the github issue - kudos to him 👏

Query can't find column

I've been trying to make this query work for the last couple of hours, but I can't. So I hope someone can help.
Here's the error:
Mysql::Error: Unknown column 'network_id' in 'where clause': SELECT networks.* FROM networks WHERE (network_id = 1,2)
Here are my models:
class Network < ActiveRecord::Base
belongs_to :customer
has_many :user_network_relations
class Customer < ActiveRecord::Base
has_one :network, :dependent=>:destroy
accepts_nested_attributes_for :network
class UserNetworkRelation < ActiveRecord::Base
belongs_to :network
accepts_nested_attributes_for :network
controller
#user = User.find(params[:id])
#user_approved = UserNetworkRelation.find(:all,:conditions => ['user_id = ? and status =? ', #user, "approved"])
#networks = Network.find(:all,:conditions => ['network_id = ?',#user_approved])
#user_networks = Customer.find(#networks)
Any help is appreciated. Thanks in advance!
The error is in this line:
#networks = Network.find(:all,:conditions => ['network_id = ?',#user_approved])
You are explicitly telling ActiveRecord that you want to restrict the column network_id which obviously doesn't exist. Probably you meant the id column. The correct line could read as follows:
#networks = Network.where(:id => #user_approved).all
or equivalently
#networks = Network.where('id = ?', #user_approved).all
Note that in my example, I used the new ActiveModel / AREL syntax of Rails 3. You used the deprecated (but still supported) Syntax of Rails 2. You should switch to the new syntax as it allows you to chain queries which is mightier and much more readable. See the documentation on how to use the new AREL syntax.