Broadcasting to multiple turbo streams with hotwire - turbolinks

I've got hotwire/turbo wired up correctly to do crud operations on a single model on one place of my page, but I'd like to update the same model at the same time in a different location on my page as well. I thought I could just set up two streams but it doesn't seem to work.
Specifying a target does work for create action depending on how I name the target, but not for update and destroy. This is what I think should work but doesn't:
----location 1 ("creatures" stream)----
<div id="creatures">
<%= turbo_stream_from "creatures" %>
<%= turbo_frame_tag "creatures" do %>
<div>
<% #creatures.each do |creature| %>
<div>
<%= render "creatures/creature", creature: creature %>
</div>
<% end %>
</div>
<% end %>
</div>
----location 2 ("creatures_main" stream)----
<%= turbo_stream_from "creatures_main" %>
<%= turbo_frame_tag "creatures_main" do %>
<% #creatures.each do |creature| %>
<div>
<%= render "creatures/creature", creature: creature %>
</div>
<% end %>
<% end %>
---- common _creature.html.erb partial ----
<%= turbo_frame_tag dom_id(creature) do %>
<%= link_to creature.name, "#" %>
<% end %>
---- creature.rb ----
class Creature < ApplicationRecord
validates :name, presence: true
after_create_commit {
broadcast_append_to "creatures"
broadcast_append_to "creatures_main"
}
after_update_commit {
broadcast_replace_to "creatures"
broadcast_replace_to "creatures_main"
}
after_destroy_commit {
broadcast_remove_to "creatures"
broadcast_remove_to "creatures_main"
}
end
What happen when I have two calls in my model is that the create action puts the newly created creature in location 1 twice, only 1 of the two are updated,but both are destroyed correctly regardless of where on the page they are.

