Rails Active Record find all including duplicates - ruby-on-rails

How can I get duplicate posts in my view?
I want to be able to do something like this:
#post = post.find(1,2,1)
to return post 1 , post 2 and then post 1 (again).
realize this is a dumb question but I can't find any documentation.

Although I'm not sure about the use case you can do something like:
#posts = Post.find(1,2) << Post.find(1)
of you can define this in your Post model:
def find_with_array(*args)
posts = []
for arg in args
posts << Post.find(arg)
end
posts
end
Obviously the above is inefficient as you are making many SQL calls. If you want it efficient then you can write a code that makes one sql call (but will not return duplicates) and then iterate through the array and rearrange (with copying for duplicates) such as (not fully tested):
def find_with_array(*args)
posts_with_no_duplicates = Post.find(args)
posts_with_duplicates = []
for arg in args
for post in posts_with_no_duplicates
if arg == post.id
posts_with_duplicates << post
end
end
end
end
This one should be better as you are only making one call to DB (normally slowest part) however it's O(N^2) There might be a way to make it O(N) if need be. However It's great improvement from the previous option

Without knowing more detail, here's what I would advise. Take a look at this post regarding checkbox arrays: http://www.skuunk.com/2008/05/checkbox-arrays-in-rails.html
Each checkbox drops a value into a particular params key. This will solve the problem of getting an array with a list of values. Let me know in the comments if this doesn't resolve your particular issue.

Related

ruby on rails iterating params

I have a client that is sending params such as age, gender, name and so on.
I need to retrieve data from the table based on the params, but first I need to check for the presence of the param(to avoid a null param and therefore an empty result). The params are working as filters, so they can be triggered or they can be left blanck.
What I am doing right now is
#retieve = Student.all
unless params[:age].nil?
#retrieve = #retrieve.where(age: params[:age])
end
unless params[:gender].nil?
#retrieve = #retrieve.where(gender: params[:gender])
end
and so on for every param I receive. This way I check if the filter has been selected, and if it has I use the selection as a parameter for the query
It works, but as Ruby is known for the DRY statement, I am pretty sure someone out there knows a better way for putting this and to make this flexible.
Thank you for whatever answer or suggestion you will provide!
This will work best if all of these filters were in a subhash of params that you can iterate over without including unwanted parameters (eg the :action and :controller parameters that rails adds)
Once you've done that you could do
(params[:filters] || {}).inject(Student.all) do |scope, (key, value)|
scope.where(key => value)
end
There's a few ways to do this sort of thing and you have options for how far you want to go at this stage.
Two big things I'd consider -
1) Make nice scopes that allow you to send a param and ignore it if it's nil. That way you can just append another scope for each param from the form and it will be ignored without using if or unless
2) Move the search into a separate class (a concern) to keep your controller clean.
Here's a blog post that talks about some of the concepts (too much to post in this answer). There is lots of info on the web about this, I searched on the web under "rails search filter params concern" to get an example for you.
http://www.justinweiss.com/blog/2014/02/17/search-and-filter-rails-models-without-bloating-your-controller/

Creating Rails todo list app with goals and tasks on one page - missing params

I'm a Rails beginner, and have been reading tutorials and typing out applications for a few months now. I'm really enjoying it after a few years spent in the front end world, and beginning to get up to speed with it all. The time has come though for me to start building my own stuff without any handholding. So far, so good.
I'm creating a basic to-do list app, where goals and tasks are displayed on the same page - goals#index. My issue is that I'm not sure how to get all tasks for a particular goal (that belongs to a user). I understand that I need to pass an ID param to the Goal model in order to find out its tasks, like so:
Goal.find(1).tasks
The above works fine, as I've already set up foreign keys on the tasks table and have a has_many :tasks relationship for the Goal model and a belongs_to relationship for the Task model.
Here's my Goals controller:
def index
#user = current_user
#goals = #user.goals # list all goals for the current user and assign it to the #goals variable.
# Need to find all tasks for each goal ID and assign it to the #tasks variable. Goal ID needs to be supplied here, but it isn't as we're not in show action.
#tasks = Goal.find(1).tasks
As I said, I can find all tasks for a Goal when I manually enter the ID (1 in this example). This works fine in my app, no errors. But obviously I want to supply these IDs dynamically, and I'm just not sure how I get the params in there.
I have tried the below:
#tasks = Goal.find(params[:id]).tasks
and
#tasks = Goal.find(params[:goal_id]).tasks
And I get the "Couldn't find Goal without an ID" error when I try to iterate over #tasks in my view. Which makes sense, as I don't think the goal params are being passed to it as we're not in the Show action.
Surely there must be an easy Rails way?! I'm stumped and don't really know where to look. Thanks for your help and Happy New Year.
You are getting current user's goals, so when you will do this you have one array object. so when you will pass array object to find, it will have multiple IDS. so when need to find All the tasks from all goals you just need to pass Array of IDS instead of single value.
#tasks = Task.where(:goal_id => #goals)
This will run this SQL query.
SELECT "tasks".* FROM "tasks" WHERE "tasks"."goal_id" IN (SELECT
"goals"."id" FROM "goals")
So when you are dealing with array just pass ids. for e.g. [1,2,3]
Once you do #goals = #user.goals (assuming that's working, which it sounds like it is), you have your goals and there is no reason to go back to the DB to "find" them.
To get ALL your tasks from ALL of user's goals, you can do the following:
#tasks = []
#goals.each do |goal|
#tasks << goal.tasks
end
Ah of course, #goals is an array of the user's goals so I can just work with that. So simple when someone just tells you. Thanks for all your help!
Here's my final code that works (I left the controller unchanged). This gets the first goal in the array and then gets the tasks associated with each goal. I have a set number of goals so I can just use goals[0], goals[1] or goals[2] for each goal.
<% #goals[0].tasks.each do |task| %>
<li><div class="task-item"><%= task.task_name %></div></li>
<% end %>

