RSpec records attribute doesn't update - ruby-on-rails

Trying to test if making a Vote on an Answer updates its score.
The score updates but its not the same object that gets tested.
( Every Vote has its own voteable Answer object, with same .id)
Therefore the spec fails.
PRY console during rspec testing:
> Answer.first
=> #<Answer:0x000000087b6418 id: 1, (...) score: 2>
> answer
=> #<Answer:0x000000081bea88 id: 1, (...) score: 0>
vote_spec.rb
answer ||= FactoryGirl.create(:answer)
vote = FactoryGirl.create(:vote, like: true, user_id: 1, voteable_id: answer.id, voteable_type: "Answer")
vote2 = FactoryGirl.create(:vote, like: true, user_id: 2, voteable_id: answer.id, voteable_type: "Answer")
vote3 = FactoryGirl.create(:vote, like: true, user_id: 3, voteable_id: answer.id, voteable_type: "Answer")
vote4 = FactoryGirl.create(:vote, user_id: 4, voteable_id: answer.id, voteable_type: "Answer")
expect(answer.score).to eq(2)
vote.rb
after_create :set_voteable_score
private
def set_voteable_score
self.voteable.update_column(:score, count_score(self.voteable))
true
binding.pry
end
def count_score(voteable)
votes = voteable.votes.all
votes.where(like: true).count - votes.where(like: false).count
end
So if I check the vote.voteable.score with all vote(vote, vote2, vote3...), each returns a different voteable object, and the answer in vote_spec.rb doesn't get updated.
Why is this behaviour ( more than 1 record with same id exists)?

If all the voteables have the same id, they're all the same records. What you have then, probably, is different instances of the record, which have been loaded from the db at different times.
Try this:
expect(answer.reload.score).to eq(2)

Related

(NoMethodError) Undefined private method in model - Rails

