Add to cart created at today rails 3.2 - ruby-on-rails

My db is postgresql and using rails 3.2.13,
the method add_to_cart is not working as expected,
I want to add to the cart created at today or create if it is not created,
but it doesn't create a new one and adds to the cart created at 2-3 days ago sometimes,
what is wrong in my code?
class Cart < ActiveRecord::Base
has_many :line_items
scope :from_today, where("created_at >= ? AND created_at <= ?",
Time.zone.now.beginning_of_day,
Time.zone.now.end_of_day)
def self.add_to_cart(user_id)
cart = Cart.from_today.first_or_create
line_item = cart.line_items.where(user_id: user_id).first_or_initialize
line_item.amount += 1
line_item.save
end
end

Seems like you have "dynamic data" in your scope. Check out this article to understand what's happening.
Your scope is executed at the initialization process but you need it to be executed at the runtime. The workaround is:
To use lambdas in the scope: scope :from_today, -> { where(...) }
Avoid using scopes and use a class method:
def self.from_today
where(...)
end
Also, when you query a range it would be better to pass an array or range as a value: where(created_at: (Time.zone.now.beginning_of_day)..(Time.zone.now.end_of_day))

My first guess would be the format of your dates inside your scope. At the moment it'll return:
Time.zone.now.beginning_of_day
=> Mon, 28 Apr 2014 00:00:00 UTC +00:00
but in a database query you need it in the format:
Time.zone.now.beginning_of_day.to_s(:db)
=> "2014-04-28 00:00:00"
You might also need to surround the time with single quotes - give it a test and see.

Related

Rails: How to sort by calculated date difference?

I have a calculated field in my model as follows.
class Products < ApplicationRecord
attr_reader :days
def days
(Date.today - self.created_at).to_i
end
end
When I try to sort it with, I receive an error.
#products = Product.all.order("days").paginate(:page => params[:page], :per_page => 15)
Error:
PG::UndefinedColumn: ERROR: column "days" does not exist
I will appreciate if anyone can show me how to sort based on a calculated field?
Rails order clause parameter columb should be exist in the table, does not support for user defined custom attribute in the model. So, you have to use the ruby sort_by method for custom defined attributes like below,
Product.all.sort_by(&:days)
And also you have to change your method to like below,
def days
DateTime.now.to_i - self.created_at.to_i
end
It will just work but this is not a best practice to sort the records based on custom user defined custom attribute. So, you have to move this logic to sql query itself like below,
Product.all.order("now() - created_at")
It works on postgres, not sure about mysql, please check alternate in mysql if not working.
the problem for your code above is attr_reader :days, meanwhile days you declared as method not variable
here is my idea for your problem
in your model
class Products < ApplicationRecord
def total_days
(Date.today - self.created_at).to_i
end
def self.sorted_by_days
Product.all.sort_by(&:total_days).reverse
# just delete .reverse incase you want ascending
# I put reverse in case you want to sort descending
end
end
in your controller
#products = Product.sorted_by_days.paginate(:page => params[:page], :per_page => 15)
I am not sure how you are running this piece of code:
(Date.today - self.created_at).to_i
Because it expects a numeric value with the - sign. I was able to do it like this:
((Time.zone.now - self.created_at) / 1.day).to_i
But the main point is I think you want to order the records by created_at date. So by default it is ascending and you want to display the records which has been recently created first so you can do it like this directly:
Product.all.order(:created_at)
and if you want it in descending order then you can do it like this:
Product.all.order(created_at: :desc)
Still if you need to sort it by an attr_reader the other answers show how you can do it. Just the issue will be that the pagination works on ActiveRecord::Collection rather than array so for will_paginate you may refer here:
Ruby on Rails will_paginate an array
Hope this helps.

Ordering Records with Same Date Using Second Field

