Ruby / Rails - Advanced grouping with group_by - ruby

I have a model called event_recurrences it contains 2 important columns event_id and cached_schedule
cached_schedule contains an array of dates
event_id is used to reference the event
I need to
Get all the event_recurrence objects #something.event_recurrences - Done
Go through each recurrence object and get the event_id and all the
dates from cached_schedule
Iterate through each month, and spit out a list like the following
Jan
event_id date
event_id date
event_id date
Feb
event_id date
... and so on
To recap the event_id is located event_recurrence.event_id the dates that the event_id will happen on are located in an array inside event_recurrence.cached_schedule
Some I have some incomplete code to work with...
This code works successfully to show each event_recurrence object by month using the created_at field.
in my controller
#schedule_months = #something.event_recurrences.order("created_at DESC").group_by { |e| e.created_at.beginning_of_month }
in my view
<% #schedule_months.keys.sort.each do |month| %>
<div class="month">
<%= month.strftime("%B %Y") %>
</div>
<% for event in #schedule_months[month] %>
<li><%= event %></li>
<% end %>
<% end %>
Thanks in advance.
Sincerely, jBeas

There are some details missing from your question... for example, can the dates in cached_schedule span multiple months, or are they all guaranteed to be in the same month?
If you just want to use core Ruby:
dates = []
#something.event_recurrences.each do |er|
er.cached_schedule.each { |date| dates << [date, er.event_id] }
end
#event_dates = dates.group_by { |(date,event_id)| date.mon }
Then in the view:
<% #event_dates.keys.sort.each do |month| %>
<div class="month"> <%= month.strftime("%B %Y") %></div>
<% #event_dates[month].each do |(date,event_id)| %>
<li><%= date %> <%= event_id %></li>
<% end %>
<% end %>
You may have to adjust the code a little depending on the specifics, but this should give you the idea. Note the use of destructuring assignment: #event_dates[month].each do |(date,event_id)|. This saves a line of code and expresses what the code is doing more clearly.
If you don't mind adding your own extensions to the core Ruby classes, you could make this code even cleaner and more consise. I often use a method which I call mappend:
module Enumerable
def mappend
result = []
each { |a| enum = yield a; enum.each { |b| result << b } if enum }
result
end
end
The name is a mix of map and append -- it is like map, but it expects the return value of the mapping block to also be Enumerable, and it "appends" all the returned Enumerables into a single Array. With this, you could write:
#event_dates = #something.event_recurrences.mappend { |er| er.cached_schedule.map { |date| [date, er.event_id] }}.group_by { |(date,event_id)| date.mon }
OK, that might be a lot for one line, but you get the idea: it saves you from using an intermediate variable to accumulate results.
UPDATE: Something like mappend is now part of the Ruby core library! It's called flat_map.

Related

Ruby - trying to make hashtags within string into links