creatures and creatures_main would be the stream name. What you're looking for is the target and partial to control where the stream would look to update your data, and which partial it would use to update.
You can try:
after_create_commit -> {
# target here is the ID of the outer div, where data would be appended to
broadcast_append_to "creatures", target: "creatures"`
broadcast_append_to "creatures_main", target: "creatures_main"
}
after_update_commit {
# target here is the ID of each of the creature div
broadcast_replace_to "creatures", target: "creature_#{id}"
broadcast_replace_to "creatures_main", target: "creature_main_#{id}"
}
after_destroy_commit {
broadcast_remove_to "creatures"
broadcast_remove_to "creatures_main"
}
<%= turbo_frame_tag "creature_#{creature.id}" do %>
<%= link_to creature.name, "#" %>
<% end %>
<%= turbo_frame_tag "creature_main_#{creature.id}" do %>
<%= link_to creature.name, "#" %>
<% end %>
Of course this means you might have to use 2 different partials if you have the turbo_frame_tag inside the partial. You can do so like this:
after_update_commit {
# target here is the ID of each of the creature div
broadcast_replace_to "creatures", target: "creature_#{id}", partial: "creatures/creature", locals: {creature: self}
broadcast_replace_to "creatures_main", target: "creature_main_#{id}", partial: "creatures/creature_main", locals: {creature: self}
}
Btw, you should use the _later version of those methods. Also use render collection for easier reading.

Related

Ruby On Rails 7.0 invalid? set true in console but not in view

I´m trying to avoid to update an empty name for #post.
I´m beginner in RoR and I don´t understand why in terminal I got #post.invalid? => true but in my view edit.html.erb #post.invalid? => false
posts_controller.rb
class PostsController < ApplicationController
before_action :set_post, :only => [:edit, :show, :update, :destroy]
def index
#posts = Post.all
respond_to do |format|
format.html
format.json { render json: #posts }
end
end
def show
respond_to do |format|
format.html
format.json { render json: #post }
end
end
def edit
end
def update
if #post.update(post_params)
redirect_to posts_path, success: "Post updated"
else
puts #post.invalid? # write true
render 'edit'
end
end
def new
#post = Post.new
end
def create
post = Post.create(post_params)
redirect_to post_path(post.id), success: "Post created"
end
def destroy
#post.destroy
redirect_to posts_path, success: "Post deleted"
end
private
def post_params
params.require(:post).permit(:name, :content)
end
def set_post
#post = Post.find(params[:id])
end
end
post.rb
class Post < ApplicationRecord
validates :name, presence: true
def as_json(options = nil)
super(only: [:name, :id, :created_at] )
end
end
edit.html.erb
<h1>Editer l´article</h1>
<%= #post.invalid? %> <!-- write false -->
<% if #post.invalid? %> <!-- return false -->
<div class="alert alert-danger">
<% #post.errors.full_messages.each do |message| %>
<%= message %>
<% end %>
</div>
<% end %>
<%= form_for #post do |f| %>
<div class="form-group">
<label>Titre de l´article</label>
<%= f.text_field :name, class: 'form-control' %>
</div>
<div class="form-group">
<label>Contenu de l´article</label>
<%= f.text_area :content, class: 'form-control' %>
</div>
<div class="form-group">
<%= f.submit "Modifier l´article", class: 'btn btn-primary' %>
</div>
<% end %>
I´m confused, someone has got an idea ?
The methods valid? and invalid? all keep running the validation methods every time they are called, and therefore potentially change the state of the model.
If you want to just check for validity when validation has already been run, you should instead use #post.errors.present? or #post.errors.blank? which will never change the status, only read existing errors (that were added in your case when the call to update failed.
Additionally (even if it´s not the case here) calling valid? and invalid? without a context will clear out errors that had been added with validations like validate ..., on: :update.
The solution I found was a combination of #rewritten´s answer and this one.
So...
<h1>Editer l´article</h1>
<% if #post.errors.present? %>
<div class="alert alert-danger">
<% #post.errors.full_messages.each do |message| %>
<%= message %>
<% end %>
</div>
<% end %>
<%= form_for #post, data: { turbo: false} do |f| %>
<div class="form-group">
<label>Titre de l´article</label>
<%= f.text_field :name, class: 'form-control' %>
</div>
<div class="form-group">
<label>Contenu de l´article</label>
<%= f.text_area :content, class: 'form-control' %>
</div>
<div class="form-group">
<%= f.submit "Modifier l´article", class: 'btn btn-primary' %>
</div>
<% end %>

undefined method `accept_nested_attributes_for' for #<Class:0x007fc5844fd868>

I am using rails 4. I am trying add a nested form of address in user but its throwing error: undefined method `accept_nested_attributes_for' for #
User.rb
has_many :addresses
accept_nested_attributes_for :addresses, :allow_destroy => true
Address.rb
belongs_to :user
form template
<%= simple_form_for #user do |f| %>
<%= f.input :name, :hint =>(t "user.name_eg"), :label =>(t "user.name") %>
<% f.fields_for :addresses do |addr| %>
<p>
<div>
<%= addr.text_field :address %>
</div>
</p>
<% end %>
<% end %>
controller
#user = User.new
#user.addresses.build
Please help what am I missing. Thanks
The method is "accepts_nested_attributes_for". You can read about it here or here

Using article frontmatter when iterating in Middleman blog

Not the best title, but I'm honestly not sure on how to properly explain what I'm looking for help for.
So I'm using Middleman blog to well create my blog. Anyways, I'm using frontmatter to pass css that change the look of each page individually. I'm using 4 variables, link_color, text_color, bg_link. So what I want to do is reuse that same frontmatter information in the layout.html.erb file.
So the layout.html.erb is the standard
<% if paginate && num_pages > 1 %>
<p>Page <%= page_number %> of <%= num_pages %></p>
<% if prev_page %>
<p><%= link_to 'Previous page', prev_page %></p>
<% end %>
<% end %>
<% page_articles.each_with_index do |article, i| %>
<li class="article_summary">
<h1><%= link_to article.title, article, id: "#{i}" %></h1>
</li>
<% end %>
<% if paginate %>
<% if next_page %>
<p><%= link_to 'Next page', next_page %></p>
<% end %>
<% end %>
What I'm trying to do is for each article within that iterator is if the article has bg_color frontmatter then use that and change the color of the article.title if not, then do nothing. Currently if I try with something like:
<style>
<% if article.data.bg_color? %>
.article_summary a#<%= i %>{
color: rgb(<%=article.data.bg_color %>);
}
<% end %>
</style>
I'm doing it this way because my blog lives on Github.
Currently it works, but since it's just a simple iteration it gives every article that same color and not on a per article basis. So I'm trying to figure out the best way to utilize the index as some sort of id so that they're targeted individually.
Perhaps changing the li from a class to an id consisting of the index, but then I won't be able to apply a global style from the scss in the stylesheet folder no?
I've found a dirty method that works.
<% page_articles.each_with_index do |article, i| %>
<li class="article_summary" id="test_<%=i %>">
<h1><%= link_to article.title, article %></h1>
<style>
<% if article.data.bg_color? %>
#test_<%=i%> a{
color: <%=article.data.bg_color %>;
}
<% end %>
</style>
</li>
<% end %>
Pretty much added "test_" to the id (before I was just doing the index itself) and viola!

Improper form validation

I'm running into a strange issue. Here is the controller code regarding the flash:
def create
user = User.find_by(email: params[:email])
if user && user.authenticate(params[:password])
session[:user_id] = user.id
redirect_to root_path, notice: "Signed in!"
else
flash.now[:error] = 'Your email or password is incorrect!'
render "new"
end
end
Here is the associated view code:
<% if flash.now[:error] %>
<%= label_tag :email %>
<%= text_field_tag :email, nil, class: "error"%>
<%= label_tag :password %>
<%= password_field_tag :password, nil, class: "error" %>
<% else %>
<%= label_tag :email %>
<%= text_field_tag :email%>
<%= label_tag :password %>
<%= password_field_tag :password%>
<% end %>
The error class should result in the input fields turning red. However, they currently briefly flash red and then the page is rendered. What can I be doing better here?
Thanks
From the docs:
When you need to pass an object to the current action, you use now, and your object will vanish when the current action is done.
Entries set via now are accessed the same way as standard entries: flash['my-key'].
(see http://api.rubyonrails.org/classes/ActionDispatch/Flash/FlashHash.html#method-i-now)
Try removing the '.now' in your view and instead do
<% if flash[:error] %>

Dynamic css classes inside embedded ruby

A Persona has persona_id of either 1, 2 or 3. I want to assign a class type of either persona-1-button, persona-2-button or persona-3-button inside the embedded ruby. The following code is not working and I don't know why:
<% current_user.personas.each do |persona| %>
<% foo = persona.persona_id.to_s %>
<% bar = "persona-" + foo + "-button" %>
<%= link_to "Persona", persona_path(persona), class: "btn btn-medium bar" %>
<% end %>
I didn't do it the follwoing way because it seems you can't have a <%=%> inside a <%=%>:
<% current_user.personas.each do |persona| %>
<%= link_to "Persona", persona_path(persona), class: "btn btn-medium persona<%=persona.persona_id%>button" %>
<% end %>
You have it almost correct already.
The thing you need to realise is that when you're inside the <%/%> tags, you're in a Ruby context. That means, that the "..." creates a String inside which you can use regular Ruby string interpolation, like this:
<%= link_to "Persona", persona_path(persona), class: "btn btn-medium #{bar}" %>
You're placing a variable in Ruby context where you are bound by the rules of Ruby, not ERB. And in Ruby it's done using string interpolation:
<%= link_to "Persona", persona_path(persona), class: "btn btn-medium #{bar}" %>

Resources