How to get only one db record from couple of same? - ruby-on-rails

I have records in the db table that can differ only by ids and creation/update time. How can I get only the unique records?
I tried this way, but it didn't work:
msg_to_user = user.messages.new_messages.uniq
I'll explain. User can follow post manually but also same post can be followed by user automatically. So I want to send only one message if post have been commented by someone.
1747 test message TamadaTours 12 new 2016-01-29 06:14:04.736869 2016-01-29 06:48:55.948529 32964382
1748 test message TamadaTours 12 new 2016-01-29 06:14:04.741184 2016-01-29 06:48:55.951371 32964382

All records in the database are uniq (at least because of ID column, which by default has a uniq constraint).
You would want to use DISTINCT:
Model.select('DISTINCT column_name1, column_name2')

Your question is flawed...
The point of having an id... otherwise known as primary_key... in a relational database is so that you can actively identify the unique records you want:
A primary key uniquely specifies a tuple within a table. In order for an attribute to be a good primary key it must not repeat
When you write... "How can I get only the unique records" ... the answer is to pull only the records based on their id.
If you refine your question to what you really want...
I want to send only one message if post have been commented by someone
--
In other words, you want to pull a collection of unique user_ids (no duplicates), to which you can send new messages?
To do this, you can use...
#recipients = Message.select(:user_id).distinct #-> all unique user_ids
If you're trying to pull the "new" messages for a user, but only show the first (if they're the same), you'll want to use something like the following:
#msg_to_user = user.messages.new_messages.uniq(:title)
Ref
A better pattern to implement would be to validate the uniqueness of new messages:
#app/models/message.rb
class Message < ActiveRecord::Base
validates :user_id, uniqueness: { scope: :message_id } #-> replace message_id with other unique identifier
end
This would ensure only one new message is present for a user.

you can also use group by in your query like this
Model.all.group("column_name")

One way to extract the data but get rid of the ORM context.
# maps every record to hash, remove the ID entry and then removes duplicates
msg_to_user = user.messages.new_messages.attributes.map { |e| e.except('ID') }.uniq

Related

Table performance when create a unique token by user

In my User model i have the following method :
def confirmation_token
self.confirmation = loop do
random_token = SecureRandom.urlsafe_base64(16, false)
break random_token unless User.exists?(confirmation: random_token)
end
end
this method will just create a random token to confirm user's email...
as you can see it loop while User.exists?(confirmation: random_token), which means it verify if there is no similar token already in user table.
my question is : if i have for example a lot of rows in "user table", i need to add index in this (confirmation) column for more performance ?
note (this method is executed just once per user ... the first time when user is sign up)
Yes. If you're doing many searches on any particular column (in this case confirmation), you should index that column.
If You are just searching by that value, then short answer was given to You.
Though there are some things to consider. First of all You probably would want that index to be unique, this will improve the performance quite a lot as table grows.
add_index :users, :confirmation_token, unique: true
Also You probably want a unique value instead of just random value. Though it's unlikely that it would generate a duplicate, it's still random, not unique value. One of the options would be generating and SHA using Digest class from some user column that You know is unique for this table, like this:
Digest::SHA1.hexdigest(user.email)
UPD: Asker is concerned about cases if someone would know that he uses email as a key and would use it to generate token.
This usually is solved by appending some unique key to the email before encrypting. You can generate such key with secure random and store it inside environment variable.
In your .bashrc/.profile/.bash_profile or any other You use, do this:
export EMAIL_TOKEN_SECRET="M9SyIuuOPhakX0b6gjvcRnsRHY="
Then do like this:
Digest::SHA1.hexdigest("#{user.email}-#{ENV['EMAIL_TOKEN_SECRET'}")

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)

Rails using a variable find condition

