Fixture reverts value after controller method - ruby-on-rails

My app for teachers includes a method to delete a student from a seminar (a class but I didn't want to use the word "class") by deleting that student's record from the join table. I don't delete the student directly, but I delete an "Aula," which is a record in the student/seminar join table. I'm trying to write a test to ensure that when a student is deleted, that student's id is also deleted from the seating chart array. This function appears to be working correctly in development, but not in testing.
The Test
test "Remove student from class period and seating chart" do
log_in_as(#teacher_user)
get seminar_path(#seminar)
seating = [#student.id, #other_student.id]
#seminar.update(:seating => seating)
assert_difference ['Aula.count','#student.aulas.count', '#seminar.students.count'], -1 do
delete aula_path(#aula)
end
puts "In test"
puts #seminar.seating
assert_redirected_to scoresheet_url(#seminar)
end
The Destroy Method in the Aula Controller
def destroy
thisAula = Aula.find(params[:id])
#seminar = Seminar.find(thisAula.seminar_id)
#Remove student from seating chart
seating = #seminar.seating
seating.delete(thisAula.student_id)
#seminar.update(:seating => seating)
puts "In Controller"
puts #seminar.seating
thisAula.destroy
#Redirect
flash[:success] = "Student removed from class period"
redirect_to scoresheet_url(#seminar)
end
Once I fix this error, I would like to include the seating chart count within the test, like so:
assert_difference ['Aula.count','#student.aulas.count', '#seminar.students.count', '#seminar.seating.count'], -1 do
But the test fails there, because the seating array is not decreasing its count.
I've tried those "puts" lines to figure out what is happening.
Thank you in advance for any insight.
-Jeff
Running the test puts:
In Controller
614857506
In Test
45061424
614857506
This shows that the student's id is deleted from the seating array like it should be. But when the program returns to the test, the seating array has reverted to its original form, which includes the id of the deleted student.

Got it. It was a case of needing to reload the model.
#seminar.reload

Related

ActiveRecord Grouping posts created by the same user in the past hour

I have a Story model. A story is created by a user, and those stories go into a feed. If a user creates more than one story within an hour, I want to only show one story, so my question is how do I write an ActiveRecord query to group by created_at to the hour, and user_id?
So this turned out to be pretty easy.
Story.find_each.group_by do |story|
[story.user_id, story.created_at.beginning_of_hour]
end
then you can map over the results and do whatever. I checked if a group had more than one object in it, and if it did, created a new Story, inserted the info for all the stories I wanted to group, and then added it to an array of stories. I never save the new story, so I don't end up with a bunch of weird stories saved.
If anyone has a better way to do this, I'm open to suggestions.
Here is the entire method:
def self.smart_feed
feed = []
self.find_each(batch_size: 10).group_by do |story|
[story.user_id, story.created_at.beginning_of_hour]
end.map do |stories|
if stories.second.count > 1
temp_story = self.new()
stories.second.map do |story|
if story.ratings.any?
temp_story.ratings.append(story.ratings)
elsif story.flights.any?
temp_story.flights.append(story.flights)
end
temp_story.description = "Added #{temp_story.ratings.size} new ratings and #{temp_story.flights.size} new flights"
temp_story.user_id = story.user_id
end
feed.append(temp_story)
else
feed.append(stories.second.first)
end
end
return feed.reverse
end

How to get random ids, that are not on X list

So I have a code snippet that essentially finds a random List, and prints it out. However in the loop it also saves that id into a table called statuses.
Now I want to be able to go through that list again, and this time print out 150 randoms. However, this time I want it to check through statuses first to make sure that List item haven't been printed out before.
Here's the code I have now:
class ScheduleTweets
#queue = :schedules_queue
def self.perform(user_token, user_id)
client = Buffer::Client.new(user_token)
user = user_id
list = List.all.sample(150)
profiles = client.profiles
profile_ids = profiles.map(&:id)
list.each do |list|
list.statuses.create(:user_id => user)
client.create_update(body: {text: "#{list.text}", profile_ids: profile_ids })
end
end
end
If I were to guess I should add something after List.all.sample(150) where it checks if the list has a list_id that is present in Status.
Gather the list items from the statues table and make an array with that result.
stat_ids = Status.all.collect{|s| s.list_id}
now loop through the random 150 list and check the list id contains in stat_ids or not.
random_lists.each do |rl|
if stat_ids.include?(rl.id)
//do something
end
end
As I don't know your database architecture, I assumed it as above.

Why do i need to reload this CollectionProxy in the rails console but not when testing?

I've been creating a cart feature in rails and I have the following models:
Cart:
class Cart < ActiveRecord::Base
has_many :items
end
Item:
class Item < ActiveRecord::Base
belongs_to :cart
belongs_to :product
end
An item also has a quantity attribute.
Now, I have an instance method on cart that given an item will either a) save the item to the database and associate it with the cart or b) if the item with the product_id already exists, simply update the quantity.
The code for this is below:
def add_item(item)
if(item.valid?)
current_item = self.items.find_by(product_id: item.product.id)
if(current_item)
current_item.update(quantity: current_item.quantity += item.quantity.to_i)
else
self.items << item
end
self.save
end
end
And this works fine.
However, I wanted to test this in the console so i opened up the console in sandbox mode and ran the following commands:
cart = Cart.new #create a cart
cart.add_item(Item.new(product: Product.find(1), quantity: 5)) #Add 5 x Product 1
cart.items #lists items, I can see 5 x Product 1 at this point.
cart.add_item(Item.new(product: Product.find(1), quantity: 3)) #Add 3 x Product 1
cart.items #lists items, still shows 5 x Product 1, not 8x
cart.items.reload #reload collectionproxy
cart.items #lists items, now with 8x Product 1
Here i create a cart, add a purchase of 5 x Product 1 and i can see this in the cart.items. If then add another purchase of 3 x Product 1, the cart.items still lists the purchase as 5 x Product 1 until i manually reload the collection proxy.
I can add more products and these will show up, it is just when updating an existing one, it does not update the collection.
I have tests around this method too which pass.
before(:each) do
#cart = create(:cart)
#product = create(:product)
#item = build(:item, product: #product, quantity: 2)
end
context "when the product already exists in the cart" do
before(:each) {#cart.add_item(#item)}
it "does not add another item to the database" do
expect{#cart.add_item(#item)}.not_to change{Item.count}
end
it "does not add another item to the cart" do
expect{#cart.add_item(#item)}.not_to change{#cart.items.count}
end
it "updates the quantity of the existing item" do
#cart.add_item(#item)
expect(#cart.items.first.quantity).to eq 4
end
end
context "when a valid item is given" do
it "adds the item to the database" do
expect{#cart.add_item(#item)}.to change{CartItem.count}.by(1)
end
it "adds the item to the cart" do
expect{#cart.add_item(#item)}.to change{#cart.items.size}.by(1)
end
end
What i want to know is, why do i have to reload the CollectionProxy when I use this method in the console?
Association caches the results of the query to achieve better performance. When you call #cart.item for the first time it will call the db to get all the items associated with given cart and it will remember its output (in internal variable called 'target'), hence every time you call it after this initial call it will give you same results without calling db at all. The only way to force it to go again to db is to clear that target variable - this can be done with reload method or passing true to association call #car.items(true).
The reason why you don't need to reload association in you rspec tests is because you are not calling items on any object twice. However if you write test like:
it 'adds an item if it is not in the cart' do
before_count = #cart.items.size # size forces the association db call
#cart.add build(:item)
after_count = #cart.items.size # items has been fetched from db, so it will return same set of results
after_count.should_not eq before_count
end
That test will fail, since you are calling items twice on the same object - and hence you will get same results. Note, that using count instead of size will make this test to pass, because count is altering the SQL query itself(which results are not being cached) while size is being delegated to the association target object.

rspec controller testing after create action has completed

I'm having trouble testing this code in rspec - based on the error the test gives me, I know the test is written (more or less) correctly - as the data it's expecting is correct, it's just not getting it for some reason. I should also note that the code works in the browser.
edit: apologies if this was unclear. In this controller (evaluations_controller), the user iterates through each student in a given group and evaluates their progress against a set of goals. In the new action, #student = groups.student.first - when evaluation data for that student has been saved successfully in the create action, the student_id incremented by 1, and the new student_id is passed to the new action again (so the next student can be evaluated) - this loops until there are no more students.
What I'm trying to test is that the student_id is being successfully incremented after evaluation has been saved in the create action.
Code:
def create
...
if #evaluation.save
#id = params[:student_id]
#id = #id.to_i + 1
redirect_to evaluate_path({ student_group_id: #student_group, student_id: #id})
else
...
end
end
Rspec test:
it "should load the next student" do
#set #id to current student.id +1
#id = #student.id
#id = #id.to_i + 1
#post :create
post :create, {student_group_id: #student_group, student_id: #student, evaluation: #attr}
controller.params[:student_id].should eql #id
end
Error:
Failure/Error: controller.params[:student_id].should eql #id expected: 2 got: "1"
Your code appears to be flawed, and consequently your test is not clear.
From gleaning over the code, I understand you want to use some type of next/previous student functionality. It appears you are hacking around your controller test to achieve that.
if #evaluation.save
#id = params[:student_id]
#id = #id.to_i + 1
You are manually calculating the next id. Ask yourself this: What happens if you are working with student.id 1, and you run this calculation, but student.id 2 has been deleted?
You get an ActiveRecord error.
You need a better way of pulling the next student. You should add an instance method to your Student model to handle that for you:
def next
Student.where(id: id).order("id ASC").first
end
In your controller you can move to the next student as such:
redirect_to evaluate_path({ student_group_id: #student_group, student_id: #student.next.id})
Then your test should be much simpler.

How to insert predictable IDs for testing using ActiveRecord

I am trying to do some Cucumber testing like the following:
Given I am logged in as admin
When I go to the article page
Then I should see "Edit Article"
And I should see "Article Title"
where the path to the article page is "articles/1" or some known id.The problem is that when I insert my data using my step definition
Article.create(:name => "Article Title")
I can't know ahead of time what the id will be when the record is inserted.
Essentially, I either need
a) to be able to insert predictable ids. I've tried the method mentioned in the answer of this question but it doesn't seem to work for me - it always inserts a different id.
or
b) write my cucumber steps in such a way that I can pass in a parameter for my Article id
All Cucumber tutorials I've seen sort of gloss over this type of scenario - it's always going to the list of all Articles or whatever, never a specific detail page.
Any help would be appreciated. Thanks!
How about navigating to the article page in the way a user would do it? Your step
When I go to the article page
could be changed to
When I go to the page for the "Article Title" article
and your step definition goes to an article overview, finds a link with "Article Title" and navigates to the page that way.
You get more test coverage, and don't have to worry about IDs.
There are two ways to create predictable ID values. The first is to manually set the ID on create:
model = Model.new(options)
model.id = DESIRED_ID
model.save
The catch here is that options passed to either new, create, or update_attributes will not affect the id value which is why the call to model.id is, unfortunately, required.
An alternative is to reset the whole table before running your tests. This can be altered to account for fixtures if required. For example:
class Model < ActiveRecord::Base
def self.reset!
connection.execute("DELETE FROM #{table_name}")
connection.execute("ALTER TABLE #{table_name} AUTO_INCREMENT=1")
end
end
This will set the ID of the first row inserted to be a predictable 1.
How about making "Go to the Article Page" go to "/articles/#{Article.first.id}"
This is what I use:
# feature
Scenario: creator archives fragment
When fragment 1 exists
And I am the fragment owner
And I am on fragment 1
#steps
def create_fragment(attribs)
force_id = attribs[:force_id]
attribs.delete(:force_id) if force_id
fragment = Fragment.create!({ :owner_id => #current_user.id, :originator_id => #current_user.id}.merge(attribs))
if force_id
fragment.id = force_id.to_i
fragment.save!
end
fragment
end
When /^fragment (.*) exists$/ do |frag|
#fragment = Fragment.find_by_id(frag)
#fragment.destroy if #fragment
#fragment = create_fragment(:force_id => frag.to_i,:description => "test fragment #{frag}",:narrative => "narrative for fragment #{frag}")
end
# paths.rb
when /fragment (.*)/
fragment_path($1)
Have fun.

Resources