Conditionally insert date separator to array of chat messages - ruby-on-rails

I'm working on a Chat feature, similar to Slack, where the messages are grouped by date. I'm using the groupdate gem to put all of the messages together in a hash, and then print them out in the browser where messages are grouped by the date they were sent on:
#messages = #chatroom.messages.group_by_day { |message| message.created_at }
- #messages.each do |day, messages|
%li.date_divider
= "#{day.strftime("%B %d")}"
%ul
- messages.each do |message|
%li= message.content
For performance, it's crazy to load months worth of messages all at once and I'm trying to think how I can approach this by paginating the messages and using some sort of infinite autoscroll. I can get this working fine using #pagy, #messages = pagy(#chatroom.messages) but all of it goes to hell when I use the group_by_day and returned in a hash. I was curious if you anyone could shed some input on how to approach this where I can still benefit from the user-experience by grouping messages by date, but also benefit from the performance in the case outlined above.
Alternative to using group_by_day, one suggestion is to paginate an array of messages ordered by date and conditionally add the extra date display in the loop to display when the next item have a new date compared to the previous one, but I'm unsure how to do this off the top of my head.

You're not far of an actual working version, when you do:
# controller
#pagy, #messages = pagy(#chatroom.messages)
The messages are stored in #messages. When you call #chatroom.messages.group_by_day you call group_by_day upon all messages belonging to the chatroom. Instead use #messages.group_by_day which holds the messages that are retuned by pagy.
-# view
- #messages = #messages.group_by_day { |message| message.created_at }
- #messages.each do |day, messages|
-# ...
For infinite scroll there you might want to use a library. The basic idea is to fetch messages from the server when the user scrolls close to the top or bottom of the page. You then insert them in the correct place in the HTML using JavaScript.
Note that you don't have to do one or the other. You could have an infinite scroll that groups the messages by date. Although it might be somewhat harder to implement.
I'm leaving the details out of this answer since that deserves it's own question. Note that you shouldn't create a new question instantly. First have a look around the internet for different libraries or methods to create your own, try to work something out. Then if you get stuck or have a specific question you should create a new question.

One thing you could do is to save the last date in a variable at the end of the outer loop (so after your messages have been printed. Once the condition day != previous_day is met, you print the divisor

You can pass a custom search to pagy in order to display the items in the way you want to be displayed. For example do something like:
#pagy, #messages = pagy(Chatroom.customSearch(params[:q]), items: 20)
In your Chatroom model you can create the customSearch function. Find all chatRoom messages you want, order and send them back to pagy. Hope it helps.

Related

How can I populate a form with results from one table which can be edited and saved to another table

The answers to date have not worked so I have re-written my request for help. I hope it is more clear what I am struggling with
I want to move fields from an array (Array1) returned from a table(Table1) into another array (Array2), allow edit and write each record to Table2.
Desired methodology in /new_multiple in the controller:
1. #tasks = Task.find(params[:task_ids]) # returns multiple records
#:task_ids are checkbox tags
#returned from previous form
Note: This works.
2. Move #tasks each into #event and send to existing new.html.erb form for edit and/or confirmation before creation as follows(??):
#tasks.each do |task|
#event = Event.new
#event.location = task.task_location
#event.description = task.task_description
event.start_date = start_date + task.days_from_from_start_task
..... more calculated fields
***send to form for edit, show, create and return for next record (QUESTION 1 below)
end
Note: a redirect and return in *** goes to the form, but the data does not show in the form. I haven't determined if it actually returns.
Questions:
1. Is there a way call a form from the middle of an iteration in the controller and then return to the loop for other records?
2. If not, how do I (can I?)
a)move the multiple #tasks records into an #events array as above (for display in a table in a form for edit)
b)tell the system that each individual row of the array is a new record to be written on submit
Background:
I am creating an application which allows the scheduling of sets of future events. Related tasks (sets) are saved in one table and specify a sequence and a number of days from the first task in the group for that task to occur.
The user starts the process by setting the criteria - task group and the start date from which the events will begin.
The task records are then displayed with the calculated dates/time in a table on a form using a form_tag. At this point, they look like the events that are going to be created. Each line has a check-box tag for selection of tasks in the group. After the user has "ticked" their selection of events (or all), the application returns to the controller to a method called new_multiple_events
Up to this point everything works fine and I can see the selected records that have been chosen (ticked) within the events controller using debugger.
I now want to display each record individually and allow the user to edit the calculated date and time that the event will occur plus a few other fields such as the location and notes etc. and then submit the record for creation in the events table. But this is where I am just NOT getting it.
Models:
Tasks table model:
task.task_description
task.task_location
task.task_notes
task.days_from_start_task
task.task_group_name
Event table model:
event.title = task.task_group_name
event.location = task.task_location
event.description = task.task_notes
event.start_date = start_date + task.days_from_from_start_task
etc...
I have a form which works fine for entry of individual events into the calendar and I would like to move the data from the returned tasks array (plus calculated fields) into the events array as default values for this form which will then allow the user to save the data.
Thanking you in advance for any help you can give.
I don't know how you are prepopulating the event you are editing / creating in the form, but your code states you are doing that in a model. You should however create that logic in the controller before rendering the form, so do something like this:
events_controller.rb
def new
task = Task.find(params[:task_id])
#event = Event.new
#event.title = task.task_group_name
#event.location = task.task_location
#event.description = task.task_notes
#event.start_date = start_date + task.days_from_from_start_task
end
Then create your form like you normally do with the form declaration you already found yourself: form_for #event do |f|
Please consider that I used params[:task_id], suggesting you set up your routes for events nested under tasks so that your url structure looks like (for instance): /tasks/13/events/new
Goog luck
I am closing this issue because I didn't receive a relevant answer. I wanted to allow editing of selected data from one table prior to writing the data, with additional information, to another table. To get around the issue I have instead saved the selected data to the other table and then immediately read the data for edit/update. To the user it looks the same.

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 %>

