after_build runs 2 times in FactoryGirl - ruby-on-rails-3

There is my model:
class Profile < ActiveRecord::Base
after_create do
pp id
track_activity
end
end
This is my factoty:
factory :user do
email {Factory.next :email}
after_build do |user|
user.profile_attributes = {name: 'why'}
end
end
The id prints 2 times with the follow code in spec:
expect do
user = create :user
end.to change(Activity, :count).by 1

Related

Rails ActiveRecord querying

So I have the following three models: Assignment.rb, Submission.rb, User.rb
And here are the relationships:
class Assignment
has_many :submissions
end
class Submission
belongs_to :assignment
belongs_to :user
# submission has a boolean column called submitted with val true or false
end
class User
has_many submissions
end
I want to know how can I query the assignments that a user has not submitted (in a clean way)? If a user submits an assignment, a new submission for that assignment and user will be created.
Not sure if I provided enough info for anyone to answer, so please comment if anything else is needed.Thx!
The logic that #Norly Canarias is using is correct, but I would alter it to use methods on the User class, and I would also modify it to make it database-agnostic (for example, using 'submissions.submitted = true' will not work at all in Postgres).
class User < ApplicationRecord
has_many :submissions
has_many :assignments, through: :submissions
def submitted_assignments
assignments.where(submissions: {submitted: true})
end
def unsubmitted_assignments
Assignment.where.not(id: submitted_assignments)
end
end
I have tested this and it works as expected. For a user who has a Submission for Assignment 1 with submitted == true, and who has a Submission for Assignment 2 with submitted == false, and assuming there are two more Assignments (3 and 4) for which no Submission exists, you will get:
>> user.submitted_assignments.ids
#=>[1]
>> user.unsubmitted_assignments.ids
#=>[2, 3, 4]
I think something like this could work (I haven't tested though):
class Assignment
has_many :submissions
end
class Submission
belongs_to :assignment
belongs_to :user
end
class User
has_many :submissions
has_many :assignments, through: :submissions
end
user = User.first
submitted = user.assignments.where('submissions.submitted = true')
not_submitted = Assignment.where.not(id: submitted)
You can also make it a scope
class Assignment
has_many :submissions
scope :not_submitted_by_user, ->(user) do
where.not(id: user.assignments.where('submissions.submitted = true'))
end
end
user = User.first
not_submitted = Assignment.not_submitted_by_user(user)
To get all the Assignments that are not from a specific user
#assignments = Assignment.where.not(user_id: user_id)
A clean way to do it is to create a scope in the Assignment Model
class Assignment
has_many :submissions
scope :not_from_user, ->(user_id) {where.not(user_id: user_id) }
end
And then calling
#assignments = Assignment.not_from_user 1

Load associations to one level while conditionally sideloading associations in Active model serializers

AMS version 0.8.3,
I created a base_serializer.rb like this and extended the same.
class BaseSerializer < ActiveModel::Serializer
def include_associations!
if #options[:embed]
embed = #options[:embed].split(',').map{|item| item.strip.to_sym}
embed.each do |assoc|
include! assoc if _associations.keys.include?(assoc)
end
end
end
end
class EventSerializer < BaseSerializer
attributes :id, :name
has_many :organizers, serializer: OrganizerSerializer
has_many :participants, serializer: ParticipantSerializer
end
class OrganizerSerializer < BaseSerializer
attributes :id, :name
has_many :related, serializer: RelatedSerializer
end
class ParticipantSerializer < BaseSerializer
attributes :id, :name
has_many :related, serializer: RelatedSerializer
end
class RelatedSerializer < BaseSerializer
attributes :id, :name
has_many :something, serializer: SomethingSerializer
end
and the index method in EventsController is written as
# GET /events?embed=organizers,participants
def index
#events = Event.all
render json: #events, embed: params[:embed]
end
With this I can get the :id and :name of events, organizers and participants. But, I want the attributes of related association as well. I don't need details of something serializer. I want to go till this level for each association. How can I achieve that?
I ended up doing this to achieve the same.
class BaseSerializer < ActiveModel::Serializer
def include_associations!
#options[:embed_level] ||= 2
return unless #options.key?(:embed) && #options[:embed_level] != 0
embed = #options[:embed].split(',').map{|item| item.strip.to_sym}
embed.each do |assoc|
next unless _associations.key?(assoc)
assoc_serializer = serializer_for(assoc)
embed = #options[:embed]
embed_level = #options[:embed_level]
#options[:embed_level] = #options[:embed_level] - 1
#options[:embed] = assoc_serializer._associations.keys.join(",")
include! assoc
#options[:embed_level] = embed_level
end
end
def serializer_for(assoc)
serializer = _associations[assoc].options[:serializer]
return serializer if serializer
assoc.to_s.classify.concat("Serializer").constantize
end
end
Ref: Github Issue Link
Special Thanks to Yohan Robert!!!

testng rSpec saving or update on a has_many relation

I have a simple User and Score class and an IndexController
class User < ActiveRecord::Base
has_many :scores
attr_accessible :scores_attributes, :scores
accepts_nested_attributes_for :scores
end
class Score < ActiveRecord::Base
attr_accessible :time_elapsed
belongs_to :user
end
My IndexController (simplified)
def setHighscore
existing_user = User.find_by_random_token(session[:user][:random_token])
existing_user.scores.new(time_elapsed: params[:time_elapsed])
existing_user.save
end
And my spec
describe IndexController do
it "appends a highscore to an existing user" do
user = User.create!(valid_session[:user])
existing_user = User.should_receive(:find_by_random_token).with(random_token).and_return(user)
existing_user.scores.should_receive(:new).with(time_elapsed: 400)
post :setHighscore, valid_params, valid_session
end
I got this error
RSpec::Mocks::MockExpectationError: (#<RSpec::Mocks::MessageExpectation:0x007fb4fa2b67c0>).new({:time_elapsed=>400})
expected: 1 time with arguments: ({:time_elapsed=>400})
received: 0 times with arguments: ({:time_elapsed=>400})
How do I correctly test when I do an update_attribute(blah) or model.model.create(blah) or model.model.new(blah)` ?
thank you
I think Score gets method new so try to change code to
Score.should_receive(:new).with(time_elapsed: 400)
I am not aware what causes this issue but I ended up using one of this two terms:
existing_user.scores.should have(1).item
but also this one works
Score.count.should == 1

ActiveRecord polymorphic has_many with ActiveSupport::Concern

I have the following Concern:
module Eventable
extend ActiveSupport::Concern
# ...
included do
has_many :subscriptions, as: :entity, dependent: :destroy
end
end
My Models are:
class Experiment < ActiveRecord::Base
include Eventable
end
class Subscription < ActiveRecord::Base
belongs_to :entity, polymorphic: true
end
In my controller I try to create a subscription for an experiment, as following:
class SubscriptionsController < ApplicationController
before_filter :find_entity
def create
subscription = Subscriptions.new(params[:subscription])
#entity.subscriptions << subscription # Why is it false?
# ...
end
end
But it doesn't work.
While debugging, I noticed that
#entity.subscriptions.count create incorrect SQL query:
SELECT COUNT(*) FROM [subscriptions] WHERE [subscriptions].[experiment_id] = 123
while I expect:
SELECT COUNT(*) FROM [subscriptions] WHERE [subscriptions].[entity_id] = 123 AND [subscriptions].[entity_type] = 'Experiment'
Note: If I do the following, it works correctly:
subscription.entity = #entity
subscription.save
Thanks for help!
The reason for this error: class Experiment (not my class) already had has_many :subscriptions
Advise: if you have strange behavior, and you use others people code, stop and review the code!

Rspec no Method error assignment

I apparently added a new field to my Customer model known as ct_ratio before running tests. When I now run tests on my invoice spec, they fail with a "No method error,ct_ratio="
Customer.rb
class Customer < ActiveRecord::Base
attr_accessible :billing_address, :customer_currency, :email, :first_name, :last_name, :mobile, :name, :payment_terms, :phase_type, :readings_attributes, :pays_vat, :ct_ratio
before_validation do
self.ct_ratio ||= 1
end
end
Invoice_spec
describe Invoice do
context 'create_item_from_readings' do
before :each do
#customer = FactoryGirl.create :customer
#reading1 = #customer.readings.create! reading1: 100, date_of_reading: 30.days.ago
#reading2 = #customer.readings.create! reading1: 200, date_of_reading: 20.days.ago
#reading3 = #customer.readings.create! reading1: 500, date_of_reading: 10.days.ago
#customer.stub(:unit_cost).and_return(100)
end
it "should create an invoice item for all reading" do
invoice = Invoice.new customer: #customer, invoice_date: 15.days.ago, due_date: Date.today
item = invoice.create_item_from_readings
item.rate.should == 100
item.amount.should == 100 * 100
item.description.should == "Electricity used from #{#reading1.date_of_reading.strftime('%d/%m/%Y')} to #{#reading2.date_of_reading.strftime('%d/%m/%Y')} - 100 units"
end
I added ct_ratio to Factory Girl
require 'factory_girl'
FactoryGirl.define do
factory :customer do
name 'Test Test'
first_name "Test"
last_name "Test"
mobile "00000000"
billing_address "Kampala"
payment_terms "Immediate"
phase_type "Single Phase"
customer_currency "UGX"
pays_vat false
email "test#test.com"
ct_ratio 1
end
factory :reading do |f|
f.reading1 100
f.reading2 150
f.reading3 200
f.date_of_reading 30.days.ago
end
end
this is a common error when not running tests via rake and your migration is not run on the test-database.
run RAILS_ENV=test rake db:migrate first and check out your testsuite.