I am trying to make the hashtags within a string into links.
e.g. I'd like a string that's currently: "I'm a string which contains a #hashtag" to transform into: "I'm a string which contains #hashtag"
The code that I have at the moment is as follows:
<% #messages.each do |message| %>
<% string = message.content %>
<% hashtaglinks = string.scan(/#(\d*)/).flatten %>
<% hashtaglinks.each do |tag| %>
<li><%= string = string.gsub(/##{tag}\b/, link_to("google", "##{tag}") %><li>
<% end %>
<% end %>
I've been trying (in vain) for several hours to get this to work, reading through many similar stackoverflow threads- but frustration has got the better of me, and as a beginner rubyist, I'd be really appreciate it if someone could please help me out!
The code in my 'server.rb' is as follows:
get '/' do
#messages = Message.all
erb :index
end
post '/messages' do
content = params["content"]
hashtags = params["content"].scan(/#\w+/).flatten.map{|hashtag|
Hashtag.first_or_create(:text => hashtag)}
Message.create(:content => content, :hashtags => hashtags)
redirect to('/')
end
get '/hashtags/:text' do
hashtag = Hashtag.first(:text => params[:text])
#messages = hashtag ? hashtag.messages : []
erb :index
end
helpers do
def link_to(url,text=url,opts={})
attributes = ""
opts.each { |key,value| attributes << key.to_s << "=\"" << value << "\" "}
"<a href=\"#{url}\" #{attributes}>#{text}</a>"
end
end
Here is the code to get you started. This should replace (in-place) the hashtags in the string with the links:
<% string.gsub!(/#\w+/) do |tag| %>
<% link_to("##{tag}", url_you_want_to_replace_hashtag_with) %>
<% end %>
You may need to use html_safe on the string to display it afterwards.
The regex doesn't account for more complex cases, like what do you do in case of ##tag0 or #tag1#tag2. Should tag0 and tag2 be considered hashtags? Also, you may want to change \w to something like [a-zA-Z0-9] if you want to limit the tags to alphanumerics and digits only.

Redmine modification for issue number

I would like to hide the issue number on the issue list.
I use "Redmine 2.3.1" and the file to achieve that is this file: ./app/views/issues/_list.html.erb
It contains this line:
<%= raw query.inline_columns.map {|column| "<td class=\"#{column.css_classes}\">#{column_content(column, issue)}</td>"}.join%>
And the array query.inline_columns contains the id for example. I thought modifying this array would help me with my problem, but the changes aren't saved.
This is what I tried:
<%= #query.inline_columns.inspect %>
=> [#<QueryColumn:0x00000003607928 #name=:id ... and so on>]
<% #query.inline_columns.delete_at(0) %>
=>
<%= #query.inline_columns.inspect %>
=> [#<QueryColumn:0x00000003607928 #name=:id ... and so on>]
Why was the delete_at useless? And how would I modify this array or hide the number?
A few things to note:
#query.inline_columns might not an array. It may quack like an Array, but it's not.
#query.inline_columns.delete_at(0) should have returned something. Did it return and you ommited?
And last, but not least, the most probable guess I can make:
#query.inline_columns fetches something, converts it to an array and dups it before returning. The only way you can do something like that would be:
some_columns = #query.inline_columns
some_columns.delete_at(0)
some_columns
This is my solution now:
<% if #project.id == 1 %>
<% #qcols = query.inline_columns.reject{ |col| col.name.to_s == 'id' } %>
<% else %>
<% #qcols = query.inline_columns.each %>
<% end %>
So I took advantage of the reject method. And then I work with the #qcols.
Posted on behalf of OP.

accessing mongodb document field names and its values through iteration

Let's say I have a mongodb document in the products collection:
{
"_id" : ObjectId("51b1eac0311b6dd93a000001"),
"name" : "Apple",
"price" : "34.45"
}
products_controller.rb for def show part:
def show
#product = Product.find(params[:id])
end
I imagine the code would seem like below in the show.html.erb:
<% #product.each do |f|%>
<p>f.label</p> # this is only an image code
<p>f.value</p> # this is only an image code
<% end %>
How the code at the lines 2 and 3 on Rails 3 should look like in a generic way so it would show like? :
name: Apple
price: 34.45
Number of fields can be 20, so I don't want to write the same code for 20 fields.
I'm using Rails 3 with Mongoid. I think it's not a mongodb-specific question.
Try this:
foreach (var item in YourcollectionName)
{
var name = item.name;
var price = item.price;
}
Mongoid models have an attributes method that returns a hash of the attributes. If you iterate over that hash you'll be yielded the name and value of each entry.
For example
<% #product.attributes.each do |name, value| %>
<p>
<%= name%> : <%= value %>
</p>
<% end %>
You'd need some more sophisticated formatting code for pretty output for all the kinds of values you might get (dates, arrays, hashes etc.)

Ruby 'First and Rest' Logic

My app is reaching a point where I must begin optimizing for performance. I've posted some code from my view that I feel can be improved.
In the view, I am treating the first item in the index a certain way and the rest of the items another way. Each time a new item is iterated over, it is being checked (Ruby asks itself.. does this item have an index of 0?)
I feel like performance can be improved if I can stop that behavior by treating the first item special with index.first? and treating the other items another way (without even checking whether they have an index of zero) How can this be done?
<% #links.each_with_index do |link, index| %>
<% if link.points == 0 then #points = "?" else #points = link.points %>
<% end %>
<% if index == 0 then %>
<h1> First Item </h1>
<% else %>
<h1> Everything else </h1>
<% end %>
<% end %>
<% end %>
You can do this non-destructively like so:
first, *rest = *my_array
# Do something with 'first'
rest.each{ |item| … }
…where first will be the first element (or nil if my_array was empty) and rest will always be an array (possibly empty).
You can get the same results more easily if it's OK to modify your array:
# remove the first item from the array and return it
first = my_array.shift
# do something with 'first'
my_array.each{ |item| … }
However, this will only clean up your code; it will make no measurable performance difference.

Rails 3 refactoring issue

The following view code generates a series of links with totals (as expected):
<% #jobs.group_by(&:employer_name).sort.each do |employer, jobs| %>
<%= link_to employer, jobs_path() %> <%= "(#{jobs.length})" %>
<% end %>
However, when I refactor the view's code and move the logic to a helper, the code doesn't work as expect.
view:
<%= employer_filter(#jobs_clone) %>
helper:
def employer_filter(jobs)
jobs.group_by(&:employer_name).sort.each do |employer,jobs|
link_to employer, jobs_path()
end
end
The following output is generated:
<Job:0x10342e628>#<Job:0x10342e588>#<Job:0x10342e2e0>Employer A#<Job:0x10342e1c8>Employer B#<Job:0x10342e0d8>Employer C#<Job:0x10342ded0>Employer D#
What am I not understanding? At first blush, the code seems to be equivalent.
In the first example, it is directly outputting to erb, in the second example it is returning the result of that method.
Try this:
def employer_filter(jobs)
employer_filter = ""
jobs.group_by(&:employer_name).sort.each do |employer,jobs|
employer_filter += link_to(employer, jobs_path())
end
employer_filter
end
Then call it like this in the view:
raw(employer_filter(jobs))
Also note the use of "raw". Once you move generation of a string out of the template you need to tell rails that you don't want it html escaped.
For extra credit, you could use the "inject" command instead of explicitly building the string, but I am lazy and wanted to give you what I know would work w/o testing.
This syntax worked as I hoped it would:
def employer_filter(jobs_clone)
jobs_clone.group_by(&:employer_name).sort.collect { |group,items|
link_to( group, jobs_path() ) + " (#{items.length})"
}.join(' | ').html_safe
end

Resources