I have an application with many Galleries. Each gallery has a start_date DateTime field.
For legacy reasons, the time of all start_dates is midnight.
Thu, 10 Jul 2014 00:00:00 UTC +00:00
I need to order the galleries by date, so a user can shuffle back and forth across them using a 'older' and 'newer' link. Galleries are ordered based on start_date:
scope :start_date_ascending, -> { order(start_date: :asc) }
scope :start_date_descending, -> { order(start_date: :desc) }
My problem is that there is no clear older or newer gallery when there are multiple galleries with the same date. In such cases I cannot predict the order that Galleries with the same date are returned in, so moving across multiple galleries with the same date becomes random and error-prone.
I have scope set up to find newer and older galleries:
scope :newer_than, -> (gallery){ where.not(id:gallery).where('start_date >= :gallery_start_date', gallery_start_date:gallery.start_date) }
scope :older_than, -> (gallery){ where.not(id:gallery).where('start_date < :gallery_start_date', gallery_start_date:gallery.start_date) }
And I find the next and previous galleries like this:
def self.next_newer(gallery)
Gallery.newer_than(gallery).start_date_ascending.limit(1).first
end
def self.next_older(gallery)
Gallery.older_than(gallery).start_date_descending.limit(1).first
end
So it seems to me I need a secondary way to order the galleries that are the same date. It doesn't matter what order that is - it could simply be their ids.
How can I handle this situation so that galleries with the same date appear in the query in a predictable, fixed order so that next_newer and next_older move through them?
Perhaps you can sort with a second criteria, name for example and if available, or even id
scope :start_date_ascending, -> { order(start_date: :asc, name: :asc) }
scope :start_date_descending, -> { order(start_date: :desc, name: :asc) }
note on the start_date_descending scope it can be nice to keep name asc, so despite of the descending date order we keep a alphabetical order
And for the next and previous gallery, if you can store an array, you can get your ordered ids and iterate through them
ids = Gallery.start_date_ascending.pluck :id
Based on #BengaminSinclaire's suggestion:
def self.next_newer(gallery)
ascending_ids = Gallery.start_date_ascending.pluck :id
current_index = ascending_ids.index(gallery.id)
Gallery.find(ascending_ids[current_index+1]) if current_index < ascending_ids.length - 1
end
def self.next_older(gallery)
ascending_ids = Gallery.start_date_ascending.pluck :id
current_index = ascending_ids.index(gallery.id)
Gallery.find(ascending_ids[current_index-1]) if current_index > 0
end

Counting User registration for a day, week, month

I want to count the users that registered on the website on specific periods.
For example:
Today: 5
Yesterday: 7
Over last week: 28
Over last month: 101
I used this stackoveflow question that is somewhat relevant to what I want to do. But when I try to apply it it has several problems in terms of logic for what I try to do.
So what I figured out is that I should use something like:
#lastfivedays = User.where(
'created_at >= :five_days_ago',
:five_days_ago => Time.now - 5.days,
)
But where am I placing this and how do I use it in the view?
Yes I am lost on how I do something like this in Rails as I am new to this. Any guidance, tip will be extremely helpful.
Your query should go in the controller and you can then access the instance variable in the view.
Controller:
#lastfivedays = User.where(
'created_at >= :five_days_ago',
:five_days_ago => 5.days.ago,
).count
View:
Number of users who registered in last 5 days: <%= #lastfivedays %>
I would recommend using ActiveRecord Scopes
You could have something like:
class User < ActiveRecord::Base
...
scope :joined_within_five_days, -> { where('created_at >= :five_days_ago',
:five_days_ago => Time.now - 5.days,) }
...
end
Your controller could then use the approriate one. For example
#users = User.joined_within_five_days

find_by_sql in Rails, accessing the resulting array

