Let's imagine if that I have 5 User records in the db, one of those 5 records is a user with age attribute 30.
And that I want to find or create two users with age attribute 28 and 30.
If I do this :
User.where(age: [28, 30])
This will give me a single user which has age 30, but it won't give me back the user with age 28 (doesn't exist).
Or if I do this
User.where(age: 28).where(age: 30)
There is no record with both ages present. Let's imagine age is just an arbitrary attribute, and the age value is an arbitrary value which can be anything.
How would I get all the records based on certain attributes/values combination and if they don't exist yet, create them?
You can actually use a range on the where conditions:
User.where(age: (28..30))
This will work for finding. To create a new user with the age of missing values I believe you will need to code it by hand.
If you want a range, you can just put a raw SQL statement in the where clause, like this:
User.where("age >= 28 AND age <= 30")
If you want a specific set of ages, you can use a list in the where clause:
unless User.where("age IN (28,30)").length > 0
# Code to create user with the age you want
end
Finally, there's also the first_or_initialize and first_or_create methods, which can be joined to where clauses. If no record is found from the where search, one is initialized/created.
User.where("age IN (28,30)").first_or_initialize
Rails 4 has a similar find_or_create_by method.
Related
I do the following so I am able to group all LineItem's together by count and display the LineItem by count along with the vendor_name
line_items = LineItem.all
vendor_line_items = line_items.group(:vendor_name).select('COUNT(*) as count', 'vendor_name').order('count desc')
My issue is that I am only able to receive the following params: id: nil, vendor_name: "name_here"
Is there a way to accomplish the same thing but allow all params from the model to be passed?
You can't select the rest of the columns since you have different values for each coulmn inside the group (like... if you have 2 LineItem in the same group, which ID do you expect to have?)
You could apply aggregate functions (like COUNT, MAX, MIN, etc) to other columns on the SELECT to tell the database which columns you want for each column I guess.
Personally, I would first get the groups ordered by count and then do more queries when needed to fetch the actual record for the groups.
counts = LineItem.group(:vendor_name).count
# counts should be something like: {vendor_1: X, vendor_2, Y, vendor_3: Z}
# order the vendors using the count for each vendor
ordered_vendors = counts.keys.sort_by { |ven| counts[ven] }
ordered_vendors.each do |vendor|
# do something with each vendor, fetch LineItems, etc
end
The reason why you only see the count and the vendor name is because that is all you are grouping by. Suppose in the database, you have 5 different Vendor A shown below.
vendor_name | product_name
-----------------------------
Vendor A | test
Vendor A | test2
Vendor A | test3
Vendor A | test4
Vendor A | test5
...
When you run your query, SQL will not know what to display for product_name as the group_by will only show 1 row instead of 5. Have a read about it here.
To achieve this you will need to either to group by the other columns too or use a min/max select to pick a value to display. Here is an example:
vendor_line_items = LineItem.select('COUNT(*) AS count', 'vendor_name', 'MAX(product_name)').group(:vendor_name).order('count DESC')
Now each of those results, you can call the attributes method.
Which will give you the following hash:
vendor_line_items.each do |x|
result = x.attributes
# Here result will be a hash.
# {"count" => 5, "vendor_name" => "Vendor A", "product_name" => "test5"}
end
(Not accepted answer unless a better way is received)
I did:
vendor_line_items = Vendor.joins(:line_items).group(:id).order('COUNT(line_items.id) DESC')
This gives me what I want by ordering the results by vendor.line_items.count and allowing me to get all of the associations to display any param I want.
I assume this way is much slower than what I was previously doing as it fetches all records and then on the front end goes through associations to get more records.
In the original way I was doing this. It is what I want minus an extra parameter that I would want the SUM of. The parameter is a decimal attribute. In the same way I count the LineItem that have the same vendor_name, I want to sum of the LineItem.attribute that share the same vendor_name.
Better Answer:
LineItem.select(:vendor_name, 'sum(line_item_revenue) as line_item_revenue', 'COUNT(*) as count').group(:vendor_name)
This seems to get me what I want with less queries (i believe) --- correct me if I am wrong on the queries.
I am quite confused about your code and your expectation. You are selecting the COUNT but the expected result is id instead of count?
If you want to group by vendor_name and show the count of group_by you can try
line_items.group(:vendor_name).count
I have a model with following columns
Charges Model
Date
fee
discount
Data
1/1/15, 1, 1
1/1/15, 2, 1
2/2/15, 3, 3
I have a few named scopes like this_year
I want to do something like Charges.this_year.summed_up
How do I make a named scope for this.
The returned response then should be:
1/1/15, 3, 2
2/2/15, 3, 3
Assuming you have a model with a date field(eg. published_at) and 2 integer fields(eg. fee, discount). You can use "group" method to run GROUP BY on published_at. Then just use sum method if you want only sum of one fields. If you want more than one field, you have to run a select with SQL SUMs inside, to get multiple column sums. Here is an example.
Charge..group(published_at)
.select("published_at, SUM(fee) AS sum_fee, SUM(discount) AS sum_discount")
.order("published_at")
Note: Summarized fields won't show up in rails console return value prompt. But they are there for you to use.
Depending upon what end result you want, you may want to look at .group(:attribute) rather than .group_by:
Charge.group(:date).each do |charge|
charge.where('date = ?', charge.date).sum(:fee)
charge.where('date = ?', charge.date).sum(:discount)
end
I found this approach easier, especially if setting multiple conditions on the data you want to extract from the table.
In any case, I had an accounting model that presented this kind of issue where I needed credit and debit plus type of payment info on a single table and spent a fruitful few hours learning all about group_by before realizing that .group() offered a simple solution.
I try to construct a username based on name given. Since many people are named "John" I need to somehow check for this and create it by following a +1 count.
I'm a bit lost in how to iterate ActiveRecord to find in this case a username called john, then if already exists try john1 and if not available try john2 and so on. I can suppose it would use while but I have no idea how to iterate it for this case.
Any idea how to do this?
In order to not make multiple trips to the database, get a list of users that match the name, check the count of users, append the count to the name, and then check the already loaded collection for the new name.
It would look something like this:
name = params[:name]
users = User.where(User.arel_table[:name].matches("#{params[:name]}%")).to_a
count = users.length
name_without_collision = if count > 0
loop do
break "#{name}#{count}" unless users.any? { |u| u.name == "#{name}#{count}" }
count += 1
end
else
name
end
Since you're calling to_a on the ActiveRecord::Relation, you don't have to worry about accidentally sending multiple queries to the database, since it is being converted to a standard Ruby array.
I want to build functionality in my Rails application that shows follower trends over time.
Currently, my following methodology involves creating and destroying relationship objects - following creates an object with the IDs of the follower and followed and unfollowing deletes that relationship object.
Since the relationship object is deleted upon an unfollow, it's impossible to go back and look at how many followers existed for a followed at any given time.
To solve this, the best solution I can think of is this:
Instead of deleting a relationship object upon unfollowing, create a new object with a negative value of, say, -1. Following would create an object with a positive value of +1. Therefore, adding up the total values for a given pair would yield whether or not they were currently following (1 or 0), while historical trends could also be calculated by adding up the total following values for a given followed.
My question is: Is this the most elegant solution this problem? Is there an easier way to do it? I realize that it's possible to use cron jobs to output a daily number, but that seems like it would duplicate data. Any other suggestions?
Any help would be greatly appreciated!
I would add an active field then instead of deleting the relationship record I would set the record to inactive. Then you'll have to update all of your user facing queries to reflect active = 1. Then you can use the records with active = 0 for reporting purposes. You can also add a deactivated_at field that stores the date that the record was deactivated.
An example scenario would be user 1 follows user 2, follows user 3, follows user 4, un-follows user 2, re-follows user 2, un-follows user 4.
follower_id followed_id active created_at deactivated_at
1 2 0 9/10/2012 9/13/2012
1 3 1 9/10/2012 NULL
1 4 0 9/10/2012 9/17/2012
1 2 1 9/16/2012 NULL
just use paranoia
https://github.com/radar/paranoia
class Relationship < ActiveRecord::Base
acts_as_paranoid
...
end
(if you have a unique index over the two numeric ID columns, remove it, use a plain index)
Then you can have
def currently_following_count(uid)
Relationship.where(:followed_id => uid).count
end
def historical_following_count(uid)
Relationship.unscoped.where(:followed_id => uid).count
end
I'm just trying to increment a record by 1 starting at 2000, when a new record is created upon clicking on the create action to create a record:
if resource_model == Student then #resource.testing_id = id + 2000 end
So if the record has an id of 1, I assume that the testing_id will be 2001. But instead it returns:
2147483647 (maximum mysql limit?)
Any suggestions on how to address this? Thanks.
You can't know record ID during create. ID is known after saving record do database.
You can't relay on ID to give you values like 1, 2, 3 ... and so on.
Don't store value like ID+2000, becouse you can get it at any time by calculating id+2000.
You can get next testing_id by something like this:
if resource_model == Student then
#resource.testing_id = Student.first(:order => "testing_id DESC").testing_id + 1
end
But if two processes at the same time will fetch the same value then you will have duplicate testing_id.
Object.idf is a (deprecated) method that returns the Ruby object ID, which uniquely identifies the object. Rails has overridden id so that in models it refers to the database primary key. I'm going to take a guess that your code snippet is from a controller, and that's why id is returning a strange and large number: It's the Ruby object id of the controller, not the primary key of the object. Give id a receiver, e.g. #resource.id, and see how that works.
2147483647 = 2 ^(32-1)
Could you show some of your code here?
From what i'm guessing here is that you are comparing apples with strawberries :P.
I think you are adding a "1" on a string so that means for example:
2000 + 1 = 20001
20001 + 1 = 200001
So if you do that a couple of times this means you will get to the maximum size of an int (21475483647). I don't know this for a 100% sure, but this has to do with the way you ask your question and the way you don't post code etc...
I hope this edit was helpfull tho.