I'm building a simple ecommerce webpage and the functionality I want to create is:
User clicks an "ADD TO CART" button in one of the products --> the ORDER is created with the user_id --> the ORDER_ITEM is created with order_id and product_id.
I want to build all the logic in OrderItem model:
class OrderItem < ApplicationRecord
belongs_to :order
belongs_to :product
before_validation :generate_order
private
def self.generate_order(user)
if Order.find_by(status: 1)
order = Order.find_by(status: 1)
else
order = Order.new(status: 1, total: 0, subtotal: 0, date: Date.today())
order.user = user
order.save!
end
return order
end
end
Basically, if there is an Order open (status = 1) then return that order and if not create one.
And in the OrderItem controller:
class OrderItemsController < ApplicationController
def create
#product = Product.find(params[:product_id])
#order = OrderItem.generate_order(current_user)
#order_item = OrderItem.new
#order_item.product = #product
#order_item.order = #order
if #order_item.save!
redirect_to cart_path
else
redirect_to root_path
end
end
def delete
end
end
Everything goes well until it arrives to the .save! point where it throws this error:
undefined method `generate_order' for #<OrderItem:0x00007fe8f77552c8>
I checked the logs and everything seems to have been created well:
>> #order_item
=> #<OrderItem id: nil, order_id: 1, product_id: 65, created_at: nil, updated_at: nil>
>> #order
=> #<Order id: 1, subtotal: 0, total: 0, date: "2021-09-05 00:00:00", user_id: 5, created_at: "2021-09-05 00:00:12", updated_at: "2021-09-05 00:00:12", status: 1>
>> #product
=> #<Product id: 65, name: "Shou Mei", description: "Sequi dolores facilis rerum quo odit veritatis ips...", price: 5893, rating: 5, user_id: 13, created_at: "2021-09-03 23:54:46", updated_at: "2021-09-03 23:54:47", availability: 2>
Why is throwing that error and how could I make it right? Thanks!
this line in your model is the problem:
before_validation :generate_order
You only have a class method self.generate_order, but this would be looking for a instance method. Judging from the code inside self.generate_order it doesn't seem you want that to be checked before each validation, so you can delete the line (or write an instance method that serves whatever purpose you had in mind).

Rails: How to Test an Array's Order with RSpec

I'm new to RSpec so I'm looking for a little help on a simple test:
# controller method
def show
#group = Group.find(params[:id])
#group_members = #group.group_members.order("posts ASC")
end
# in my rspec
it "should show order correctly" do
#group = FactoryGirl.create(:group)
#user_1 = FactoryGirl.create(:user, user_name: "Gary")
#user_2 = FactoryGirl.create(:user, user_name: "Shawn")
#user_3 = FactoryGirl.create(:user, user_name: "Gus")
#user_4 = FactoryGirl.create(:user, user_name: "Jack")
#group_member_1 = FactoryGirl.create(:group_member, group_id: #group.id, user_id: #user_1.id, posts: 30)
#group_member_2 = FactoryGirl.create(:group_member, group_id: #group.id, user_id: #user_2.id, posts: 20)
#group_member_3 = FactoryGirl.create(:group_member, group_id: #group.id, user_id: #user_3.id, posts: 10)
#group_member_4 = FactoryGirl.create(:group_member, group_id: #group.id, user_id: #user_4.id, posts: 15)
visit group_path(#group)
# how do i assert the order of the array?
end
Can someone please help me with a statement to check that the array sorted correctly?
my_array.should eq expected_array
That will make sure that each item is in the exact same spot. If you want to check that an array has the same elements as another array, but the order doesn't matter, do this:
my_array.should =~ expected_array
So in your particular case, you first would need to do a get to the show action, then check the variable. That's done like this:
get :show, :id => #group.id
expected_group_members = [#group_member_3, #group_member_4, #group_member_2, #group_member_1]
assigns(:group_members).should eq expected_group_members
For more information, check out RSpec's GitHub page.

Rails - how to instantly get id of inserted row

I use this logic in my app:
controller
#current_user = User.find_or_create_from_oauth(auth_hash)
user.rb
def self.find_or_create_from_oauth(auth_hash)
provider = auth_hash["provider"]
uid = auth_hash["uid"].to_s
case provider
when 'twitter'
if user = self.find_by_twitter_uid(uid)
return user
else
return self.create_user_from_twitter(auth_hash)
end
end
end
def self.create_user_from_twitter(auth_hash)
a = self.create({
:twitter_uid => auth_hash["uid"],
:name => auth_hash["info"]["name"]
})
puts a.inspect
user = User.find_by_twitter_uid(a.twitter_uid)
puts '---'
puts user.inspect
end
Immediately after self.create I would need to run this line:
Assignment.create(:user_id => a.id, :role_id => 2)
The problem is, that the line puts user.inspect return something like this:
#<User id: nil, name: "...name...", twitter_uid: "96580821", provider: "twitter", created_at: nil, updated_at: nil>
Why is in the hash returned id: nil?
Or, is there any other way, how to get the ID of last created record?
If the user has been correctly saved, you can use directly a:
a.assignments.create(:role_id => 2)
Otherwise (check using create! instead of create) there may be a validation error.

Rails - Determine what properties on an object are set by a setter

Given this class:
class MyModel < ActiveRecord::Base
belongs_to :association1
belongs_to :association2, :polymorphic => true
end
I know that when I set association1, it sets association1_id to the ID of object 1
m = MyModel.new
m.association1 = object1
#<MyModel id: nil, association1_id: 1, association2_id: nil, association2_type: nil>
I know that when I set association2, it sets association2_id AND association2_type
m.association2 = object2
#<MyModel id: nil, association1_id: 1, association2_id: 2, association2_type: 'ClassType'>
My question is:
Is there a function that can easily tell me what information is being set on an object in hash form?
MyModel.magic_function(:association1, object1)
# returns {:association1_id => 1}
MyModel.magic_function(:association2, object2)
# returns {:association2_id => 2, :association2_type => 'ClassType'}
Perhaps you're looking for changes:
person = Person.new
person.changes # => {}
person.name = 'bob'
person.changes # => { 'name' => [nil, 'bob'] }
This is the stop gap solution I have for now, just though I'd share:
def self.magic_method(association, object)
instance = self.new
instance.send(association, object)
h = Hash.new
instance.changes.each do |k,v|
h[k] = v[1]
end
h
end
Is this built into rails somewhere?

How to create associated objects (you have accepted parameters) for after saving in Rails?

The problem I am having with this is Product is trying to create variants before the product is even created and there are certain callbacks for variants that require the product to exist. So how can I rewrite this so that v.save doesn't execute till the object is created or whatever.
Product.class_eval do
validates_presence_of [:style_no, :market_price, :designer, :short_description, :description]
validates_numericality_of [:size_47_quantity,
:size_46_quantity,
:size_45_quantity,
:size_44_quantity,
:size_43_quantity,
:size_42_quantity,
:size_41_quantity,
:size_40_quantity,
:size_39_quantity]
for i in 39..47
define_method:"size_#{i}_quantity" do
if v = self.variants.find_by_size(i)
v.count_on_hand
else
0
end
end
define_method:"size_#{i}_quantity=" do |amount|
# if only there is some method that can postpone all the following if this product hasn't been created yet!
self.id = Product.last.id + 1 unless self.id
v = self.variants.find_by_size(i) || self.variants.new(:size => i)
v.count_on_hand = amount
v.save
end
end
end
You can try this solution:
Product class
class Product < ActiveRecord::Base
validates_presence_of [:style_no, :market_price, :designer, :short_description, :description]
has_many :variants
# This method would check if variant was created or loaded.
#
# So many sequantial calls to it will return same object
def variant_with_size(size)
self.variants.select{|v| v.size == size}.first || self.variants.where('size = ?', size).first
end
module ClassExtensions
def self.included(base)
(39..47).each do |i|
method = "size_#{i}_quantity".to_sym
included_module = Module.new
included_module.module_eval <<EOF
def #{method}
if v = self.variant_with_size(#{i})
v.count_on_hand
else
0
end
end
def #{method}=(amount)
v = self.variant_with_size(#{i}) || self.variants.build(:size => #{i})
v.count_on_hand = amount
v
end
EOF
base.send :include, included_module
end
end
end
include ClassExtensions
end
Variant class
class Variant < ActiveRecord::Base
belongs_to :product
validates :count_on_hand, :numericality => true
end
Usage
Usage example with correct variant amount:
ruby-1.9.2-p180 :001 > p = Product.new
=> #<Product id: nil, style_no: nil, market_price: nil, designer: nil, short_description: nil, description: nil, created_at: nil, updated_at: nil>
ruby-1.9.2-p180 :002 > p.size_39_quantity
=> 0
ruby-1.9.2-p180 :003 > p.size_39_quantity = 2
=> 2
ruby-1.9.2-p180 :004 > p.variants
=> [#<Variant id: nil, product_id: nil, size: 39, count_on_hand: 2, created_at: nil, updated_at: nil>]
ruby-1.9.2-p180 :005 > p.save
=> true
ruby-1.9.2-p180 :006 > p.variants
=> [#<Variant id: 3, product_id: 3, size: 39, count_on_hand: 2, created_at: "2011-04-06 06:34:46", updated_at: "2011-04-06 06:34:46">]
Usage with incorrect variant amount:
ruby-1.9.2-p180 :007 > p1 = Product.new
=> #<Product id: nil, style_no: nil, market_price: nil, designer: nil, short_description: nil, description: nil, created_at: nil, updated_at: nil>
ruby-1.9.2-p180 :008 > p1.size_39_quantity = 'A'
=> "A"
ruby-1.9.2-p180 :009 > p1.save
=> false
ruby-1.9.2-p180 :010 > p1.errors
=> {:variants=>["is invalid"]}
ruby-1.9.2-p180 :011 > p1.variants[0].errors
=> {:count_on_hand=>["is not a number"]}
At a glance, I'd consider using an after_save callback on Product to create product variants.
Something like:
class Product < ActiveRecord::Base
has_many :variants
after_save :create_variants! if :not_a_variant?
OPTIONS = [:size_1_qty, :size_2_qty] # TODO: move to a OptionType model associated with Product
def not_a_variant?
size.nil? # or however you might distinguish a Product from a Variant
end
private
def create_variants!
# OPTIONS could instead be related option_types. perhaps a 'size' option type with values of 40, 41, 42, etc.
OPTIONS.each do |size|
variants.build(...)
end
save!
end
end
I was just reviewing the Spree shopping cart project by Rails Dog and they handle product variants in a similar fashion. You might check it out.

Resources