Problems working with the output of the YouTube_It gem in Rails

I've been messing around with the Youtube_It gem for the past couple of days as I'm trying to integrate it into a Rails 4 app I'm currently working on.
When I run a search query with it, it outputs an array with a ton of results. As long as I'm in the Rails console, there's no problem with manipulating it. A brief example :
require 'youtube_it'
client = YouTubeIt::Client.new(:dev_key => "dev_key")
data = client.videos_by(:query => "penguin")
data.videos.each { |v| puts v.unique_id }
This outputs a nice, tidy list of all the unique id's that were returned from the query.
When I try to do something similar within a view (I know it shouldn't really be in the view, it's simply there for testing at this point) it just outputs a huge jumbled list of what appears to be XML. This only seems to happen when I try to iterate using .each.
When I do something like :
<% data = client.videos_by(:query => "penguin") %>
<%= data.videos[1].unique_id %>
This returns exactly what I was expecting, one unique_id from the index that I chose. Great! But how do I get ALL the unique id's?
That's where I'm stuck and would really appreciate some help! :)
OK, two reasons (working from the gist you gave me on IRC, located here):
1) You are not actually using the same code as in the console. Your console uses puts unique_id which will print the value of the unique ID, but you are just using unique_id which will get the ID... and then do nothing with it. What you want is probably something like data.videos.map(&:unique_id) (or data.videos.map { |v| v.unique_id } in long form) which will return you an array of the IDs.
2) You are using <%=, which means 'evaluate this ruby line and output the return value onto the page'. The return value of an each statement is the object you called each on - ie. data.videos, so that is what is getting printed out.

Need to output 1095 fields in Rails app

