Need help on active record updating via rails - ruby-on-rails

I had the follow code in a rake file that I will be run weekly.
now = Date.today
Order.where(("status NOT IN ('Completed','Canceled','Shipped') AND DATE(updated_at) <= ?"),(now-30)).update_all("status = '*'",'Pending Requestor')
The problem is it is throwing wrong number of arguments error.
looking at http://apidock.com/rails/ActiveRecord/Base/update_all/class
I tried
now = Date.today
Order.update_all("status = 'Pending Requester'",("status NOT IN ('Completed','Canceled','Shipped') AND DATE(updated_at) <= ?"),(now-30))
but that gives me a 3 for one error.
So what I need to do is I need to find all of the orders where the status is not in that list and the last time they were updated was beyond 30 days ago and automatically put them into a Pending Requester status.
Can someone help me with what I am getting wrong on this?

In your code, what is assigned to the variable now? I'm going to assume it is Time.now.
Also, all of the extra parenthesis you added aren't necessary. I've simplified your query below and wrote it out so that it is easy to understand.
Change your code to:
Order.where(
"status NOT IN (?) AND updated_at <= ?", # Simplifyied the query
%w(Completed Canceled Shipped), # Can also be written as ['Completed', 'Canceled', 'Shipped']
30.days.ago # Self-explanatory
).update_all(status: 'Pending Requestor')
where only takes 1 argument UNLESS the argument contains question marks (?). For each question mark, it receives an additional argument to substitute the question mark with a value.
Bonus: When working with statuses in Rails, I suggest learning the Enumerable convention. It's amazing!

Related

Rails: How to write model spec for this method?

