I want to get the sales from the past 10 weeks. I can do that with this method:
def past_10_sales(yw)
past_sales = []
year_week = yw
10.times do
sales = Sales.where(year_week: year_week.previous)
past_sales << sales unless sales.empty?
year_week = year_week.previous
end
past_sales.flatten
end
But now I need this method somewhere else as well and wanted to put it in the sales.rb model, but i don't know what the best practice of this would be or if there is something in Rails that makes this better? It feels wrong calling Sales.where in the sales.rb model...
Edit:
year_week is a model with the current year and calendar week. So the current year_week would be 202244. Calling year_week.previous gives me 202243. I'm getting all the sales from a specific calendar week.
This works fine btw.
Edit 2:
I have a model sales.rb. in this model i save all the sales. it only matters in which calendar week the sales were made, the exact date does not matter so it looks something like this:
create_table "sales" do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.bigint "year_week_id"
etc...
end
i also have a model year_week which just saves the year and the calendarweek and the id is like this: yearweek (so 202244). there are methods like def previous that return the previous year_week.
What i now want is all the sales form the past 10 weeks. so i call this:
sales = Sales.where(year_week: year_week.previous)
past_sales << sales unless sales.empty?
year_week = year_week.previous
10 times because this way it gets all the sales in the past 10 weeks but it just doesn't feel right and i was wondering if/what better way there is for this.
If you really have to work with a bigint you can do something like:
starting_week = Time.current.advance(weeks: -10).strftime("%Y%V").to_i
Sale.where(
year_week: starting_week..
)
This uses ISO weeks. Otherwise use %U or %W. See DateTime#strftime. Of course using date_trunc('week', sales.created_at) or the equivilent database function would make this column reduntant unless the week isn't actually the same as the calender week its created.
you can do it in the below way:
def past_10_sales(yw)
year_week = yw.previous
past_sales = Sales.where(year_week: (year_week-9..year_week.to_a))
end
Related
Consider this table:
create_table "liquor_lots", force: :cascade do |t|
t.integer "recipe_id"
t.datetime "created_at", precision: 6, null: false
t.integer "counter"
end
And the resulting model
class LiquorLot < ApplicationRecord
def lotcode
"#{recipe_id}#{created_at.strftime("%y")}#{created_at.strftime("%W")}#{created_at.strftime("%u")}"
end
def pallet_lotcode
"#{lotcode}-#{counter}"
end
end
I'd like to do the equivalent of this in SQL:
Select distinct(lotcode) from liquor_lots
I've tried this and it understandably fails because lotcode is not a column on the liquor_lots table. But I've always been advised against adding columns to store data that is derived from data in other columns.
So how do I search for those values?
For context, my lotcode actually consists of many more values concatenated together, I just limited to three in the example for readability.
As far as I know, with basic ActiveRecord you cannot do that.
ActiveRecord would have to know too much about your ruby code.
You could implement a SQL query that concatenates the relevant values by hand (see comment to your question).
Or you can query all objects (or just the relevant values using pluck()) and then work on that with standard Ruby Array/Enumerable methods (in memory). If the application is not performance-critical, happens rarely, and you do not have thousands of the liquor_lots, that would be an okay productivity-tradeoff in my eyes.
Besides storing it in an own column, you could also extract the codes in separate table and make PalletLotcode an entity of its own. LiquorLots would than belong_to a single PalletLotcode which would have_many LiquorLots. But compared to the separate column this is a rather complex operation, but makes sense if other information is to be stored on the Lotcodes.
You can try something like:
LiquorLot.where("recipe_id = :rcp_id AND created_at >= :begin_of_day AND created_at <= :end_of_day", {begin_of_day: calculate_begin_of_day, end_of_day: calculate_end_of_date, rcp_id: id})
calculate_begin_of_day and calculate_end_of_date can be implemented using Date.comercial method and Date.beginning_of_day and Date.end_of_day
For the sake of explanation, I'm writing an app where a User can log their expenses.
In the User's show view, I want to only show the User's expenses from the current month.
My expenses table looks like this:
create_table "expenses", force: :cascade do |t|
t.date "date"
t.string "name"
t.integer "cost"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
end
The date field is in the date format, so looks like: Thu, 14 Apr 2016
In my controller, I've got something like:
def show
month = Date.today.strftime("%m")
#user = User.find(params[:id])
#expenses = Expense.where(:user_id => #user.id, :date => month)
end
Obviously, this isn't going to work, but it will be something along these lines, I'm guessing?
Any help would be great, thanks!
Usually you can tackle it this way:
Expense.where(date: (Date.today.beginning_of_month..Date.today.end_of_month))
Where that defines a range that can be used as a BETWEEN x AND y clause.
If this is a common operation you might want to express the date as a separate column in YYYYMM format so that these are easily retrieved.
If you're using MySQL, you can use the extract function, to create a .where like:
def show
month = Date.today.strftime("%m")
year = Date.today.strftime("%y")
#user = User.find(params[:id])
#expenses = Expence.where('extract(month from `date`) = ? AND extract(year from `date`) = ? AND `user_id` = ?', month, year, #user.id)
end
Havent tested, although it should work.
Sources:
https://dev.mysql.com/doc/refman/5.5/en/date-and-time-functions.html
I am trying to get some relationships in Rails set up and am having some confusion with how to use the ones I have configured.
My scenario is this:
I have a model called Coaster. I wish each Coaster to be able to have 0 or more versions. I wish to be able to find all versions of a Coaster from it's instance and also in reverse.
My models and relationships as they stand:
coaster.rb:
has_many :incarnations
has_many :coaster_versions,
through: :incarnations
incarnation.rb:
belongs_to :coaster
belongs_to :coaster_version,
class_name: "Coaster",
foreign_key: "coaster_id"
Database schema for Incarnations:
create_table "incarnations", force: :cascade do |t|
t.integer "coaster_id"
t.integer "is_version_of_id"
t.boolean "is_latest"
t.integer "version_order"
end
and my code that happens when importing Coasters from my CSV data file:
# Versions
# Now determine if this is a new version of existing coaster or not
if Coaster.where(order_ridden: row[:order_ridden]).count == 1
# Create Coaster Version that equals itself.
coaster.incarnations.create!({is_version_of_id: coaster.id, is_latest: true})
else
# Set original and/or previous incarnations of this coaster to not be latest
Coaster.where(order_ridden: row[:order_ridden]).each do |c|
c.incarnations.each do |i|
i.update({is_latest: false})
end
end
# Add new incarnation by finding original version
original_coaster = Coaster.unscoped.where(order_ridden: row[:order_ridden]).order(version_number: :asc).first
coaster.incarnations.create!({is_version_of_id: original_coaster.id, is_latest: true})
Now all my DB tables get filled in but I am unsure how to ensure everything is working how I want it to.
For example I have two coasters (A and B), B is a version of A. When I get A and ask for a count of it's coaster_versions, I only get 1 returned as a result? Surely I should get 2 or is that correct?
In the same line, if I get B and call coaster_versions I get 1 returned as well.
I just need to ensure I am getting back the correct results really.
Any comments would be highly appreciated as I have been working on this for ages now and not getting very far.
Just incase anyone is going to reply telling me to look at versioning gems. I went this route initially and it was great but the problem there is that in MY case a Coaster and a VERSION of a coaster are both as important as each other and I can't do Coaster.all to get ALL coasters whether they were versions or not. Other issues along the same line also cropped up.
Thanks
First of all, welcome to the wonderful world of history tracking! As you've found, it's not actually that easy to keep track of how your data changes in a relational database. And while there are definitely gems out there that can just track history for audit purposes (e.g. auditable), sounds like you want your history records to still be first-class citizens. So, let me first analyze the problems with your current approach, and then I'll propose a simpler solution that might make your life easier.
In no particular order, here are some pain points with your current system:
The is_latest column has to be maintained, and is at risk for going out of sync. You likely wouldn't see this in testing, but in production, at scale, it's a very valid risk.
Your incarnations table creates a one-master-version-with-many-child-versions structure, which is fine except that (similar to is_latest) the ordering of the versions is controlled by the version_order column which again needs to be maintained and is at risk of being incorrect. Your import script doesn't seem to set it, at the moment.
The incarnations relationship makes it difficult to tell that B is a version of A; you could fix this with some more relations, but that will also make your code more complex.
Complexity. It's hard to follow how history is tracked, and as you've found, it's hard to manage the details of inserting a new version (A and B should both agree that they have 2 versions, right? Since they're the same coaster?)
Now, I think your data model is still technically valid -- the issues you're seeing are, I think, problems with your script. However, with a simpler data model, your script could become much simpler and thus less prone to error. Here's how I'd do it, using just one table:
create_table "coasters", force: :cascade do |t|
t.string "name"
t.integer "original_version_id"
t.datetime "superseded_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
The original_version_id serves the same purpose as your incarnations table does, to link a version back to the original record. The superseded_at column is both usable as an is_latest check and a way to order the versions (though below, I just order by id for simplicity). With that structure, this is my Coaster class:
class Coaster < ActiveRecord::Base
belongs_to :original_version, class_name: "Coaster"
scope :latest, -> { where(superseded_at: nil) }
scope :original, -> { where('original_version_id = id') }
# Create a new record, linked properly in history.
def self.insert(attrs)
# Find the current latest version.
if previous_coaster = Coaster.latest.find_by(name: attrs[:name])
# At the same time, create the new version (linked back to the original version)
# and deprecate the current latest version. A transaction ensures either both
# happen, or neither do.
transaction do
create!(attrs.merge(original_version_id: previous_coaster.original_version_id))
previous_coaster.update_column(:superseded_at, Time.now)
end
else
# Create the first version. Set its original version id to itself, to simplify
# our logic.
transaction do
new_record = create!(attrs)
new_record.update_column(:original_version_id, new_record.id)
end
end
end
# Retrieve all records linked to the same original version. This will return the
# same result for any of the versions.
def versions
self.class.where(original_version_id: original_version_id)
end
# Return our version as an ordinal, e.g. 1 for the very first version.
def version
versions.where(['id <= ?', id]).count
end
end
This makes adding new records simple:
irb> 5.times { Coaster.insert(name: "Coaster A") }
irb> 4.times { Coaster.insert(name: "Coaster B") }
irb> Coaster.latest.find_by(name: "Coaster A").version
(2.2ms) SELECT COUNT(*) FROM "coasters" WHERE "coasters"."original_version_id" = $1 AND (id <= 11) [["original_version_id", 7]]
=> 5
irb> Coaster.original.find_by(name: "Coaster A").version
(2.3ms) SELECT COUNT(*) FROM "coasters" WHERE "coasters"."original_version_id" = $1 AND (id <= 7) [["original_version_id", 7]]
=> 1
Granted, it's still complex code that would be nice to have made simpler. My approach is certainly not the only one, nor necessarily the best. Hopefully you learned something, though!
Good Afternoon,
Feels like a newbie question but I have the following:
:start_datetime t.date
:end_datetime t.date
:length t.integer
On create my end_datetime is nil but I want to get it by adding the length to the start_datetime in order to generate the endtime.
Currently my integer is stored as '30 Mins', 30 and '1 Hour', 60.
Drawn a blank on where I should do this. I'm guessing I need to create it in the model when the booking is created.
If you're going to be using increments of minutes, I think you should start by redefining your datetimes as datetime rather than date.
then do something like this:
#controller
def create
...
end_datetime = params[:start_datetime] + params[:length].minutes
#save this
...
end
Let's say we have the following model.
create_table :meetings do |t|
t.datetime :started_at
t.datetime: ended_at
end
class Meeting < ActiveRecord::base
end
How would I order a meetings_result, so that the longest meeting is the first meeting in the collection and the shortest meeting the last.
Something like
Meeting.order(longest(started_at..ended_at))
Obviously that doesn't work.
How would I achieve this, preferably without using raw SQL?
I don't think you can do it without using raw SQL.
Using Raw SQL:
Meeting.order('(ended_at - start_at) DESC')
(works with PostGreSQL)
No SQL? Two options come to mind. Create an array of hashes and sort it there, or add another column in the db and sort on that.
# How many records in the meetings table? This array of hashes could get huge.
meetings_array = []
Meeting.all.each do |meeting|
meetings_array << {id: meeting.id, started_at: meeting.started_at, ended_at: meeting.ended_at , duration: meeting.ended_at - meeting.started_at }
end
meetings_array.sort_by { |hsh| hsh[:duration] }
Or, create another column:
# Is it worth adding another column?
create_table :meetings do |t|
t.datetime :started_at
t.datetime :ended_at
t.datetime :duration
end
Update this column whenever you have both started_at and ended_at. Then you can:
Meeting.order("duration")