rails each loop with from to - ruby-on-rails

In my controller i fetch 9 row's for object organizations.
#organizations = Organization.where(parent_id: 1).order(city_id: :asc, is_vip: :desc, title: :asc).limit(25).sample(9)
and then in view i must separate this 9 value's to 3 view loops, like first .each do if for row's 1-3, second for 4-6, third 6-9
and i try so:
- #organizations[0..2].each do |org|
...
- #organizations[3..5].each do |org|
...
- #organizations[6..8].each do |org|
...
but it seems that i do something wrong, but what exactly? and how to do it right?

Not sure why your data is duplicated. But you can use the following method for splitting the array into slices
you can use each_slice
#organization.each_slice(3) do |sliced_orgs|
end
Some documentation here

First I don't really get why you use .limit(25).sample(9), you could limit your results to 9 already. But maybe you have some use of the random factor introduced by sample? Strange.
Other than that,
#organizations[0..2].each do |org|
puts org
end
...
should work perfectly fine. If the data is repeated it is because you have multiple times the same entry in your model. sample(9) is taking random unique entries and #organizations[0..2] is a fixed range returning an array or nil. (Rubydoc : ary[range] → new_ary or nil)
In short, nothing wrong with the code but probably somewhere in your data/logic.

Related

Iterating through CSV::Rows

