Activity
Posted in How do I find a Rails mentor?
I've been developing Rails apps since 2012 but my main app is still on 3.2.14 so I'm quite behind on all the new tech. I'm slowly developing a new app on the latest version but I find it slow going because everything is so unfamiliar.
I'd love to find a Rails mentor with whom I could do 1-hour video call every 2-4 weeks. Mainly to discuss how to implement my upcoming features. This would be paid, of course.
I find my lowest confidence points are in the initial implementation decisions - writing the actual code is fairly easy once I know what direction to take.
Where can I find a mentor like this?
Visual aid: https://wmd.d.pr/ktKzOu
I have SchoolClass
and Student
resources. Students belong to classes through ClassMemberships
. So far students have been added to classes using a grid of class checkboxes on the Edit Student page. Now I'm working on a feature to add students to classes directly from the Edit Class page.
I have a search input that remote
loads the student search results in a div. I want each student name to be clickable to add them to the class (ie create a new ClassMembership
).
Would you use something like the button_to
tag to create a tiny form for each student name? Where would you send the form? The class_membership#create
controller action, passing in the class.id
and student.id
? Or is there a more Rails-y way to do it? (I'm aware the whole process I'm using here isn't very Rails-y)
# school_class.rb
class SchoolClass < ActiveRecord::Base
...
has_many :class_memberships
has_many :students, through: :class_memberships
...
end
# student.rb
class Student < ActiveRecord::Base
...
has_many :class_memberships
has_many :school_classes, through: :class_memberships
...
end
# class_membership.rb
class ClassMembership < ActiveRecord::Base
...
belongs_to :student
belongs_to :school_class
...
end
You could build the conditions first then use them in the query:
conditions = { category: params[:category] }
conditions[:author_id] = author.id if author
Article.find(:all, conditions: conditions)
As I understand it, author_ids
is one of those Rails built-in magic merhods that gives you all the IDs of associated records. For example, if I have a School that has_many :teachers
I can do School.find(1).teacher_ids
to get all the IDs of all the teachers associated with that school. Super handy and saves writing some gnarly SQL.
The name_with_initial
method is generating the text shown alongside the checkbox. It could just be a plain text column from Author, like first_name
but this is showing you how you can spruce it up a bit and use a method to generate fotmatted text instead.
Posted in I'm not sure what's the correct title for my question is. Read the content for more information.
Post your whole form code if possible.
I have a Family model that has_many
Kids. I'm using dependent: :destroy
on the family so when it is destroyed the associated kids are destroyed too. All good.
Now I need to destroy the family when the last associated kid is deleted. This is to prevent stray families in the database that have no kids.
Should I do it in students_controller#destroy
method? Or is there a better way?
For now I've changed the timer from default 5 seconds to 60 seconds with this:
# config/initializers/delayed_job_config.rb
Delayed::Worker.sleep_delay = 60
Rails 3.2.14 / Ruby 2.0.0 / Delayed Job 4.1.4 / Foreman
I'm running a worker with Delayed Job, starting the process with Foreman. It all works fine but my dev logs are full of Delayed Job SQL statements. It makes debugging anything in development tiresome. Every 5 seconds I get a big chunk like this:
17:40:26 worker.1 | Delayed::Backend::ActiveRecord::Job Load (0.9ms) UPDATE "delayed_jobs" SET locked_at = '2020-03-25 00:40:26.874025', locked_by = 'host:MacBook-Pro-8.local pid:67410' WHERE id IN (SELECT id FROM "delayed_jobs" WHERE ((run_at <= '2020-03-25 00:40:26.873540' AND (locked_at IS NULL OR locked_at < '2020-03-24 20:40:26.873551') OR locked_by = 'host:MacBook-Pro-8.local pid:67410') AND failed_at IS NULL) ORDER BY priority ASC, run_at ASC LIMIT 1 FOR UPDATE) RETURNING *
How do quiet these SQL logs from Delayed Job? I tried a few solutions form the interwebs but nothing worked so far 😕
Mostly from this thread: https://github.com/collectiveidea/delayed_job/issues/886
Posted in Moving from STI to roles
Found this SO answer suggesting using the strategy pattern to give methods different behaviour based on the user role. Looks very interesting but quite complex.
Posted in Moving from STI to roles
Finding it tricky to move from STI (Single Table Inheritance) to user roles because associations between the various user roles to other resources mean different things to different user types.
Teacher
and Family
models both inherit from User
table. Separately we also have Student
and Lesson
resources.
Teachers teach (has_many
) students, so when I call @teacher.students
I get all the students taught by that teacher. Likewise, a family has_many
students, so when I call @family.students
I get all the student who belong to that family.
Similar for lessons - teachers make lessons and families view their students lessons. When I call @teacher.lessons
I get a list of all lessons created by that teacher. When I call @family.lessons
I get a list of all lessons created for studnets that belong to that family.
So if I were to get rid of STI and just use one User
model, what would calling @user.students
or @user.lessons
return?
I could create some new association names and specify a foreign key, something like:
class User < ActiveRecord::Base
# for Teachers
has_many :students, foreign_key: "teacher_id", class_name: "Student"
has_many :lessons, foreign_key: "teacher_id", class_name: "Lesson"
# for Families
has_many :kids, foreign_key: "family_id", class_name: "Student"
has_many :kids_lessons, foreign_key: "family_id", class_name: "Lesson"
...
Is this a reasonable way to go or is there another more Rails-y way to do this?
Hey Ivan, thanks for your ideas. Good stuff.
Unfortunately scheduling won't really help here because the long-running job is user-initiated. Nor will breaking it up into smaller pieces since it's a single FFMPEG video creation process.
A dedicated worker that just handles these requests would help but I'll still be limited to one job per minute. I guess multiple dedicated workers would multiply that but it's early stage and I want to keep costs down. Running the whole app on a $15/mo Digital Ocean server right now.
Having the external service ping my app when the job is done was my original approach. I always wanted to porcess to be asynchronous and started by passing in a webhook URL for the job to call when finished but...
Calling the job via HTTP is the issue. The basis of HTTP requests is that they wait for a response and this gets to the crux of my issue - the job is a serverless function running on Zeit Now platform. These functions stop processing as soon as they send an HTTP response. So I can't make it respond right away with some kind of 'ACCEPTED' message because then the rest of the function won't run.
One hacky option is to set a short timeout on the HTTP request in my Rails app. Then just accept the error and move on. That way the external job doesn't send any response at all. It just calls the webhook URL in app with the result. Super hacky!
Nice! 🙌
Just in case anyone finds this helpful, here's how I solved this with Stimulus. If you see any improvements let me know!
# _form.html.erb
...
<div class="form-group" data-controller="filters">
<%= form.label "spectrum" %>
<%= radio_button_tag :spectrum, "none", :checked,
data: { "action": "click->filters#update" } %>
<%= label_tag :spectrum_none, "None" %>
<%= radio_button_tag :spectrum, "white", false,
data: { "action": "click->filters#update" } %>
<%= label_tag :spectrum_white, "White" %>
<%= radio_button_tag :spectrum, "black", false,
data: { "action": "click->filters#update" } %>
<%= label_tag :spectrum_black, "Black" %>
<%= form.hidden_field :spectrum,
data: { "target": "filters.spectrum" } %>
<%= form.hidden_field :spectrum_color,
data: { "target": "filters.spectrum_color" } %>
</div>
...
# app/javascripts/controllers/filters_controller.js
import { Controller } from "stimulus"
export default class extends Controller {
update(event) {
const value = event.target.value
const spectrum = value == "none" ? false : true
const spectrum_color = value == "none" ? "" : value
this.targets.find("spectrum").value = spectrum
this.targets.find("spectrum_color").value = spectrum_color
}
}
My Rails app calls a long-running external service which can take over a minute to return a response. I'm calling the service from a Sidekiq job but I don't want to tie up my worker for over a minute on every job waiting for the response.
I looked into a 'fire and forget' approach - call the service with the params then don't wait for a response, just move on. If I did that I'd end up with more jobs and code to check if the external job was completed. Not ideal.
I can have the external service call a webhook endpoint in my app with the result but I can't make the external service respond to the original call any earlier.
What are some good approaches to handling long-running external services?
Chris - how will this work with the associations already set up in the app?
I have an upcoming situation where I'll be importing a lot of data with many associations into a Rails app. The data will show the links between the objects using the original native database IDs.
When I import the data (not sure how I'll do it yet, probably CSV) what would be a good way to set up the correct associations?
I'm thinking of writing the original IDs of each object to an old_id
column and using that somehow to create the associations in the database.
I'm concerned about the depth of associations here. Less than a dozen objects but all heavily associated to each other.
Any thoughts? Have I explained this well enough?
Really loving this testing series. I've been using RSpec for years but it looks like Minitest now covers a lot of the functionality that made me choose RSpec back then.
One of the things I don't like about RSpec is the amount of setup and gems required. Going with the built-in testing framework avoids all that, which is a definite win for beginners and quick-starting projects.
Be great to see your comparison of Minitest and RSpec.
How about headless browsers for system tests? I see you can switch to :headless_chrome
but do you still get screenshots with that? Also, is a headless browser any faster?
The answer turned out to be very simple, of course 😉
Use to_json
on the nested params in Ruby:
params = { str: "word", bool: true, hash: { number: 123 }.to_json }
which sends:
GET /test.js?str=word&bool=true&hash=%7B%22number%22%3A123%7D
resulting in hash
as {"number":123}
in JS.
Use regular JSON parse to get the value: JSON.parse(hash).number
gives 123
🎉
Sending data from Rails via HTTParty to a Node endpoint:
params = { str: "word", bool: true, hash: { number: 123 } }
result = HTTParty.get("http://localhost/test.js", query: params)
It works fine without the nested hash in the params but when I add that in things get hairy.
If I send it as shown above the Node endpoint receives:
GET /test.js?str=word&bool=true&hash[number]=123
and hash
is undefined
If I convert the nested hash to a string:
params = { str: "word", bool: true, hash: { number: 123 }.to_s }
then Node receives this:
GET /test.js?str=word&bool=true&hash=%7B%3Anumber%3D%3E123%7D
and hash
is available in JS as: "{:number=>123}"
but I can't figure out how to convert this to a JS object.
If I convert the nested hash to query:
params = { str: "word", bool: true, hash: { number: 123 }.to_query }
then Node receives this:
GET /test.js?str=word&bool=true&hash=number%3D123
and hash is available in JS as: "number=123"
.
How do I access that nested hash in JS? How do I convert it to a usable object? I don't mind which of the various ways to send the nested params, I just to access them easily.