I'm trying to run a query in a very quick and dirty way in Rails, without putting the rest of the model in place. I know this is bad practice but I just need a quick result in a tight timeframe until I've got the whole solution in place.
I've got items that have a shipping price, based on weight. The weight is stored in the item, the price is stored in the table shipping_zone_prices, and all I currently do is look for the price relating to the first row where the weight is heavier than the item for sale:
class Item < ActiveRecord::Base
def shipping_price
item_id = self.id
shipping_price = ShippingZonePrice.find_by_sql(
"SELECT z.price as price
FROM shipping_zone_prices z, items i
WHERE i.id = '#{item_id}'
AND z.weight_g > d.weight
ORDER BY z.weight_g asc limit 1")
end
end
This sort of works. The SQL does the job, but when plugged into the app as follows:
<%= #item.shipping_price %> Shipping
I get the following displayed:
[#<ShippingZonePrice price: 12>] Shipping
In this example, '12' is the price that is being pulled from the db, and is correct. #item.shipping_price.class returns 'Array'. Trying to access the array using [0] (or any other integer) returns a blank.
Is there another way to access this, or am I missing something fundamental?
Since you are defining an instance method, I think it should return the price if it exists or nil
Try something like this:
def shipping_price
ShippingZonePrice.find_by_sql(
"SELECT z.price as price
FROM shipping_zone_prices z, items i
WHERE i.id = '#{self.id}'
AND z.weight_g > d.weight
ORDER BY z.weight_g asc limit 1").first.try(:price)
end
Then this should work for you:
#item.shipping_price
The first.try(:price) part is needed because find_by_sql may return an empty array. If you tried to do something like first.price on an empty array, you would get an exception along the lines of NoMethodError: undefined method 'price' for nil:NilClass.
This is because find_by_sql returns a model, not data. If you want to do a direct fetch of the data in question, use something like this:
ShippingZonePrice.connection.select_value(query)
There are a number of direct-access utility methods available through connection that can fetch single values, a singular array, rows of arrays, or rows of hashes. Look at the documentation for ActiveRecord::ConnectionAdapters::DatabaseStatements.
As when writing an SQL directly, you should be very careful to not create SQL injection bugs. This is why it is usually best to encapsulate this method somewhere safe. Example:
class ShippingZonePrice < ActiveRecord::Base
def self.price_for_item(item)
self.connection.select_value(
self.sanitize_sql(
%Q[
SELECT z.price as price
FROM shipping_zone_prices z, items i
WHERE i.id=?
AND z.weight_g > d.weight
ORDER BY z.weight_g asc limit 1
],
item.id
)
)
end
end
#item.shipping_price.first.price
or
#item.shipping_price[0].price
Thanks Atastor for pointing that out!
When you use AS price in find_by_sql, price becomes a property of the result.
If not for you saying that you tried and failed accessing [0] i'ld say you want to put
#item.shipping_price.first.price # I guess BSeven just forgot the .first. in his solution
into the view...strange
So, I had a hacky solution for this, but it works great.
Create a table that has the same output as your function and reference it, then just call a function that does a find_by_sql to populate the model.
Create a dummy table:
CREATE TABLE report.compliance_year (
id BIGSERIAL,
year TIMESTAMP,
compliance NUMERIC(20,2),
fund_id INT);
Then, create a model that uses the empty table:
class Visualization::ComplianceByYear < ActiveRecord::Base
self.table_name = 'report.compliance_year'
def compliance_by_year(fund_id)
Visualization::ComplianceByYear.find_by_sql(["
SELECT year, compliance, fund_id
FROM report.usp_compliance_year(ARRAY[?])", fund_id])
end
end
In your controller, you can populate it:
def visualizations
#compliancebyyear = Visualization::ComplianceByYear.new()
#compliancefunds = #compliancebyyear.compliance_by_year(current_group.id)
binding.pry
end
Then, you can see it populate with what you need:
[1] pry(#<Thing::ThingCustomController>)> #compliancefunds
[
[0] #<Visualization::ComplianceByYear:0x00000008f78458> {
:year => Mon, 31 Dec 2012 19:00:00 EST -05:00,
:compliance => 0.93,
:fund_id => 1
},
[1] #<Visualization::ComplianceByYear:0x0000000a616a70> {
:year => Tue, 31 Dec 2013 19:00:00 EST -05:00,
:compliance => 0.93,
:fund_id => 4129
},
[2] #<Visualization::ComplianceByYear:0x0000000a6162c8> {
:year => Wed, 31 Dec 2014 19:00:00 EST -05:00,
:compliance => 0.93,
:fund_id => 4129
}
]

Find and display nearest date in RoR

I am new to ruby on rails and I'm not sure where to start with this. I have a model for users, and one for projects. Users have many projects, and projects have one user. There is an end_date column in the projects table (as well as a name column).
What I want to do is find the project with the nearest end_date and display it's name and end date on the user's show page.
I tried putting this code in the projects controller, but I do not know if it is working, because I don't know how to access it and display the project name in the view.
def next_deadline(after = DateTime.now, limit = 1)
find(:all, :conditions => ['end_date > ?', after], :limit => limit)
end
Any help would be appreciated. Let me know if more information is needed.
As #Dan mentioned, you do need the :order clause to get the first one, but you should add it to your query and not replace the :conditions (otherwise you'll get the project with the earliest end_date irrespective of your after argument). The way you're defining this method is a bit off though. It should be defined in your Project model (and definitely not the controller) as a class method, or, what I think is a better approach, as a scope. In Rails < 3 (which it seems that you're using):
class Project < ActiveRecord::Base
named_scope :next_deadline, Proc.new { |after = DateTime.now, limit = 1| {:conditions => ['end_date > ?', after], :order => "end_date ASC", :limit => limit} }
...
end
Or in Rails >= 3:
class Project < ActiveRecord::Base
scope :next_deadline, Proc.new { |after = DateTime.now, limit = 1| where('end_date > ?', after).order("end_date ASC").limit(limit) }
...
end
Also, you can always test this kind of code using the Rails console: script/console in Rails < 3, rails c in Rails >= 3.
#projects = Project.find_by_sql("SELECT projects.* FROM projects
JOIN users ON users.id = projects.user_id AND projects.user_id = " + #user.id.to_s + "
WHERE projects.end_date > now()
ORDER BY projects.end_date ASC
LIMIT " + limit)
or
#projects = Project.where(:user_id => #user.id)
.where("end_date > ?", DateTime.now)
.order("end_date ASC")
You want to use :order, not :conditions.
Model.find(:all , :order => "end_date ASC")
Then the first result will be the item with the closest end_date
As Dan said, the condition you wrote won't get the nearest end date, but the dates that are greater than today, or the date passed in as a parameter.
In your User model you could write
def next_deadline_project
self.projects.first
end
as long as you give projects a default scope that orders records by end_date
In order to show information on the view you must set it in an instance variable in the User's controller show method. Instance variables are passed to views and you can access them to display the data.
#project = next_deadline_project
And in your show.html.erb you can use something like:
<%= #project.name %> - <%= #project.end_date %>

Resources