each_with_index not working for an array rails [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 9 years ago.
Improve this question
When each_with_index is used along with an array of active records returned from a query and when i call the attributes of the model. it says undefined method, for the attribute
Controller
#user_contact_query=Usercontact.select("*")
#available_contacts_array=Array.new
#user_contact_query.each do |variable|
#user_query=User.find( :all,:conditions => ["id = ?",variable.relator_id])
#available_contacts_array.push(#user_query)
end
View
<% #available_contacts_array.each_with_index do |variable, index| %>
<%= variable.email %>
<% end %>
Well, step by step, since this is rather bad code:
#user_contact_query = Usercontact.select("*")
the select part here is a bit strange, I would have used .all. Otherwise no problem here.
#available_contacts_array = Array.new
since in the next line you assign an array anyway there is no need to 'initialize' this variable here, you could just omit that line. (Most Ruby programmers would use [] to initialize an empty array in most cases)
#available_contacts_array = #user_contact_query.to_a
Assuming that we get some records back from the query we now should have an array with user contact objects. Again a simple .all would have had a similar effect as far as I understand what's going on. But assuming that Usercontact has email this is fine and what you expect it to be.
#user_contact_query.each do |variable|
#user_query = User.find(:all, :conditions => ["id = ?", variable.relator_id])
#available_contacts_array.push(#user_query)
end
Now if I get this right what you do is you go through all Usercontacts you found, find every User related to it (possibly more than one) and then attach the result at the end of the array of Usercontacts. As a result you have a mix of Users and Usercontacts in that array. (Which still would work in your view if both have email, since thanks to duck typing Ruby would not care. But most likely one of both is missing email). Also if more than one User is found then an array of users is pushed. And an array for sure has no email.
Ok, with your edit it becomes simple. When you use :all with find then what you get is not a record but an array of records (even if there us only one.). You can just do:
User.find(variable.relator_id)
which will then find a single record and push that in the array. Which I guess is what you expect it to do.
There would be better ways in Rails to do this but this would require the models to be defined properly what your use of relator_id makes look unlikely.
First off please post your full error message. Although Without seeing the error I can tell with certainty that the issue is caused by you calling a method on a nil object i.e you think a value exists where it actually does not.
Besides that you really really should take more care of your code. The code you have right now is just plain horrible. This is an equivalent of your code:
#available = []
UserContact.find_each do |contact|
#available << User.find(contact.relator_id)
end
An alternative is to use #inject
#available = UserContact.all.inject([]) do |list, contact|
list << User.find(contact.relator_id)
list
end
And the best solution would be to actually use ActiveRecord assocations here.
P.S your code does not make much sense anyhow. Why would you put users contacts and users both into that list?
Why don't you do just?
controller's helper:
#array = Usercontact.select( "*" ).map do| variable |
User.find( :all,:conditions => [ "id = ?",variable.relator_id ])
end
view's helper:
#array.each.with_index do| variable, index |
# ...
end
General note: you'be used in the #available_contacts_array variable mixed types, probably this lead to the error, please don't do in such matter.

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 do I add an `.order` invocation to a ActiveRecord collection that I retrieved?

I am new in this world of Rails. And I cannot get my head around this problem. How to get #microposts ordered by the date the micropost was created.
This is the line
#microposts = current_user.followeds.map(&:microposts).flatten
I only managed to order by date the 'followeds', but that is not what I am looking for. All the attempts I have made gave me errors, so I guess I am not aware of some syntax.
Any help is welcome!
Normally, you would add an order clause, as in:
Micropost.where(:written_by => current_user.followeds).order(:created_at)
The way you currently have this line structured doesn't permit that, however, since order is only available on ActiveRecord::Relations, and once you do map you no longer have a Relation available to chain on.
If that's the case, you'll want something like:
current_user.followeds.map(&:microposts).flatten.sort_by { |m| m.created_at }
I think you should try to approach this from another angle.
How about something like this:
#microposts = Micropost.where(author_id: current_user.followed_ids).order(:created_at).all
You might of course have to exchange author_id for whatever foreign key you have to identify what user a Micropost was written by.
If you want to reverse the posts (newest first), you just write order("created_at desc") instead.

Resources