I am using Braintree for managing subscriptions in my Rails app.
I have a Subscription model that stores the braintree customer ID and subscription ID.
I want to filter active subscriptions in my Subscription model. So far I have
def find_active_subscriptions
#active_subscriptions = Braintree::Subscription.search do |search|
search.status.is "Active"
end
But now I want to use the subscription IDs in #active_subscriptions to find all of the objects in my local Subscription model with the same subscription IDs and put that into a variable such as #local_active_subscriptions.
The reason I have to do this is to use the local info to access Braintree::Address and only pull active addresses.
Thanks for the help.
One you have the #active_subscriptions you can collect all of the ids into an array and pass them right into the find method of your local Subscription model. I don't know what attributes you are using here, so I'm just making some guesses:
#active_subscription_ids = #active_subscriptions.collect(&:subscription_id)
#local_active_subscriptions = LocalSubscriptionModel.find(#active_subscription_ids)
I'm not sure what Braintree::Subscription.search returns, but if it's something akin to ActiveRecords, could you use something like:
#local_active_subscriptions = Subscription.where("id IN(?)", #active_subscriptions.map{ |act_subs| act_subs.id })
The .map function should put all the IDs into an array, and then ActiveRecord query would look for all the Subscriptions in your subscriptions table whose ID is in that array.
I'm not certain about mapping on Braintree::Subscriptions; I've never used that.
Edit
Like ctcherry said, you can also do the search with find. And I guess collect is good for mapping the ids into an array too. You could also maybe use #active_subscriptions.map(&:id)

How to select all of a unique attribute

I would like to find all the Records that have unique email addresses. I am trying to perform this :
#uniq_referrals = []
CardReferral.all.select{|a| #uniq_referrals << a.email}
#referral_count = #uniq_referrals.uniq.each{|a|CardReferral.find_by_email(a)}
But I would like to do it in a single call. Is that possible?
You can use:
CardReferral.select("distinct(email), other_field_you_need")
where other_field_you_need it's a list of field name you need to use from the objects you get from ActiveRecord.
To get the count of unique email record you can use:
CardReferral.count("distinct(email)")
I would like to find all the Records that have unique email addresses. I am trying to perform this :
#uniq_referrals = []
CardReferral.all.select{|a| #uniq_referrals << a.email}
#referral_count = #uniq_referrals.uniq.each{|a|CardReferral.find_by_email(a)}
But I would like to do it in a single call. Is that possible?
I think that it would help you:
Model.group("email")
when we group all the record by email id then you will find all the record that have uniq email. if some record have same id then you will get first record.
please prefer to image for more understanding.
If you could not get yet, then i will explain elaborate more.

how to store facebook friends to DB (newbie)

i'm creating a facebook-app for university project and i'm trying to store all my friends in the DB.
By using the API-syntax "me/friends" i get a facebook-respond looking like this:
{"data"=>[{"name"=>"Albert Einstein", "id"=>"11111111"}, {"name"=>"Max Mustermann", "id"=>"222222222"}, {"name"=>"Just Another Name", "id"=>"333333333"}]}
I believe its a json-object, but i'm not sure.
Question: How can i save the data, i need a DB with all the User-IDs of my friends.
Thx!
Edit:
Hey, this is what i have searched for. But i still get an error and don't know why.
My code:
def insert_1
fb_friends = rest_graph.get('me/friends')
fb_friends[:data].each do |f|
#friend = Model.new(:name => f["name"] )
#friend.save
end
end
I get an Heroku error (We're sorry, but something went wrong.)
You have two options -
Option 1-
You can create a friends table which will belong to users table. If a user has 200 friends, it will create 200 entries in friends table all belonging to the user via has_many-belongs_to relationship. For storing data, you just have to iterate over facebook friends hash and then save each of them separately
Pros : You can search for any friend separately.
Cons : There will be so many of friend entries. Saving them will take time, if somebody has many friends(say 500-700). Repeating entries will be created for mutual friends.
Options 2
You can add a friends column in your users table and declare this in your user.rb
serialize :friends
This way, you just have to pass a hash object to friends attribute of user table, and rails will save that in yaml format for you. When you will do #user.friends, rails will again convert that yaml formatted data to hash object and return it.
Pros : There will be only one query to save all users. You can iterate through this hash to show list of all friends.
Cons : You can't update them separately, you will update all together. Not good if you want to store some other information in relation to user's friends.
Update
as per your code example above
fb_friends = #your logic to get data as shown above.
fb_friends[:data].each do |f|
#friend = Friend.new(:name => f["name"],:fb_user_id => f["id"] )#creating Friend model obj.
#friend.save
end

Resources