I'm writing an application that requires 3 inputs a day for every day of the year. (3*365=1095). I'm struggling with a way to output each field in an efficient manner.
The inputs are not all-or-nothing (you could fill in 10 days worth of input, hit save, and come back later to fill in more)
I attempted to do this by building all 1095 objects in the controller and then outputting the inputs in the view, but obviously this is really slow and probably memory intensive.
Any suggestions? I'm leaning toward writing the entire form client-side and then filling in the existing elements using AJAX.
EDIT
The model is called Timing and has these attributes:
month, day, time1, time2, time3
so there are 365 models to be saved.
Sounds like you've got a nested resource. You have a resource called timing which contains a resource called, what, day?
#routes
resources :timing do
resources :day
end
So assuming that when timing is created, you have all 365 days created as well (sounds like a pretty expensive operation). Displaying the fields isn't that tricky. You could just do
#controller
def show
#timings = Timing.all
end
#view
(Date.beginning_of_year..Date.end_of_year).each do |day|
t = #timings.find { |timing| timing.date == day } #or some other method of deciding that the current day has a timing
unless t.nil?
form_for t #etc
else
form_for Timing.new #etc
end
end
Then perhaps you could make each for submit via UJS and call it a day. Though this sounds like a pretty complicated design, but I'm not sure what your problem area is.
If I understand your question correctly, you want a way to dynamically show time inputs, 3 of them per day, on a form.
If the above is correct, what I would suggest is that you do the nested resource as #DVG has detailed, and load the current day only. If you need to load multiple days, you can easily request that through UJS (Ajax) and load it on the same page.
What you probably want to do, in order not to melt down the server, is auto-save the time inputs or auto-save each day's time inputs when the grouping loses focus.
#DVG's answer probably works fine, but it keeps all of the work on the server.
What I ended up going with was my initial thought: get all of the existing timings like this:
def edit
#timings = Timing.find_by_store_id(params[:store_id])
end
then in the view, I wrote two javascript functions: one that writes all 365 rows with all 3 columns. Once the field were all output in Javascript, I used another function that took the existing records and inserted them into the form:
<script type="text/javascript">
function updateForm(){
timings = <%= #timings.to_json %>;
... fill out the fields ...
}
</script>
It works nice and fast, and best of all, no AJAX calls. Of course one caveat is that this fails if the user has Javascript disabled, but that's not an issue for me.

Display all versions of individual records in Papertrail

I'm building a league system and currently it stores and updates the players 'elo score' depending on the result. Now, I'm trying to add in 'HighCharts' to display the players elo score over the season in a sweet looking line chart. Someone suggested I use Papertrail to store the updates and I have got that all installed.
Now here comes my problem, I can't seem to figure out how to spit out the users elo_score versions in an array easy for 'HighCharts' to use. I can get the last updates to elo_score:
Last updated score = <%= #player.versions.last.reify.elo_score %>
But I can't seem to find the syntax to spit out all the 'versions' for 'elo_score'. Something like "1000, 1020, 1043, 1020".
I've also tried:
<%= #player.versions.map { |version| version.reify.elo_score} %>
But this gives me "undefined method `elo_score' for nil:NilClass". While just <%= #player.versions.map { |version| version.reify %> spits out all information in the record and obviously not just the elo_score.
Can anyone help? Sorry if I've not made this clear, I'm absolute brand new to rails, and this is just a fun project in my spare time but I'm having a blast!
Thanks alot!
What you did here:
#player.versions.map { |version| version.reify.elo_score }
Is perfectly fine to take all those scores and put them in an array. The problem that you're getting (the nil:NilClass stuff) is coming because at least one reify is nil. That is, that some version doesn't have a reify.
If each version is supposed to have a reify, be sure to add that as a model validation, and find in your code where the reify is being set and see why it's nil.
If it's okay for a version to have a nil reify, you could accomplish it a number of ways, but the straightforward and explicit way would look like this:
elo_scores = []
#player.versions.each do |version|
unless version.reify.nil?
elo_scores << version.reify.elo_score
end
end
I would suggest putting this in to a method, like get_elo_scores, and then you could more easily call it like:
#player.get_elo_scores
EDIT For clarification from the comments:
Your User model (or Player model, whatever you named it) should have a method that looks like this:
def get_elo_scores
elo_scores = []
self.versions.each do |version|
unless version.reify.nil?
elo_scores << version.reify.elo_score
end
end
return elo_scores
end
I apologize for not making this clearer, but you won't have access to #player within this method because that only exists in the context of your controller and view. The above is now a proper instance method: it will call .versions upon itself, and the rest is fine. I also added an explicit return call at the end.
Now you will be able to call #player.get_elo_scores on any User (or Player) object.
Hope that helps!
Here's a one-liner version of #MrDanA's answer :
elo_scores = self.versions.map{|version| version.reify.elo_scores}
note that you can't check if version.reify.nil? though

Resources