Nil Values When Iterating Through Arrays and Some Really Weird Results - ruby-on-rails

I'm a rails newbie and am building an app. My current problem is trying to find the average time since the last purchase for each customer of an online store using the app, where we have data about their orders and their customers. The problem is that I'm getting an error that says "undefined method `src_created_at' for nil:NilClass." Right now I'm trying to do this only for customers that have purchased once, and leaving aside those that purchased multiple times.
Here's my code:
#customers = Customer.where(:identity_id => #identity.id)
#single_order_customers = #customers.where("orders_count = ?", 1)
days_since_array = []
#single_order_customers.each do |s|
one_order = Order.where(:identity_id => #identity.id, :src_customer_id => s.src_id)
the_date = one_order[0].src_created_at
purchase_date = the_date.to_date
days_between = (Date.today - purchase_date)
days_since_array << days_between
end
days = days_since_array.inject(:+)
#adslp = days / days_since_array.count
Thanks in advance. I can provide what customer and order data looks like if necessary. Any advice would help, even though I know this question is somewhat vague. I've tried some kind if and unless statements validating presence or nil values and they're not working.
Edit:
Here's what's run in the console:
Order Load (123.0ms) SELECT "orders".* FROM "orders" WHERE "orders"."identity_id" = 2 AND "orders"."src_customer_id" = '114863554'
NoMethodError: undefined method `src_created_at' for nil:NilClass
(The above is several orders successfully run and then breaking on the last I've shown.)
Last point: when I try, specifically, to find nil values for this purpose I don't find any.
Order.where(:identity_id => 2, :src_created_at => nil)
Order Load (209.6ms) SELECT "orders".* FROM "orders" WHERE "orders"."identity_id" = 2 AND "orders"."src_created_at" IS NULL
=> []

For your last point where you tried to find nil values, you are getting an empty array [] because your query returned an empty set (no records matched your query). Trying to access any index on an empty array in rails won't throw an IndexOutOfBoundsException, but will instead just return nil, which is where you are getting your nil from. You are expecting one_order[0] to be an order item, but it is instead nil.
The solution to your problem would be to make sure the Order you are searching for already exists in the database. You might want to check how your orders are being created, e.g. if you use Order.create(params[:order]), do you have any validations that are failing, etc. You can check the orders you have through the rails console; just run Order.all. There should be an Order that has an identity_id of 2 and src_created_at of nil for the last query you wrote to return an actual order in the set.
A quick fix for now would be to remove the extra query to set one_order and just get it from the customer:
...
#single_order_customers.each do |s|
one_order = s.orders.first
the_date = one_order.src_created_at
...
The extra query does not seem to be necessary. You have already filtered #customers by identity_id, and src_customer_id will definitely match s.src_id if you get orders by calling s.orders. Relationships should be set up correctly in the models so that a customer has_many orders. You mentioned that you are just starting with rails; I would highly recommend reading a tutorial for rails first to save yourself headaches like these in the future :)

Related

How to perform atomic increments in rails [duplicate]

What's the best way to implement an atomic insert/update for a model with a counter in Rails? A good analogy for the problem I'm trying to solve is a "like" counter with two fields:
url : string
count : integer
Upon insert, if there is not currently a record with a matching url, a new record should be created with count 1; else the existing record's count field should be incremented.
Initially I tried code like the following:
Like.find_or_create_by_url("http://example.com").increment!(:count)
But unsurprisingly, the resulting SQL shows that the SELECT happens outside the UPDATE transaction:
Like Load (0.4ms) SELECT `likes`.* FROM `likes` WHERE `likes`.`url` = 'http://example.com' LIMIT 1
(0.1ms) BEGIN
(0.2ms) UPDATE `likes` SET `count` = 4, `updated_at` = '2013-01-17 19:41:22' WHERE `likes`.`id` = 2
(1.6ms) COMMIT
Is there a Rails idiom for dealing with this, or do I need to implement this at the SQL level (and thus lose some portability)?
You can use pessimistic locking,
like = Like.find_or_create_by_url("http://example.com")
like.with_lock do
like.increment!(:count)
end
I am not aware of any ActiveRecord method that implemts atomic increments in a single query. Your own answer is a far as you can get.
So your problem may not be solvable using ActiveRecord. Remember: ActiveRecord is just a mapper to simplify some things, while complicating others. Some problems just solvable by plain SQL queries to the database. You will loose some portability. The example below will work in MySQL, but as far as I know, not on SQLlite and others.
quoted_url = ActiveRecord::Base.connection.quote(#your_url_here)
::ActiveRecord::Base.connection.execute("INSERT INTO likes SET `count` = 1, url = \"#{quoted_url}\" ON DUPLICATE KEY UPDATE count = count+1;");
Here is what I have come up with so far. This assumes a unique index on the url column.
begin
Like.create(url: url, count: 1)
puts "inserted"
rescue ActiveRecord::RecordNotUnique
id = Like.where("url = ?", url).first.id
Like.increment_counter(:count, id)
puts "incremented"
end
Rails' increment_counter method is atomic, and translates to SQL like the following:
UPDATE 'likes' SET 'count' = COALESCE('count', 0) + 1 WHERE 'likes'.'id' = 1
Catching an exception to implement normal business logic seems rather ugly, so I would appreciate suggestions for improvement.
Rails supports Transactions, if that's what you're looking for, so:
Like.transaction do
Like.find_or_create_by_url("http://example.com").increment!(:count)
end
My favorite solution thus far:
Like.increment_counter(
:count,
Like.where(url: url).first_or_create!.id
)
For this use case and others, I think update_all approach is cleanest.
num_updated =
ModelClass.where(:id => my_active_record_model.id,
:service_status => "queued").
update_all(:service_status => "in_progress")
if num_updated > 0
# we updated
else
# something else updated in the interim
end
Not exactly you example, just pasted from the linked page. But you control the where condition and what is to be updated. Very nifty

Rails 4 - Am I causing excessive Active Record queries?

I'm a newer dev, and I want to know if I'm causing excessive queries with an index method.
The index method for my Ownerships model looks like this:
def index
#ownerships = Ownership.all.select { |t| t.player_id == nil}
Rails.application.config.counter = (Ownership.all.count - Ownership.all.select{ |t| t.player_id == nil}.count) + 1
end
It works fine. The goal is to set the counter equal to the id of the first Ownership that does not have a player assigned. But I'm concerned that I'm causing 3 separate queries every time the index action is called. Is that the case?
I know I can find the first record with 'nil' Player like this:
Ownership.all.select { |t| t.player_id == nil}.first(1)
Is there a simple way to set the counter equal to the ID of the record that is found?
Thank you.
Using Ruby in AR is inefficient and I suggest using it as a last resort.
Always try to get to the database layer.
The following AR query
Ownership.find_by(player_id: nil)
fires single SQL query:
SELECT `ownerships`.*
FROM `ownerships`
WHERE `ownerships`.`player_id` = NULL
LIMIT 1
and does exactly what you need.
Be aware, that with find_by the following would result in NoMethodError, if no record found
Ownership.find_by(player_id: nil).id
#=> NoMethodError: undefined method `id' for nil:NilClass
Take a look into finders docs for more info on AR querying.
You are selecting all existing Ownerships and then process them in Ruby - which is very inefficient. What if you got 10k Ownerships? Or 100k? Not only the query to database is too heavy, but also Ruby can exhaust the memory if collection is too big.
What you might want to do here is using SQL to find all needed records.
In Rails, you can get SQL generated for you by ActiveRecord queries, like this:
#ownerships = Ownership.where(player_id: nil)
or
#ownership = Ownership.find_by(player_id: nil)
to find the first matching one.

active record how do I specify a column after a join?

I'm trying to make an sql query using activerecord and I'm having a hard time specifying a specific column from multiple joined tables.
for instance in sql
select go.id, sequence.name, sequence.id from sequence join (goterms,...) on ...
this is not beautiful sql but my point is that I'm able to specify which .id I want returned
in activerecord I'm doing this:
results = Sequence.joins(:Foreigndb,:Goterm,:Taxa)
.select(:header,:taxaclass, :genus, :interpro_desc,:description,:dbname,:read_depth, :name)
.distinct
I want to be able to get id from :Goterm but :Taxa and :Foreigndb also use id as a column in the database so i'm getting uninformative errors that I assume stem from this issue when I do the following.
results = Sequence.joins(:Foreigndb,:Goterm,:Taxa)
.select(:header,:taxaclass, :genus, :interpro_desc,:description,:dbname,:read_depth, :name,:id)
.distinct
What is the correct way to just specify that I want Goterm.id?
edit - Here is the error:
ActiveRecord::StatementInvalid: Mysql2::Error: Unknown column 'Goterm.id' in 'field list'
when I run:
results = Sequence.joins(:Foreigndb,:Goterm,:Taxa).select(:header,:taxaclass,:genus, :interpro_desc,:description,:dbname, :read_depth, :name,'Goterm.id').limit(5).offset(0).dresults = Sequence.joins(:Foreigndb,:Goterm,:Taxa).select(:header,:taxaclass, :genus, :interpro_desc,:description,:dbname, :read_depth, :name,'Goterm.id').limit(5).offset(0).distinct
results = Sequence.joins(:Foreigndb,:Goterm,:Taxa).select(:header,:taxaclass, :genus, :interpro_desc,:description,:dbname,:read_depth, :name, 'sequences.id')
.distinct
It turns out that ilan's answer is correct, however be sure that everything is lower case. I was using 'Goterm.id' to make the selection when it needs to be 'goterm.id'
If anyone else runs into this, I also ran into difficulties grabbing the goterm.id data out of the returned query objects. Each time I called object.id on that return set it would give me something different from what I was expecting. I think the attribute I was expecting was being obscured by something else. To get the data I needed I did the following:
results = Sequence.joins(:Foreigndb,:Goterm,:Taxa).select(:header,:taxaclass, :genus,:interpro_desc,:description,:dbname,:read_depth, :name).distinct
firstRes = results[0]
firstRes.attributes['id']

Ruby on Rails - ActiveRecord::Relation count method is wrong?

I'm writing an application that allows users to send one another messages about an 'offer'.
I thought I'd save myself some work and use the Mailboxer gem.
I'm following a test driven development approach with RSpec. I'm writing a test that should ensure that only one Conversation is allowed per offer. An offer belongs_to two different users (the user that made the offer, and the user that received the offer).
Here is my failing test:
describe "after a message is sent to the same user twice" do
before do
2.times { sending_user.message_user_regarding_offer! offer, receiving_user, random_string }
end
specify { sending_user.mailbox.conversations.count.should == 1 }
end
So before the test runs a user sending_user sends a message to the receiving_user twice. The message_user_regarding_offer! looks like this:
def message_user_regarding_offer! offer, receiver, body
conversation = offer.conversation
if conversation.nil?
self.send_message(receiver, body, offer.conversation_subject)
else
self.reply_to_conversation(conversation, body)
# I put a binding.pry here to examine in console
end
offer.create_activity key: PublicActivityKeys.message_received, owner: self, recipient: receiver
end
On the first iteration in the test (when the first message is sent) the conversation variable is nil therefore a message is sent and a conversation is created between the two users.
On the second iteration the conversation created in the first iteration is returned and the user replies to that conversation, but a new conversation isn't created.
This all works, but the test fails and I cannot understand why!
When I place a pry binding in the code in the location specified above I can examine what is going on... now riddle me this:
self.mailbox.conversations[0] returns a Conversation instance
self.mailbox.conversations[1] returns nil
self.mailbox.conversations clearly shows a collection containing ONE object.
self.mailbox.conversations.count returns 2?!
What is going on there? the count method is incorrect and my test is failing...
What am I missing? Or is this a bug?!
EDIT
offer.conversation looks like this:
def conversation
Conversation.where({subject: conversation_subject}).last
end
and offer.conversation_subject:
def conversation_subject
"offer-#{self.id}"
end
EDIT 2 - Showing the first and second iteration in pry
Also...
Conversation.all.count returns 1!
and:
Conversation.all == self.mailbox.conversations returns true
and
Conversation.all.count == self.mailbox.conversations.count returns false
How can that be if the arrays are equal? I don't know what's going on here, blown hours on this now. Think it's a bug?!
EDIT 3
From the source of the Mailboxer gem...
def conversations(options = {})
conv = Conversation.participant(#messageable)
if options[:mailbox_type].present?
case options[:mailbox_type]
when 'inbox'
conv = Conversation.inbox(#messageable)
when 'sentbox'
conv = Conversation.sentbox(#messageable)
when 'trash'
conv = Conversation.trash(#messageable)
when 'not_trash'
conv = Conversation.not_trash(#messageable)
end
end
if (options.has_key?(:read) && options[:read]==false) || (options.has_key?(:unread) && options[:unread]==true)
conv = conv.unread(#messageable)
end
conv
end
The reply_to_convesation code is available here -> http://rubydoc.info/gems/mailboxer/frames.
Just can't see what I'm doing wrong! Might rework my tests to get around this. Or ditch the gem and write my own.
see this Rails 3: Difference between Relation.count and Relation.all.count
In short Rails ignores the select columns (if more than one) when you apply count to the query. This is because
SQL's COUNT allows only one or less columns as parameters.
From Mailbox code
scope :participant, lambda {|participant|
select('DISTINCT conversations.*').
where('notifications.type'=> Message.name).
order("conversations.updated_at DESC").
joins(:receipts).merge(Receipt.recipient(participant))
}
self.mailbox.conversations.count ignores the select('DISTINCT conversations.*') and counts the join table with receipts, essentially counting number of receipts with duplicate conversations in it.
On the other hand, self.mailbox.conversations.all.count first gets the records applying the select, which gets unique conversations and then counts it.
self.mailbox.conversations.all == self.mailbox.conversations since both of them query the db with the select.
To solve your problem you can use sending_user.mailbox.conversations.all.count or sending_user.mailbox.conversations.group('conversations.id').length
I have tended to use the size method in my code. As per the ActiveRecord code, size will use a cached count if available and also returns the correct number when models have been created through relations and have not yet been saved.
# File activerecord/lib/active_record/relation.rb, line 228
def size
loaded? ? #records.length : count
end
There is a blog on this here.
In Ruby, #length and #size are synonyms and both do the same thing: they tell you how many elements are in an array or hash. Technically #length is the method and #size is an alias to it.
In ActiveRecord, there are several ways to find out how many records are in an association, and there are some subtle differences in how they work.
post.comments.count - Determine the number of elements with an SQL COUNT query. You can also specify conditions to count only a subset of the associated elements (e.g. :conditions => {:author_name => "josh"}). If you set up a counter cache on the association, #count will return that cached value instead of executing a new query.
post.comments.length - This always loads the contents of the association into memory, then returns the number of elements loaded. Note that this won't force an update if the association had been previously loaded and then new comments were created through another way (e.g. Comment.create(...) instead of post.comments.create(...)).
post.comments.size - This works as a combination of the two previous options. If the collection has already been loaded, it will return its length just like calling #length. If it hasn't been loaded yet, it's like calling #count.
It is also worth mentioning to be careful if you are not creating models through associations, as the related model will not necessarily have those instances in its association proxy/collection.
# do this
mailbox.conversations.build(attrs)
# or this
mailbox.conversations << Conversation.new(attrs)
# or this
mailbox.conversations.create(attrs)
# or this
mailbox.conversations.create!(attrs)
# NOT this
Conversation.new(mailbox_id: some_id, ....)
I don't know if this explains what's going on, but the ActiveRecord count method queries the database for the number of records stored. The length of the Relation could be different, as discussed in http://archive.railsforum.com/viewtopic.php?id=6255, although in that example, the number of records in the database was less than the number of items in the Rails data structure.
Try
self.mailbox.conversations.reload; self.mailbox.conversations.count
or perhaps
self.mailbox.reload; self.mailbox.conversations.count
or, if neither of those work, just try reloading as many of the objects as possible to see if you can get it to work (self, mailbox, conversations, etc.).
My guess is that something is messed up between memory and the DB. This is definitely a really weird error though, might wanna put in an issue on Rails to see why this would be the case.
The result of mailbox.conversations is cached after the first call. To reload it write mailbox.conversations(true)

How to efficiently retrieve all record in rails 3.2 and assign them to a query-able object?

In our rails 3.2 app, we need to retrieve all customer records out of customer table and assign them to a variable customers and do query (such as .where(:active => true) on variable customers late on. There are 2 questions here:
what's the better way to retrieve all records?
Customer.all works. However according to rails document, it may have performance issue when Customer table gets large. We tried Customer.find_each and it has error "no block given (yield)".
How to make the variable customers query_able?
When performing query on variable customers (like customers.where(:active => true)), there is an error: undefined methodwhere' for #. It seems that thecustomersis an array object and can't takewhere. How can we retrievecustomers` in such a way it can be query-able?
Thanks for help.
In Rails < 4 .all makes database call immediately, loads records and returns array. Instead use "lazy" scoped method which returns chainable ActiveRecord::Relation object. E.g.:
customers = Customer.scoped
...
customers = customers.where(:active => true)
customers = customers.where(...)
etc...
And at the moment when you will need to load records and iterate over them you can call find_each:
customers.find_each do |customer|
...
end

Resources