I've just started to take on my first model spec task at work. After writing a lot of feature specs, I find it hard to get into the different perspective of writing model specs (not taking the context into consideration). I'll take a method of the Order model as an example, to explain which difficulties I am experiencing:
def update_order_prices
self.shipping_price_cents = SHIPPING_PRICE_CENTS unless shipping_price_cents
return if order_lines.empty?
self.total_price_cents = calculate_order_price
self.total_line_items_price_cents = calculate_total_order_line_price
self.total_tax_cents = calculate_tax_amount
end
EDIT TL;DR
I am totally happy with an answer that simply writes me a spec for this method. The rest of the post just shows what I tried so far but is not necessary to answer this question.
First approach:
At first I didn't know what to test for. I tried to find out when and where the method was called and to find a scenario where I would know what the attributes that are touched in this method should be equal to. Put short, I spent a lot of time trying to understand the context. Then a coworker said that I should test methods in model specs self-contained, independent from the context. I should just make sure I identify all cases. So for this method that would be:
it sets shipping price cents to default (if not done already)
it returns early if order_lines is empty
it sets values if order_line is set
Current approach:
I tried writing the tests for these points but still questions arise:
Test 1
it 'sets shipping price cents to default (if not done already)' do
order.shipping_price_cents = nil
order.update_order_prices
expect(order.shipping_price_cents).to eq(Order::SHIPPING_PRICE_CENTS)
end
I am confident I got this one right, but feel free to prove me wrong. I set shipping_price_cents to nil to trigger the code that sets it, call the tested method on the cents to be equal to the default value as defined in the model.
Test 2
it 'returns early if order_lines is empty' do
expect(order.update_order_prices).to eq(nil)
end
So here I want to test that the method returns early when there is no object in the order_lines association. I didn't have a clue how to do that so I went into the console, took an order, removed the order_lines associated with it, and called the method to see what would be returned.
2.3.1 :011 > o.order_lines
=> #<ActiveRecord::Associations::CollectionProxy []>
2.3.1 :012 > o.update_order_prices
=> nil
Then did the same for an order with associated order_line:
2.3.1 :017 > o.update_order_prices
=> 1661
So I tested for 'nil' to be returned. But it doesn't feel like I am testing the right thing.
Test 3
it 'sets (the correct?) values if order_line is set' do
order_line = create(:order_line, product: product)
order = create(:order, order_lines: [order_line])
order.update_order_prices
expect(order.total_price_cents).to eq(order.calculate_order_price)
expect(order.total_line_items_price_cents).to eq(order.calculate_order_line_price)
expect(order.total_tax_cents).to eq(order.calculate_tax_amount)
end
I simply test that the attributes equal what they are set to, without using actual values, as I shouldn't look outside. If I wanted to test for an absolute value, I would have to investigate outside of this function which then wouldn't test the method but also status of the Order object etc.?
Running the tests
Failures:
1) Order Methods: #update_order_prices sets (the correct?) values if order_line is set
Failure/Error: expect(order.total_price_cents).to eq(order.calculate_order_price)
NoMethodError:
private method `calculate_order_price' called for #<Order:0x007ff9ee643df0>
Did you mean? update_order_prices
So, the first two tests passed, the third one didn't. At this point I feel a bit lost and would love hear how some experienced developers would write this seemingly simple test.
Thanks
I guess you have to spec against the exact values you are expecting after update_order_prices.
Let's say you set up your order and order lines to have a total price of 10 euros then I'd add the following expectation
expect(order.total_price_cents).to eq(1000)
Same for the other methods. Generally I try to test against specific values. Also as you are relying on the result of a private method you only care about the result.

Need Help: Model is not working

So what I'm doing is I have a rake task that everyday will decrease the days left on a subscription. Here is the rake task:
namespace :delete do
desc 'Remove a day for premium subscription days left'
task :premium_subscription_remove => :environment do
PremiumSubscription.find_each do | premium_subscription|
premium_subscription.premium_subscription_days_left -= 1
premium_subscription.save
end
end
end
This rake task will count down the days left on the subscription. Now I created a new model that will handle the days left once it hit zero. Once it hit zero the code will cause the subscription to auto renew. Here is the code for the renewal:
def self.renew_premium_subscription(user, premium_subscribe)
if premium_subscribe.premium_subscription_days_left <= 0
user.premium_subscriptions.where(:premium_subscribe_id => premium_subscribe.id).destroy_all
if user.points >= premium_subscribe.premium_subscription_cost
user.premium_subscriptions.where(:premium_subscribe_id => premium_subscribe.id).first_or_create
user.points = user.points - premium_subscribe.premium_subscription_cost
user.save
end
end
end
The problem I am having is that the premium_subscription_days_left is at negative two and the renew_premium_subscription has never been acted. I tried putting in random letters and the model hasn't givin an error. How does the model get acted upon inorder for the renewal? I have put code in the controller:
def renew_subscription
PremiumSubscription.renew_premium_subscription(current_user, #user)
end
But that hasn't worked at all. If anybody knows how to get this thing working it will be great. Thank you for the help : )
edit: Tried putting the update function inside of the rake task but that did not work at all.
edit 2: No such luck on getting this fixed. Anybody have a clue?
edit 3: So i though about something, is there a way to automatically call a model class. I just need to get this thing working.
Here is an outline of what I did:
Created a rake task. This rake task will be called with whenever to count down to zero.
I created a model in the premiumSubscription model that says when it hits zero it will either update the subscription or destroy it.
I have set the count down to zero, refreshed the page but the subscription isn't updated or destroyed.
edit 4: So I learned that the controller needs to be triggered by a route. Is there any way to trigger this code when the page loads?
A couple of notes:
user.save
This can fail. If save is unsuccessful it will return false. Your method does not test the return value, so an unsuccessful save will go unnoticed -- nothing is written to the log file, no error is raised, nada. Either check the result of save or call save!, which will raise an exception.
premium_subscription.premium_subscription_days_left -= 1
You might have a good reason for storing this value, but you should also consider storing the start date (or the expiration date) instead, and calculating the days left given the current date. Decrementing "days left" requires that the cron job runs when it is supposed to ... if it misses a day, the count will be off.

Rails ActiveRecord Query Date Range

I'm trying to use the following line in my controller to capture all tasks due less than a week from the current date:
#due_this_week = current_user.tasks.where(due_date: Date.today..1.week.from_now)
For some reason it's not finding any results even I know I have tasks due within four and six days. This is the only instance variable using a range query. I have another one that works fine to find overdue tasks:
#overdue = current_user.tasks.where("due_date <= ?", Date.today)
What am I missing?
Should be:
#due_this_week = current_user.tasks.where(due_date: 1.week.ago..Date.today)
FYI you can also use one sided ranges.
For all calls with due date from 1 week ago
#due_this_week = current_user.tasks.where(due_date: 1.week.ago..)
or for all calls with due date up to today
#due_this_week = current_user.tasks.where(due_date: ..Date.today)
Turns out my controller somehow wasn't set up correctly and was no longer saving the current_user's ID when creating new assignments, which is why they weren't being found. I figured this out using the rails console and running a find on the last few assignments submitted. The user_id was set to nil. Thanks for the assist #mu is too short.

Updating a lot of records frequently

I have a Rails 3 app that has several hundred records in a mySQL-DB that need to be updated multiple times each hour. The actual updating is done through delayed_job which is triggered in controller-logic (checking if enough time has passed since the last update, only then sth. happens).
Each update is slow, it can take up to a second in some cases (although it averages at 3 - 5 updates/sec.).
Code looks like this:
class Thing < ActiveRecord::Base
...
def self.scheduled_update
Thing.all.each do |t|
...
t.some_property = new_value
t.save
end
end
end
I've observed that the execution stalls after 300 - 400 records and then the delayed job just seems to hang and times out eventually (entries in delayed_job.log). After a while the next one starts, also fails, and so forth, so not all records get updated.
What is the proper way to do this?
How does Rails handle database-connections when used like that? Could it be some timeout issue that is not detected/handled properly?
There must be a default way to do this, but couldn't find anything so far..
Any help is appreciated.
Another options is update_all.
Rails is a bad choice for mass data records. See if you can create a sql stored procedure or some other way that would avoid active record.
Use object.save_with_validation(false) if you are ok with skipping validations altogether.
When finding records, use :select => 'a,b,c,other_fields' to limit the fields you want ('a', 'b', 'c' and 'other' in this example).
Use :include for eager loading when you are initially selecting and joining across multiple tables.
So I solved my problem.
There was some issue with the rails-version I was using (3.0.3), the Timeout was caused by some bug I suspect. Updating to a later version of the 3.0.x branch solved it and everything runs perfectly now.

datetime_select with zeroed minutes

I know that I can use the component parts of the date helpers, rather than the full
datetime_select, but I'm not sure how it would work as far as combining the params.
A bit of background, I'm creating an app for traffic monitoring where people can log traffic counts in 1 hour blocks. I'm presenting the user with a datetime_select so they can specify the start of the block, then later I'm calculating the end.
So I don't want people to be able to submit minutes or seconds, well seconds aren't shown with the helper so that's a start.
I've tried to zero it before the record is created with something like:
params[:result]['start(5i)'] = 0
which is the key that the development log shows rails is using for minutes. Unfortunately I get:
undefined method `empty?' for 0:Fixnum
I guess I could do this with some javascript, hide the minutes select box and remove all but the "00" option. I'd rather find a nice, clean solution if I can though.
Grateful for some tips. Happy to provide more information but not sure what else might be of use at the moment.
params are Strings! try this:
params[:result]['start(5i)'] = '0'
or this:
params[:result]['start(5i)'] = ''

Resources