I'm going to preface that I'm still learning ruby.
I'm writing a script to parse a .csv and identify possible duplicate records in the data-set.
I have a .csv file with headers, so I'm parsing the data so that I can access each row using a header title as such:
#contact_table = CSV.parse(File.read("app/data/file.csv"), headers: true)
# Prints all last names in table
puts contact_table['last_name']
I'm trying to iterate over each row in the table and identify if the last name I'm currently iterating over is similar to the next last name, but I'm having trouble doing this. I guess the way I'm handling it is as if it's an array, but I checked the type and it's a CSV::Row.
example (this doesn't work):
#contact_table.each_with_index do |c, i|
puts "first contact is #{c['last_name']}, second contact is #{c[i + 1]['last_name']}"
end
I realized this doesn't work like this because the table isn't an array, it's a CSV::Row like I previously mentioned. Is there any method that can achieve this? I'm really blanking right now.
My csv looks something like this:
id,first_name,last_name,company,email,address1,address2,zip,city,state_long,state,phone
1,Donalt,Canter,Gottlieb Group,dcanter0#nydailynews.com,9 Homewood Alley,,50335,Des Moines,Iowa,IA,515-601-4495
2,Daphene,McArthur,"West, Schimmel and Rath",dmcarthur1#twitter.com,43 Grover Parkway,,30311,Atlanta,Georgia,GA,770-271-7837
#contact_table should be a CSV::Table which is a collection of CSV::Rows so in this:
#contact_table.each_with_index do |c, i|
...
end
c is a CSV::Row. That's why c['last_name'] works. The problem is that here:
c[i + 1]['last_name']
you're looking at c (a single row) instead of #contact_table, if you said:
#contact_table[i + 1]['last_name']
then you'd get the next last name or, when c is the last row, an exception because #contact_table[i+1] will be nil.
Also, inside the iteration, c is the current (or (i+1)th) row and won't always be the first.
What is your use case for this? Seems like a school project?
I recommend for_each instead of parse (see this comparison). I would probably use a Set for this.
Create a Set outside of the scope of parsing the file (i.e., above the parsing code). Let's call it rows.
Call rows.include?(row) during each iteration while parsing the file
If true, then you know you have a duplicate
If false, then call rows.add(row) to add the new row to the set
You could also just fill your set with an individual value from a column that must be distinct (e.g., row.field(:some_column_name)), such as email or phone number, and do the same inclusion check for that.
(If this is for a real app, please don't do this. Use model validations instead.)
I would use #read instead of #parse and do something like this:
require 'csv'
LASTNAME_INDEX = 2
data = CSV.read('data.csv')
data[1..-1].each_with_index do |row, index|
puts "Contact number #{index + 1} has the following last name : #{row[LASTNAME_INDEX]}"
end
#~> Contact number 1 has the following last name : Canter
#~> Contact number 2 has the following last name : McArthur

Rails, sum the aggregate of an attribute of all instances of a model

I have a model Channel. The relating table has several column, for example clicks.
So Channel.all.sum(:clicks) gives me the sum of clicks of all channels.
In my model I have added a new method
def test
123 #this is just an example
end
So now, Channel.first.test returns 123
What I want to do is something like Channel.all.sum(:test) which sums the test value of all channels.
The error I get is that test is not a column, which of course it is not, but I hoped to till be able to build this sum.
How could I achieve this?
You could try:
Channel.all.map(&:test).sum
Where clicks is a column of the model's table, use:
Channel.sum(:clicks)
To solve your issue, you can do
Channel.all.sum(&:test)
But it would be better to try achieving it on the database layer, because processing with Ruby might be heavy for memory and efficiency.
EDIT
If you want to sum by a method which takes arguments:
Channel.all.sum { |channel| channel.test(start_date, end_date) }
What you are talking about here is two very different things:
ActiveRecord::Calculations.sum sums the values of a column in the database:
SELECT SUM("table_name"."column_name") FROM "column_name"
This is what happens if you call Channel.sum(:column_name).
ActiveSupport also extends the Enumerable module with a .sum method:
module Enumerable
def sum(identity = nil, &block)
if block_given?
map(&block).sum(identity)
else
sum = identity ? inject(identity, :+) : inject(:+)
sum || identity || 0
end
end
end
This loops though all the values in memory and adds them together.
Channel.all.sum(&:test)
Is equivalent to:
Channel.all.inject(0) { |sum, c| sum + c.test }
Using the later can lead to serious performance issues as it pulls all the data out of the database.
Alternatively you do this.
Channel.all.inject(0) {|sum,x| sum + x.test }
You can changed the 0 to whatever value you want the sum to start off at.

update_attributes inside each rails

I have array of data
array = [1,2,4,6]
and what i want it update my model like this
array.each do |a|
Mymodel.all.each do |mm|
mm.update_attributes(name: a)
end
end
but problem is when im trying to do this
update_attributes only with last a
at the end all objects have name 6
How can fix it?
because thats what you are doing, you are running each loop over the array but you are doing the same.
first iteration:
Mymodel.all.each do |mm|
mm.update_attributes(name: 1)
end
second iteration:
Mymodel.all.each do |mm|
mm.update_attributes(name: 2)
end
last iteration:
Mymodel.all.each do |mm|
mm.update_attributes(name: 6)
end
so you just update every time the attribute name to the element in the array. and the last one is there to keep.
your logic is incorrect.
That's logical. For every element in the array you're updating all the model records. So first you're updating all your model entries with the first value in your array, being 1, then you're doing the same for very every model entry only with the value 2. Last value in your array is 6 so you're setting all the values in your model to name with the value 6.
I'm not sure what you're trying to accomplish but from your question I assume you want to update your model in order of your array. So something like this.
model_entries = Mymodel.all
array.each do |value, index|
model_entries[index].update_attributes(name: value)
end
That's one solution, but it seems to me that's something wrong with your base logic here. From where I stand this is certainly something you want to solve in a different way. I can't really go into detail here as I don't know enough about what it is you're trying to achieve

How do I count items in an array that have a specific attribute value?

In my application, I have an array named #apps which is loaded by ActiveRecord with a record containing the app's name, environment, etc.
I am currently using #apps.count to get the number of apps in the array, but I am having trouble counting the number of applications in the array where the environment = 0.
I tried #apps.count(0) but that didn't work since there are multiple fields for each record.
I also tried something like #apps.count{ |environment| environment = 0} but nothing happened.
Any suggestions?
Just use select to narrow down to what you want:
#apps.select {|a| a.environment == 0}.count
However, if this is based on ActiveRecord, you'd be better off just making your initial query limit it unless of course you need all of the records and are just filtering them in different ways for different purposes.
I'll assume your model is call App since you are putting them in #apps:
App.where(environment: 0).count
You have the variable wrong. Also, you have assignment instead of comparison.
#apps.count{|app| app.environment == 0}
or
#apps.count{|app| app.environment.zero?}
I would use reduce OR each_with_object here:
reduce docs:
#apps.reduce(Hash.new(0)) do |counts, app|
counts[app.environment] += 1
counts
end
each_with_object docs:
#apps.each_with_object(Hash.new(0)) do |app, counts|
counts[app.environment] += 1
end
If you are able to query, use sql
App.group(:environment).count will return a hash with keys as environment and values as the count.

Which is faster "count" or "length"?

Assuming there are 2 models called User and Post
Which will be better performance(fast) either "Plan A" or "Plan B"?
"Plan A"
controller
#users = User.find_all_by_country(params[:country])
#posts = Post.find_all_by_category(params[:category])
view
<%= #users.count.to_s %>
<%= #posts.count.to_s %>
"Plan B"
controller
#users = User.find_all_by_country(params[:country])
#posts = Post.find_all_by_category(params[:category])
view
<%= #users.length.to_s %>
<%= #posts.length.to_s %>
In ruby, count, length and size all do pretty much the same thing regarding arrays. See here for more info.
When using ActiveRecord objects, however, count is better than length, and size is even better.
find_all_by_country returns a dumb array so you shouldn't use that method (because it always returns an array). Instead, use where(country: params[:country]).
I'll let Code School's Rails Best Practices slide nº 93 speak for itself (and hope they don't get mad at me for reproducing it here).
Just in case the image gets taken down, basically:
length always pulls all the records and then calls .length on the array - bad
count always does a count query - good
size looks at the cache if you have a cache counter, otherwise does a count query - best
Both will be the same, count with no arguments and length are identical as you are invoking them on a Ruby array (returned by the magic find_* method), and not an ActiveRecord object.
That said, both methods are the worst way to do this, if you're simply interested in the number of matching records.
Instead of instantiating the entire result set just to find its length, use .count on an actual ActiveRecord relation:
#num_users = User.where(country: params[:country]).count
#num_posts = Post.where(category: params[:category]).count
This will actually execute as select count(*) from instead of a full select * from, which will be much faster depending on the number of results.

Resources