Activity
To make it as commenters
you'd need to set an alias on the users association on comments. Hope this helps. Sorry I don't have exact code for it.
@post.comments.users
would work if you had your comments has_many: users
association on your comment
model.
Pundit is my preference for this type of functionality. I've followed this tutorial and added the appropriate pundit policies on posts and comments along the way. +1 to pundit :)
I have fixed this issue but it requires quite a few changes. First of all, the cause is that the ajax is only rerendering the partial, which means the nesting value is not being incremented. That much is fairly obvious.
To fix this, I moved the max_nesting
into the Comment
model as a class variable. ie def self.max_nesting 3 end
. I then replace all references as Comment.max_nesting
. You can then move that part of the logic into the comments helper.
The second fix was to take the nesting value for the comment and add it as a field on the Comment model. So you know that the @comment.nesting
value is stored with the comment itself.
It is worth noting I have the paranoia/soft delete function set which I think has reduced my chances of the nesting becoming broken as comments are deleted.
In my comment controller, I am storing the comment nesting value through a Comment model method called set_nesting
. This increments from the parent comment OR sets it to 1.
comments_helper.rb
def reply_to_comment_id(comment, nesting)
nesting = 1 unless nesting.present?
max_nesting = Comment.max_nesting
if max_nesting.blank? || nesting < max_nesting
comment.id
else
comment.parent_id
end
end
end
comments_controller.rb
class CommentsController < ApplicationController
before_action :authenticate_user!
def create
@comment = @commentable.comments.new(comment_params)
@comment.nesting = @comment.set_nesting
@comment.user = current_user
if @comment.save
respond_to do |format|
format.html { redirect_to @commentable }
format.js
end
else
redirect_to @commentable, alert: "Something went wrong."
end
end
def destroy
@comment = @commentable.comments.find(params[:id])
@comment.destroy
redirect_to @commentable
end
def restore
@comment = @commentable.comments.with_deleted.find(params[:id])
@comment.restore
redirect_to @commentable
end
private
def comment_params
params.require(:comment).permit(:body, :parent_id)
end
end
comment.rb
class Comment < ApplicationRecord
acts_as_paranoid
belongs_to :user
belongs_to :commentable, polymorphic: true
belongs_to :parent, optional: true, class_name: "Comment"
validates :body, presence: true
validates_length_of :body, maximum: 140
def comments
Comment.with_deleted.where(commentable: commentable, parent_id: id).order(created_at: :asc)
end
def self.max_nesting
3
end
def set_nesting
if self.parent.present? && self.parent.nesting.present?
self.nesting = self.parent.nesting + 1
else
self.nesting = 1
end
end
end
_comment.html.erb
<div class="border-gray-300 border-l p-4 my-4 mt-2 ml-2">
<div class="flex"><%= comment.user.name %> says..</div>
<% if comment.deleted? %>
<div class="border-gray-300 border-l p-2 italic text-gray-500">
<%= simple_format "This comment has since been deleted..." %>
<div class="italic text-gray-500 text-sm">
<div class="flex">
<%= comment.created_at.strftime("%I:%M %p") %> • <%= comment.created_at.strftime("%d %b %y") %> <%= "~" + time_ago_in_words(comment.created_at) + " ago."%>
</div>
</div>
</div>
<% else %>
<div class="border-gray-300 border-l p-2">
<%= simple_format comment.body %>
<div class="italic text-gray-500 text-sm">
<div class="flex">
<%= comment.created_at.strftime("%I:%M %p") %> • <%= comment.created_at.strftime("%d %b %y") %> <%= "~" + time_ago_in_words(comment.created_at) + " ago."%>
</div>
</div>
</div>
<% end %>
<div class="mt-2" data-controller="reply">
<% if policy(comment).create? %>
<%= link_to "Reply", "#", class: "text-green-700", data: { action: "click->reply#show" } %>
<% end %>
<% if policy(comment).destroy? %>
<%= link_to "Delete", comment_path(comment, post_id: comment.commentable), method: :delete, class: "text-red-700", data: { confirm: "Are you sure?" } %>
<% end %>
<% if policy(comment).restore? %>
<%= link_to "Undo", restore_comment_path(comment, post_id: comment.commentable), method: :patch, class: "text-red-700", data: { confirm: "Restore this comment?" } %>
<% end %>
<%= render partial: "comments/form", locals: {
commentable: comment.commentable,
parent_id: reply_to_comment_id(comment, comment.nesting),
method: "post",
class: "hidden",
target: "reply.form"
} %>
</div>
<%= tag.div id: "#{dom_id(comment)}_comments" do %>
<%= render comment.comments, nesting: comment.nesting %>
<% end %>
</div>
create.js.erb
<% if @comment.parent_id? %>
var comments = document.querySelector("#<%= dom_id(@comment.parent) %>_comments")
<% else %>
var comments = document.querySelector("#comments")
<% end %>
comments.insertAdjacentHTML('beforeend', '<%=j render partial: "comments/comment", locals: { comment: @comment, nesting: @comment.nesting }, format: :html %>')
var form = comments.parentElement.querySelector("form")
form.reset()
<% if @comment.parent_id? %>
form.classList.add("hidden")
<% end %>
show.html.erb
<h2 class="title-2">Comments</h2>
<div class="mt-4">
<div id="comments">
<%= render partial: "comments/form", locals: {commentable: @post, method: "post" } %>
</div>
</div>
<%= render @comments %>
</div>
20200901061626_add_nesting_to_comments.rb
class AddNestingToComments < ActiveRecord::Migration[6.0]
def change
add_column :comments, :nesting, :integer
end
end
Hmmm tried this from a fresh rails 6 app.
cap production deploy
is failing with
/home/userme/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/sshkit-1.20.0/lib/sshkit/runners/parallel.rb:15:in `rescue in block (2 levels) in execute': Exception while executing as userme@123.456.789.10: rake exit status: 1 (SSHKit::Runner::ExecuteError)
$HOME/.rbenv/bin/rbenv exec bundle exec rake assets:precompile
Compiling…
Compilation failed:
error Command "webpack" not found.
Webpacker docs is saying Make sure you have public/packs and node_modules in :linked_dirs
for capistrano deployments. Then If you have node_modules added to :linked_dirs you'll need to run yarn install before deploy:assets:precompile
and they have a snippet to add to your deploy.rb
...
before "deploy:assets:precompile", "deploy:yarn_install"
namespace :deploy do
desc "Run rake yarn install"
task :yarn_install do
on roles(:web) do
within release_path do
execute("cd #{release_path} && yarn install --silent --no-progress --no-audit --no-optional")
end
end
end
end
Testing this now to see if it works... 10 mins later... nope... still busted... will try again later :/
I believe I may need to add capistrano-npm
to my Gemfile so npm install
can be run in the capistrano deploy scripts.
I really like that you come at it fairly 'bare bones'... as in, no Rspec / FactoryBot / Fixtures / Cucumber / Faker / shoulda-matchers /guard etc etc. I recall when learning for the first time, it was just so much to take in for testinig. Your PORO approach makes it so simple and not as intimidating.
I did and it worked pretty well. I'll hunt down a sample of the code and share back here soon. Might take me a while though as I am away for a few days. I'll dump a bit below but I don't have time to cut out the sensitive info.
It was a little more to it than I first thought it would be. Here is a bit of stimulus. Notice the stuff with the Rails.Ajax that is querying my rails controller and returning json. So a bit is needed to be done in the controller to respond to that ajax request and only return the json in a format for stimulus to use.
I'm running it all from a new.html.erb that is requesting to create and then returning a show partial which allows the dropdown to be updated based on the selection of the previous dropdown. It really needs a demo of the whole thing I put together in a more generic way than what I had done in my application (ie do a simple country / state / city selector from Rails api all the way through to stimulus).
import { Controller } from "stimulus"
export default class extends Controller {
static targets = [ "material", "price", "from", "query", "result", "button"]
initialize() {
console.log("Stimulus at your service!")
this.updateQueryParams()
this.toggleLoading()
}
get from () {
return this.targets.find("from").value
}
get material() {
return this.targets.find("material").value
}
get price() {
return this.targets.find("price").value
}
toggleLoading() {
this.targets.find("button").classList.toggle("is-loading")
}
updateToUnitOptions() {
this.clearResult()
Rails.ajax({
type: "GET",
url: "/alt_units.json",
data: "material=" + this.material,
success: (data) => {
console.log('Alt Units were got!')
this.refreshDropdownValues(data)
}
})
}
refreshDropdownValues(data) {
let fromBefore = this.from
this.fromTarget.innerHTML = ""
for(var i = 0; i < data.length; i++) {
var opt = data[i]
this.fromTarget.innerHTML += "<option value=\"" + opt.name + "\">" + opt.name + "</option>"
}
this.fromTarget.value = fromBefore
this.updateQueryParams()
}
clearResult() {
this.queryTarget.innerHTML = ""
}
}
Wow. I was just deep diving in the console and wondering why this
inside the callback was only referencing the Rails.ajax
object... OK! Back on the road again!
I am now at the point where the dropdown menu 'toTarget' options need to be updated. Thanks for your help.
updateToUnitOptions() {
const material = this.material
Rails.ajax({
type: "GET",
url: "/alt_units.json",
data: "material=" + material,
success: (data) => {
console.log('Alt Units were got!')
this.refreshDropdownValues(data)
}
})
}
refreshDropdownValues(data) {
// update a Stimulus Target
this.result = this.material
this.toTarget <<<<< here is where I need to update selection option values.
console.log(data)
}
```
Simply, all I am doing here is taking the material_id
from a select dropdown, and then eventually I want to update the next dropdown menu item with the filtered list of alt_units
that comes back from the alt_units_controller#index
from rails.
This is a follow up question from Populate dropdowns based on selection that I asked like... 3 years ago... and still haven't reallly done it very well.
Now that StimulusJS is here. Things seem more structured.
I have a stimulus controller below. After I complete the AJAX request, I want to send that data to another function just to keep my code clean. How do I call the this.doThingWithData(data)
in the Ajax success callback? All I get so far is a TypeError: this.doThingWithData is not a function
import { Controller } from "stimulus"
export default class extends Controller {
static targets = [ "material_id", "to_unit", "result"]
get material_id() {
return this.targets.find("material_id").value
}
updateToUnitOptions() {
const material_id = this.material_id
Rails.ajax({
type: "GET",
url: "/alt_units.json",
data: "material_id=" + material_id,
success: function(data) {
message()
this.doThingWithData(data)
}
})
this.resultTarget.innerHTML = "You have selected material: " + material_id
}
doThingWithData(data) {
// update a Stimulus Target
console.log(data)
}
}
function message() {
console.log('Alt Units were got!');
}
I am trying to dynamically render some links to the index action on a range of controllers.
Essentially just create a table of all my models with a count of their records and a link_to the index page as html.
How would I link_to_if the controller, action and format combination exist? I have currently tried the below but because the route is the same but with different formats (csv/json). html
doesn't exist so the link_to_if should fail and just render the model name.
resources_path GET /resourcess(.:format) resourcess#index {:format=>/(csv|json)/}
<%= link_to_if klass.name, controller: klass.name.pluralize.underscore, action: 'index' if Rails.application.routes.url_helpers.method_defined?(klass.name.pluralize.underscore + '_path') %>
<% Dir[Rails.root.join('app/models/*.rb').to_s].each do |filename| %>
<% klass = File.basename(filename, '.rb').camelize.constantize %>
<% next unless klass.ancestors.include?(ActiveRecord::Base) %>
<% next if klass.abstract_class? %>
<% next if configuration_tables.exclude? klass.name %>
<tr>
<td><%= link_to_if klass.name, controller: klass.name.pluralize.underscore, action: 'index' if Rails.application.routes.url_helpers.method_defined?(klass.name.pluralize.underscore + '_path') %></td>
<td><%= klass.count.to_s %></td>
</tr>
<% end %>
Posted in Setup Windows 10 Discussion
@eliot I looked into this a few months ago and just accepted that it wasn't going to go away unless WSL was changed by Microsoft. Details at: https://github.com/Microsoft/WSL/issues/1426.
There appears to be ways to get rid of it but could introduce a security issue... I decided it was probably a bit of OCD on my behalf and to just let it go. I just find chasing these bugs means hours lost where I could actually be writing code. If you do figure out a good way please tell though.
- Applicatoin instead of Application
- stumulus instead of stimulus.
- constrollers instead of controllers
- $ needed in .js regex
import { Applicatoin } from 'stimulus' import { autoload } from 'stumulus/webpack-helpers' const application = Application.start() const contsrollers = require.context("./controllers", true, /\.js$/ )
Even after I did this I had an error `TypeError: Object(...) is not a function`. I reviewed the stimulus installation guide and changed to:
import { Application } from "stimulus" import { definitionsFromContext } from "stimulus/webpack-helpers" const application = Application.start() const context = require.context("./controllers", true, /\.js$/) application.load(definitionsFromContext(context))
It looks like it should do the same thing. Funnily enough I tried
import { Application } from "stimulus" import { autoload } from "stimulus/webpack-helpers" const application = Application.start() const controllers = require.context("./controllers", true, /\.js$/) application.load(autoload(controllers))
And it had the same error (all I did was rename 'context' to 'controllers' and 'definitionsFromContext' to 'autoload'
Maybe the name 'controllers' has become reserved?
Posted in Setup Windows 10 Discussion
Is there anything wrong with just running `sudo apt install rbenv` instead of the shell script? I was having issues with the script. Only the plugins folder was cloned...
Ended up chasing errors most of the day. Windows + Vagrant + Chef for ruby 2.4 and rails 5.1 was not worth the hassle for me as I will be the only dev on this project. Ended up using Vagrant then ssh'ing in and DIY server provision by following the usual GoRails guide -> https://gorails.com/setup/u...
I'm working through this now. https://github.com/applicat... Looks like this issue was closed long ago but has reemerged. Will try to come back with a fix if I figure it out... which I will probably forget to do once I have figured it out :P
Thanks Jacob,
I did have a read of this and have implemented it in my app for now. Would be nice to be able to get the ActiveRecord query builder to also use the SQL 'AS' alias method too so that it asks for:
SELECT [dbo].[dimCustomer].[CustomerCode] AS [id] FROM [dbo].[dimCustomer];
At the moment it will still send
SELECT [dbo].[dimCustomer].[CustomerCode] FROM [dbo].[dimCustomer];
And in BetterErrors if I inspect the Customer object it still refers to methods as the unaliased name.
But works for now so Thank You.
I am thinking it will be some kid of alias method on the model that emulates SQL AS
Essentially I am hoping to be able to use customer.id
and rails knows I am meaning customer.CustomerCode
or when ActiveRecord runs the SQL it passes something like
SELECT [dbo].[dimCustomer].[CustomerCode] AS [id] FROM [dbo].[dimCustomer];
Hi
I have a rails app that sits over an existing SQL Server database that I have no control over. Purely read only access to.
I have a model like Customer with fields like CustomerCode, CustomerName etc. Which means ugly unconventional Customer.first.CustomerCode
and Customer.first.CustomerName
.
Is there a way in the app/models/customer.rb
to rename these columns so I can instead call Customer.first.id
and Customer.first.name
Hopefully this is very straightforward.
Thanks
Cheers that is what I ended up doing. Had to figure out how to create the two server blocks but ended up with all as follows. This was two apps on one server, one app called example and the other called foo with foo directed to subdomain foo.example.com but using the example.com certificate.
/etc/nginx/sites-available/example.com
server {
listen 80;
listen [::]:80;
server_name example.com;
passenger_enabled on;
passenger_ruby /home/deploy/.rbenv/shims/ruby;
rails_env production;
root /home/deploy/example/current/public;
# redirect server error pages to the static page /50x.html
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_session_timeout 5m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_dhparam /home/deploy/dhparams.pem;
}
/etc/nginx/sites-available/foo.example.com
server {
listen 80;
listen [::]:80;
server_name foo.example.com;
passenger_enabled on;
passenger_ruby /home/deploy/.rbenv/shims/ruby;
rails_env production;
root /home/deploy/foo/current/public;
# redirect server error pages to the static page /50x.html
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_session_timeout 5m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_dhparam /home/deploy/dhparams.pem;
}
then symlinked them in the sites-enabled directory
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/foo.example.com /etc/nginx/sites-enabled/
then ran the lets encrypt using certbot
sudo certbot certonly --webroot --webroot-path /home/deploy/sales_playbook/current/public --renew-by-default --email me@jaykilleen.com --text --agree-tos -d example.com -d foo.example.com