Rails json response in emberjs - javascript

This is my serializer
class TestSerializer < ActiveModel::Serializer
attributes :post
def post
#post = user.joins(:post).select("user.name as name,post.content as content").where("user_id = ?",object.id)
end
end
How can i call this json response in emberjs model and in view.

You seem to have this a little backwards. If you want to serialize a Post, you should have a PostSerializer.
If you already have a Post model setup like the following,
class User < AR::Base
has_many :posts
end
class Post < AR::Base
belongs_to :user
end
create a serializer with:
rails g serializer post
rails g serializer user
This will look like
class PostSerializer < ActiveModel::Serializer
attributes :id, :title
has_one :user
end
In your controller, make sure you're setup:
class PostsController
respond_to :html, :json
def show
#post = Post.find(params[:id])
respond_with #post
end
end
Then, you're all set. Make a call to /posts/1.json and you'll get
{"post":{"id":1,"title":"the title","user":{"id":1,"name":"jesse"}}}

Related

Rails 6 Joins Tables using has_many relationships and accepts_nested_attributes_for

I've been around a few circles with this one.
I was having an issue adding more Regions to the Listing on update.
Now I cannot even get multiple Regions added to the Listing on create. If anyone can help me with the solution that would be great. A look over my code from fresh (experienced) eyes might notice what I'm doing that is stupid.
Two models: Listing and Region
Third model for joining: Regionalization
Models:
# app/models/listing.rb
class Listing < ApplicationRecord
has_many :regionalizations
has_many :regions, through: :regionalizations
accepts_nested_attributes_for :regionalizations, allow_destroy: true, reject_if: :all_blank
end
# app/models/region.rb
class Region < ApplicationRecord
has_many :regionalizations
has_many :listings, through: :regionalizations
end
# app/models/regionalization.rb
class Regionalization < ApplicationRecord
belongs_to :listing
belongs_to :region
end
Models and associations seem sound to me. I think the problem lies in the controller and or the nested form.
Controller Actions [note I'm using dashboard namespace for this controller]
class Dashboard::ListingsController < Dashboard::BaseController
def new
#listing = Listing.new
end
def create
#listing = Listing.new(listing_params)
#listing.user_id = current_user.id
#listing.regionalizations.build
if #listing.save
redirect_to dashboard_path, notice: "Your Listing was created successfuly"
else
render :new
end
end
def update
respond_to do |format|
if #listing.update(listing_params)
format.html { redirect_to edit_dashboard_listing_path(#listing), notice: 'Your Listing was successfully updated.' }
format.json { render :show, status: :ok, location: #listing }
else
format.html { render :edit }
format.json { render json: #listing.errors, status: :unprocessable_entity }
end
end
end
private
def listing_params
params.require(:listing).permit(:id, :name, :excerpt, :description, :email, :website, :phone_number, :user_id, :featured_image, :business_logo, :address, :category_id, :facebook, :instagram, :twitter, :status, :regionalization_id, gallery_images: [], regionalizations_attributes: [:id, :region_id, :listing_id, :_destroy])
end
end
dashboard/listings/_form:
<%= form_with(model: [:dashboard, listing], local: true) do |f| %>
<article class="card mb-3">
<div class="card-body">
<h5 class="card-title mb-4">Delivery Regions</h5>
<%= f.fields_for :regionalizations do |regionalizations_form| %>
<%= render 'regionalization_fields', f: regionalizations_form %>
<% end %>
<%= link_to_add_fields "Add Region", f, :regionalizations %>
</div>
</article>
<%= f.submit data: { turbolinks: false }, class: "btn btn-outline-primary" %>
<% end %>
_regionalization_fields.html.erb:
<p class="nested-fields">
<%= f.collection_select(:region_id, Region.all, :id, :name, {multiple: true}, {class: 'form-control'}) %>
<%= f.hidden_field :_destroy %>
<%= link_to "Remove", '#', class: "remove_fields" %>
</p>
error on validation when creating a new Listing:
Regionalizations region must exist
If I add this to the Regionalization table I can get the regionaliztion to work.
belongs_to :region, optional: true
Now my parameters only ever show one regionalization atribute unless I tell it to build 3 or 4.
Like so:
4.times do #listing.regionalizations.build end
I have used Steve Polito's guide to try get this working. I've not changed any of the javascript stuff or application_helper stuff.
The add and delete fields work fine on front end. The remove nested field works fine in the dB.
Am I missing something totally stupid here, please?
The only thing I can notice any different to a new nested field and one pulled in from the build method is the "Selected" tag is not on the new nested field added to the form.
Params on submit:
Started POST "/dashboard/listings" for ::1 at 2020-10-30 20:22:15 +0000
Processing by Dashboard::ListingsController#create as HTML
Parameters: {"authenticity_token"=>"shtfCS/cSj/w/I6S1tNey99L8TKf48Xj0GAOMsODU3l44o0pJdjucCteQXca496aosNCEp7sPD85UM4QO4jEnw==", "listing"=>{"name"=>"", "excerpt"=>"", "description"=>"", "category_id"=>"1", "email"=>"", "phone_number"=>"", "website"=>"", "address"=>"", "facebook"=>"#", "instagram"=>"#", "twitter"=>"#", "regionalizations_attributes"=>{"0"=>{"region_id"=>"14", "_destroy"=>"false"}}}, "commit"=>"Create Listing"}
I'm going to add in the applicaton_helper file taken from Steve's tutorial on nested forms. One of the comments makes mention about the dynamic ability of the code. It works (just not for me). I can achieve what I need on the create method by forcing a numbered loop. Just can't get the fields to add dynamically into the db.
# This method creates a link with `data-id` `data-fields` attributes. These attributes are used to create new instances of the nested fields through Javascript.
def link_to_add_fields(name, f, association)
# Takes an object (#person) and creates a new instance of its associated model (:addresses)
# To better understand, run the following in your terminal:
# rails c --sandbox
# #person = Person.new
# new_object = #person.send(:addresses).klass.new
new_object = f.object.send(association).klass.new
# Saves the unique ID of the object into a variable.
# This is needed to ensure the key of the associated array is unique. This is makes parsing the content in the `data-fields` attribute easier through Javascript.
# We could use another method to achive this.
id = new_object.object_id
# https://api.rubyonrails.org/ fields_for(record_name, record_object = nil, fields_options = {}, &block)
# record_name = :addresses
# record_object = new_object
# fields_options = { child_index: id }
# child_index` is used to ensure the key of the associated array is unique, and that it matched the value in the `data-id` attribute.
# `person[addresses_attributes][child_index_value][_destroy]`
fields = f.fields_for(association, new_object, child_index: id) do |builder|
# `association.to_s.singularize + "_fields"` ends up evaluating to `address_fields`
# The render function will then look for `views/people/_address_fields.html.erb`
# The render function also needs to be passed the value of 'builder', because `views/people/_address_fields.html.erb` needs this to render the form tags.
render(association.to_s.singularize + "_fields", f: builder)
end
# This renders a simple link, but passes information into `data` attributes.
# This info can be named anything we want, but in this case we chose `data-id:` and `data-fields:`.
# The `id:` is from `new_object.object_id`.
# The `fields:` are rendered from the `fields` blocks.
# We use `gsub("\n", "")` to remove anywhite space from the rendered partial.
# The `id:` value needs to match the value used in `child_index: id`.
link_to(name, '#', class: "add_fields", data: {id: id, fields: fields.gsub("\n", "")})
end
Your issue
Your error is telling you that at least one Regionalization record can't be saved because it doesn't have a region_id.
And adding belongs_to :region, optional: true is actually undermining your data integrity. You now have Regionalization records where the Listing is present, but not the Region, which defeats the purpose of the Regionalization join table. Plus, you could totally bloat your database with one-sided records in your join table.
Easy route
You need to reject Regionalizations that don't have BOTH a :listing_id and a :region_id
This:
accepts_nested_attributes_for :regionalizations, allow_destroy: true, reject_if: :all_blank
is not doing the job for you. The record isn't "all blank" because it has a :listing_id
Handling this in the model is doable:
# app/models/listing.rb
class Listing < ApplicationRecord
has_many :regionalizations
has_many :regions, through: :regionalizations
accepts_nested_attributes_for :regionalizations, allow_destroy: true, reject_if: :missing_keys
private
def missing_keys(attributes)
attributes['region_id'].blank? ||
attributes['listing_id'].blank?
end
end
v2 update
So for whatever reason the regionalizations.build will only send the first one through the params. If I make it a loop like in the question it will pass them all through.
Yes, #listing.regionalizations.build only builds 1 new record.
If you know exactly how many drop-downs you want to appear on the page, you can use your 4.times do #listing.regionalizations.build end code. This loads 4 new records into memory and Rails will find them as a collection when it runs this:
<%= render 'regionalization_fields', f: regionalizations_form %>
BUT f.fields_for is NOT a loop so the fact that this works is a bit mysterious to me.
If you are creating 4 child records, you should use f.fields_for 4 times.
You can do this by changing around your partials a bit:
class Dashboard::ListingsController < Dashboard::BaseController
def new
#listing = Listing.new
4.times { #listing.regionalization.build }
#regionalizations = #listing.regionalizations
end
...
def edit
#listing = Listing.find(params[:id])
4.times { #listing.regionalization.build }
# will include ALL existing regionalizations, and 4 new ones
#regionalizations = #listing.regionalizations
end
dashboard/listings/_form:
<%= form_with(model: [:dashboard, listing], local: true) do |f| %>
<article class="card mb-3">
<div class="card-body">
<h5 class="card-title mb-4">Delivery Regions</h5>
<%= render 'regionalization_fields', collection: #regionalizations %>
<%= link_to_add_fields "Add Region", f, :regionalizations %>
</div>
</article>
<%= f.submit data: { turbolinks: false }, class: "btn btn-outline-primary" %>
<% end %>
RENAME: _regionalization_fields.html.erb to _regionalizations_form.html.erb for clarity:
<!-- change the form handler's name so it doesn't conflict with the local variable 'regionalizations_form` -->
<%= f.fields_for regionalizations_form do |f_reg| %>
<p class="nested-fields">
<%= f_reg.collection_select(:region_id, Region.all, :id, :name, {multiple: true}, {class: 'form-control'}) %>
<%= f_reg.hidden_field :_destroy %>
<%= link_to "Remove", '#', class: "remove_fields" %>
</p>
<% end %>
If you need a variable number of Regionalizations, the best way to handle that is with some Rails AJAX and UJS. This would mean creating the form with only 1 new Regionalization record and having a button that says "Add another". The user clicks it and you add in another select field with everything you need, including the correct conventions to have the results posted to the params.
This is a whole other can of worms, but I still recommend (and so does Steve Polito) Ryan Bate's Railscast on nested forms
Better route (still)
You've opted for the "has_many / belongs_to :through" option, but if Regionalization is truly just a join table, you could simplify this with a has_and_belongs_to_many.
If you're unsure if you can go this route, consider this: if Regionalization needs no methods or other dB fields beyond the foreign key ID's, then you've added complexity with a model you don't need.
If Regionalization can be a more standard JoinTable, you can avoid some nesting complexity and allow Rails to do it for you.
If you can go this way, see this doc. You'll need to change your migration, but you can use the create_join_table to let Rails handle the naming and indexes for you. Rails will call this join table listings_regions instead of regionalizations, but you won't ever really need to reference it.
Here's how your models could look with a simple join table:
# app/models/listing.rb
class Listing < ApplicationRecord
has_and_belongs_to_many :regions
accepts_nested_attributes_for :regions, :dependent_destroy
end
# app/models/region.rb
class Region < ApplicationRecord
has_and_belongs_to_many :listings
end
# app/models/regionalization.rb can be deleted

Creating Multiple Iterations of JSON in Rails

New to Rails so I apologize if this is a silly question.
I currently have an application in Rails that renders JSON output to javascript framework 'jsTree', but I am only able to get one 'tree' worth of information because I am creating, deleting, etc. the nodes themselves in the tree using ajax pointed at the correct route.
I would like to be able to have a function that creates an entirely new set of JSON data for use with other individual trees (stated another way, upon creation of an object with a view containing the jsTree client-side code in the view, I would like to associate different JSON information with that object/tree).
If someone could explain the logic of how I might go about this or point me in the direction of a tutorial/other resources, that would be very helpful.
Controller for the JSON
class JsonsController < ApplicationController
before_action :set_json, only: [:show, :update, :destroy]
# GET /jsons
# GET /jsons.json
def index
#jsons = Json.all
render json: #jsons
end
# GET /jsons/1
# GET /jsons/1.json
def show
render json: #json
end
# POST /jsons
# POST /jsons.json
def create
#json = Json.new(json_params)
if #json.save
render json: #json, status: :created, location: #json
else
render json: #json.errors, status: :unprocessable_entity
end
end
# PATCH/PUT /jsons/1
# PATCH/PUT /jsons/1.json
def update
#json = Json.find(params[:id])
if #json.update(json_params)
head :no_content
else
render json: #json.errors, status: :unprocessable_entity
end
end
# DELETE /jsons/1
# DELETE /jsons/1.json
def destroy
#json.destroy
head :no_content
end
private
def set_json
#json = Json.find(params[:id])
end
def json_params
params.require(:json).permit(:text, :parent, :id)
end
end
Each dashboard object should have its own set of JSON but only one is created for the entire application at the moment via the controller above.
class DashboardsController < ApplicationController
before_action :authenticate_user!
# Requires user to be signed in
def index
#dashboards = Dashboard.all
end
def new
#dashboard = Dashboard.new
end
def edit
#dashboard = Dashboard.find(params[:id])
end
def create
#dashboard = Dashboard.new(dashboard_params)
#dashboard.save
if #dashboard.save
redirect_to :action => :index
else
render :action => new
end
end
def update
#dashboard = Dashboard.find(params[:id])
if #dashboard.update(dashboard_params)
redirect_to :action => :index
else
render 'edit'
end
end
def show
#dashboard = Dashboard.find(params[:id])
end
private
def dashboard_params
params.require(:dashboard).permit(:title, :description)
end
end

Updating rails partial with a collection through jquery

I have a form which allows users to post an update. Once the users post the update I would like the list of updates to refresh. To achieve this I'm using Ajax and jQuery and using Rails. I'm running into trouble while trying to get jquery to render the post feed partial though.
Here's the jquery I'm using
$(".microposts").html("<%= j render partial: 'shared/feed_item', collection: #feed_items %>")
At the moment, the feed just refreshes and displays nothing. I believe it's due to the way I'm trying to pass the #feed_items. What's the best way to pass that variable?
Someone asked for the controller;
class MicropostsController < ApplicationController
before_action :signed_in_user, only: [:create, :destroy]
before_action :correct_user, only: :destroy
def create
#micropost = current_user.microposts.build(micropost_params)
if #micropost.save
respond_to do |format|
format.html { redirect_to root_url }
format.js
end
else
#feed_items = []
flash[:error] = "Failed to create micropost!"
render 'static_pages/home'
end
end
def destroy
#micropost.destroy
flash[:success] = "Micropost deleted!"
redirect_to root_url
end
private
def micropost_params
params.require(:micropost).permit(:content)
end
def correct_user
#micropost = current_user.microposts.find_by(id: params[:id])
redirect_to root_url if #micropost.nil?
end
end
#feed_items needs to be defined somewhere in the controller. The # is a special symbol in Ruby that signifies an instance variable of the current class. If you define it somewhere else, it becomes an instance variable of that class.
Rails has some special magic that makes the instance variables of the controller available in the view. If it's not an instance variable on the controller, it won't work.
def create
#micropost = current_user.microposts.build(micropost_params)
if #micropost.save
#feed_items = #micropost.do_whatever_to_build_the_feed_items
respond_to do |format|
format.html { redirect_to root_url }
format.js
end
else
#feed_items = []
flash[:error] = "Failed to create micropost!"
render 'static_pages/home'
end
end

Rails 3, comments nested resource saving 1 instance 4 times

I have a strange problem. I have 2 models Issue, Comment. Comments is nested inside Issues so for that I have the create action in comments controller as follows:
def create
#issue = Issue.find(params[:issue_id])
#comment = #issue.comments.create!(params[:comment])
respond_to do |format|
if #comment.save
format.html { redirect_to #comment, notice: 'Comment was successfully created.' }
format.json { render json: #comment, status: :created, location: #comment }
format.js #create.js.erb
else
format.html { render action: "new" }
format.json { render json: #comment.errors, status: :unprocessable_entity }
end
end
end
And my create.js.erb:
var new_comment = $("<%= escape_javascript(render(:partial => #comment))%>").hide();
$('#comments').prepend(new_comment);
$('#comment_<%= #comment.id %>').fadeIn('slow');
$('#new_comment')[0].reset();
Issue.rb
class Issue < ActiveRecord::Base
attr_accessible :category, :description, :title
has_many :comments
end
Comment.rb
class Comment < ActiveRecord::Base
attr_accessible :body, :issue_id
belongs_to :issue
end
routes.rb
resources :comments
resources :issues do
resources :comments
end
Problem: When I create a comment for which is a form partial residing on views/issues/show.html.erb. The comment gets created 4 times in the db.
I couldn't locate what the problem was and whats causing it. Please help
First, I would build the associated comment:
#comment = #issue.comments.build(params[:comment])
And then I would save the comment instance
#comment.save
And also check the Javascript, maybe you are having some problems with event bubbling and the event is being triggered twice.
I actually was working on some old Rails version where the js files were put inside the /public/assets and that was the reason for that weird behaviour. I deleted all the files inside the /public/assets folder and the app works fine now.

Rails - handling an Ajax destroy call on a polymorphic association

I am trying to destroy a comment through Ajax. In my app, my comment model uses a polymorphic association. The comment deletes successfully (in the database) using the code below. However, when I call the destroy.js.erb, it doesn't do anything and I think the problem is that the dom_id doesn't match the HTML id, so it is not updating. I think I am experiencing the same thing that #mu_is_too_short articulated in the answer to this question. I need help with how to solve this though. I do not know if the solution involves a) somehow passing the local variable comment to the destory.js.erb or b) another solution.
routes.rb
resources :feeds do
resources :comments
end
destroy.js.erb
$('#<%= dom_id(#comment) %>')
.fadeOut ->
$(this).remove()
_comment.html.erb
<div id=<%= dom_id(comment) %> class="comment">
<em>on <%= comment.created_at.strftime('%b %d, %Y at %I:%M %p') %></em>
<%= link_to "Remove", [#commentable, comment], :method => :delete, :remote => true %>
<%= simple_format comment.content %>
</div>
feeds/show.html.erb
<div id="comments">
<%= render #comments %>
</div>
Comments controller
class CommentsController < ApplicationController
def create
#comment = #commentable.comments.new(params[:comment])
if #comment.save
respond_to do |format|
format.html { redirect_to #commentable }
format.js
end
else
render :new
end
end
def destroy
#comment = Comment.find(params[:id])
#commentable = #comment.commentable
if #comment.destroy
respond_to do |format|
format.html { redirect_to #commentable }
format.js
end
end
end
end
Feeds controller
class FeedsController < ApplicationController
def show
#feed = Feed.find(params[:id])
#commentable = #feed
#comments = #commentable.comments
#comment = Comment.new
end
end
By the looks of it, #comment will contain a comment when you get to destroy.js.erb, as you set it in the controller immediately before that, so I do not think it has anything to do with the referenced answer. I wonder if this is being caused by the lack of quotes around your id here: <div id=<%= dom_id(comment) %> class="comment">... I suspect if you correct this, and any other relevant HTML validation errors, it will start working.

Categories