Access images in React through Rails Active Storage using JSON - ruby-on-rails

I'm making a Rails API where I would like to use Active Storage to attach images to posts and then be able to access them in REACT, such as getting the url link in JSON. How do I convert the Active Storage images to urls for JSON. Is there a better way to be able to get the link to the images for the posts?
For example, I would want the image url to be included in this information:
From: http://localhost:3001/api/posts
[{"id":8,"title":"lolol","body":"lolol","created_at":"2018-07-19T23:36:27.880Z","updated_at":"2018-07-20T00:17:50.201Z","admin_user_id":1,"post_type":"Song","link":"dgdadg","song_title":"dgdadg","tag_list":[]},{"id":13,"title":"ddd","body":"dd","created_at":"2018-07-20T00:21:39.903Z","updated_at":"2018-07-20T00:21:39.907Z","admin_user_id":1,"post_type":"Song","link":"dddd","song_title":"ddd","tag_list":["Tag"]}]
Here is my Post Controller :
class PostsController < ApiController
before_action :set_post, only: [:show, :update, :destroy]
def index
if params[:tag]
#posts = Post.tagged_with(params[:tag])
else
#posts = Post.all
end
render :json => #posts
end
def show
#post
render :json => #post
end
def create
#post = Post.new(post_params)
end
def update
#post.update(post_params)
head :no_content
end
def destroy
#post.destroy
head :no_content
end
private
def post_params
params.require(:post).permit(:title, :body, :song_title, :post_type, :admin_user_id, :link, :tag_list, :image)
end
def set_post
#post = Post.find(params[:id])
end
end
Any suggestions would be greatly appreciated.

Have been there like 2 weeks ago and decided against ActiveStorage because of the exact same issue. You can do some controller magic that uses
url_for(#post.image)
like
render :json => #post.merge({image: url_for(#post.image)})
or something along those lines
class Post
def image_url
Rails.application.routes.url_helpers.rails_blob_path(self.image, only_path: true)
end
end
# controller
render :json => #post.as_json(methods: :image_url)

It's possible within jbuilder to access the url_for helper. So your _post.json.jbuilder could work something like this
json.extract! post, :id, :body, :created_at, :updated_at
if post.image.attached?
json.image_url url_for(post.image)
end
json.url product_url(product, format: :json)

If your Post model has an ActiveStorage::Attachment you can just call
#post.file.service_url or #post.file.public_url assuming you have has_one_attached :file in your model.

Related

Friendly_id preventing edit/new pages due to before_action :find_post

I am using the friendly_id gem to handle URL Slugs and when applying a fix to avoid 404's when the slug changes from the documentation, my code doesn't work properly.
The problem is that it simply redirects to the post's show view when I click on the edit button and won't let me make a new post because it "can't find post with ID..." because it's using the find_post method.
I do have the friendly_id_slugs table to store history as well.
In my Post Model:
class Post < ApplicationRecord
extend FriendlyId
friendly_id :title, use: :slugged
...
def should_generate_new_friendly_id?
slug.nil? || title_changed?
end
end
Post Controller:
class PostsController < ApplicationController
before_action :find_post
...
def find_post
#post = Post.friendly.find(params[:id])
# If an old id or a numeric id was used to find the record, then
# the request path will not match the post_path, and we should do
# a 301 redirect that uses the current friendly id.
if request.path != post_path(#post)
return redirect_to #post, :status => :moved_permanently
end
end
end
I've tried using before_filter but asks me if I mean before_action and I've tried the find_post method in both the public & private section of my controller.
It sounds to me like you may want to skip that redirect logic for anything but the show action, since redirect_to #post only sends you to the show route.
def find_post
#post = Post.find params[:id]
if action_name == 'show' && request.path != post_path(#post)
return redirect_to #post, :status => :moved_permanently
end
end
Alternately, you can decouple the redirecting behavior from the pre-loading of the post with something like this:
before_action :find_post
before_action :redirect_to_canonical_route, only: :show
def find_post
#post = Post.find params[:id]
end
def redirect_to_canonical_route
if request.path != post_path(#post)
return redirect_to #post, :status => :moved_permanently
end
end

Editing a Wizard multistep form after save - Wicked Gem / Rails

I've been going round in circles all day with this. I have a large multi-step form using the Wicked gem and Ruby on Rails. It works perfectly but I can't figure out how to to get back into the form to edit individual entries.
Iim trying to make the ability to go into the client show page, click an individual client and then from there go back into the quote to edit and update it. As the Wicked gem only seems to work with show and update actions, if I try to build a standard edit action Wicked expects to be on a step therefore doesn't work.
I read the I would have to factor the edit action into my show/update actions but I'm having difficulties. Any help would be great thanks!
Clients Controller:
class ClientsController < ApplicationController
before_action :authenticate_user!, only: [:index, :show, :edit]
before_action :set_client, only: [:edit, :show, :update]
def index
#clients = Client.order('created_at DESC').paginate(page: params[:page], per_page: 10)
end
def show; end
def new
#client = Client.new
end
def edit; end
def update
if #client.update_attributes(client_params)
redirect_to client_quotes_path
flash[:success] = 'Client successfully updated'
else
render 'edit'
end
render_wizard #client
end
# After client is completed:
def create
#client = Client.new(client_params)
if #client.valid?
#client.save
session[:current_user_id] = #client.id
ClientMailer.new_client(#client).deliver
redirect_to quotes_path
else
flash[:alert] = 'Sorry, there was a problem with your message. Please contact us directly at ...'
render :new
end
end
private
def set_client
#client = Client.find(params[:id])
end
def client_params
params.require(:client).permit(:first_name, :last_name, :title, :email, :email_confirmation,
:phone, :time, :reminder, :ref_number, :day, :note, :logs_reminder)
end
end
Quotes Controller:
class QuotesController < ApplicationController
include Wicked::Wizard
before_action :set_client, only: [:show, :update, :quote_success]
steps :profile, :employment, :general_questions, :indemnity_details, :declarations
def show
#client.build_doctor unless #client.doctor.present?
#client.build_dentist unless #client.dentist.present?
#client.old_insurers.build
#client.practice_addresses.build
render_wizard
end
def update
#client.update(client_params)
render_wizard #client
end
def quote_success; end
private
def set_client
current_user = Client.find_by_id(session[:current_user_id])
#client = current_user
end
# After full quote form is completed:
def finish_wizard_path
if #client.valid?
ClientMailer.new_quote(#client).deliver
ClientMailer.new_quote_user_message(#client).deliver
end
quote_success_path
end
end
def client_params
params.require(:client).permit(:name, :email, :email_confirmation, :phone, :date_required,
:title, :first_name, :last_name, :date_of_birth, :nationality, :reg_body, :reg_date, :reg_type, :reg_number,
:qual_place, :qual_year, :post_grad, :membership ...
Routes:
Rails.application.routes.draw do
devise_for :users
root 'clients#new'
get 'client', to: 'clients#new', as: 'client'
post 'client', to: 'clients#create'
get '/client_quotes', to: 'clients#index', as: 'client_quotes'
get '/client_quotes/:id', to: 'clients#show', as: 'client_quote'
get '/client_quotes/:id/edit', to: 'clients#edit', as: 'edit_client_quote'
patch '/client_quotes/:id', to: 'clients#update'
put '/client_quotes/:id', to: 'clients#update'
resources :quotes, only: [:index, :show, :update, :quote_success]
get 'quote-success' => 'quotes#quote_success'
devise_scope :user do
get '/login' => 'devise/sessions#new'
end
end
My solution to this in the end was rather than have the edit form as a multi step wizard, I've joined the form data together in a separate view page and got a traditional route to it as you mention. Not perfect but does the job!
When you are updating it is like you are "editing" the element, so you need to redirect to the wizard when you want to edit and when you call the update method you really would be editing that entry. So call the wicked path.

i can't find whats undefined method "description" on my code

i am newbee want learn ruby on rails
i learn this code from tutorial on youtube.
i stuck to find undefined method `description'.
Here my code jobs_controller.rb
class JobsController < ApplicationController
before_action :find_job, only: [:show, :edit, :update, :destroy]
def index
#jobs = Job.all.order("created_at DESC")
end
def show
end
def new
#job = Job.new
end
def create
#job = Job.new(jobs_params)
if #job.save
redirect_to #job
else
render "New"
end
end
def edit
end
def update
end
def destroy #to delete
end
private
def jobs_params
params.require(:job).permit(:title, :description, :company, :url)
end
def find_job
#job = Job.find(params[:id])
end
end
Description is not an attribute of Job model/table and still you are using it in your form_for that's why it's giving undefined method description.
You haven't got a description on your job model. Run this command in your console:
rails g migration add_description_to_jobs description
'add_description_to_jobs' creates the migration file itself, and 'description' will create a string field 'description'. If you said 'description:integer' it would instead create it as an integer, but you won't need that in this instance.
What I can see is you are using simple simple_form_for with #job, and in the code you have
f.input :description, label: "Job Description"
So this error means your #job object is not having "description" attribute, which you have used with f.input, please check for that.

In Rails How to display errors in my comment form after I submit it?

I have a very straight-forward task to fulfil --- just to be able to write comments under posts and if the comments fail validation display error messages on the page.
My comment model uses a gem called Acts_as_commentable_with_threading, which creates a comment model after I installed.
On my post page, the logic goes like this:
Posts#show => display post and a form to enter comments => after the comment is entered, redisplay the Post#show page which has the new comment if it passes validation, otherwise display the error messages above the form.
However with my current code I can't display error messages if the comment validation fails. I think it is because when I redisplay the page it builds a new comment so the old one was erased. But I don't know how to make it work.
My codes are like this:
Comment.rb:
class Comment < ActiveRecord::Base
include Humanizer
require_human_on :create
acts_as_nested_set :scope => [:commentable_id, :commentable_type]
validates :body, :presence => true
validates :first_name, :presence => true
validates :last_name, :presence => true
# NOTE: install the acts_as_votable plugin if you
# want user to vote on the quality of comments.
#acts_as_votable
belongs_to :commentable, :polymorphic => true
# NOTE: Comments belong to a user
belongs_to :user
# Helper class method that allows you to build a comment
# by passing a commentable object, a user (could be nil), and comment text
# example in readme
def self.build_from(obj, user_id, comment, first_name, last_name)
new \
:commentable => obj,
:body => comment,
:user_id => user_id,
:first_name => first_name,
:last_name => last_name
end
end
PostController.rb:
class PostsController < ApplicationController
before_action :authenticate_user!, except: [:index, :show]
def show
#post = Post.friendly.find(params[:id])
#new_comment = Comment.build_from(#post, nil, "", "", "")
end
end
CommentsController:
class CommentsController < ApplicationController
def create
#comment = build_comment(comment_params)
respond_to do |format|
if #comment.save
make_child_comment
format.html
format.json { redirect_to(:back, :notice => 'Comment was successfully added.')}
else
format.html
format.json { redirect_to(:back, :flash => {:error => #comment.errors}) }
end
end
end
private
def comment_params
params.require(:comment).permit(:user, :first_name, :last_name, :body, :commentable_id, :commentable_type, :comment_id,
:humanizer_answer, :humanizer_question_id)
end
def commentable_type
comment_params[:commentable_type]
end
def commentable_id
comment_params[:commentable_id]
end
def comment_id
comment_params[:comment_id]
end
def body
comment_params[:body]
end
def make_child_comment
return "" if comment_id.blank?
parent_comment = Comment.find comment_id
#comment.move_to_child_of(parent_comment)
end
def build_comment(comment_params)
if current_user.nil?
user_id = nil
first_name = comment_params[:first_name]
last_name = comment_params[:last_name]
else
user_id = current_user.id
first_name = current_user.first_name
last_name = current_user.last_name
end
commentable = commentable_type.constantize.find(commentable_id)
Comment.build_from(commentable, user_id, comment_params[:body],
first_name, last_name)
end
end
comments/form: (this is on the Posts#show page)
<%= form_for #new_comment do |f| %>
<% if #new_comment.errors.any? %>
<div id="errors">
<h2><%= pluralize(#new_comment.errors.count, "error") %> encountered, please check your input.</h2>
<ul>
<% #new_comment.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<% end %>
I would instead use nested routes to create a more restful and less tangled setup:
concerns :commentable do
resources :comments, only: [:create]
end
resources :posts, concerns: :commentable
This will give you a route POST /posts/1/comments to create a comment.
In your controller the first thing you want to do is figure out what the parent of the comment is:
class CommentsController < ApplicationController
before_action :set_commentable
private
def set_commentable
if params[:post_id]
#commentable = Post.find(params[:post_id])
end
end
end
This means that we no longer need to pass the commentable as form parameters. Its also eliminates this unsafe construct:
commentable = commentable_type.constantize.find(commentable_id)
Where a malicous user could potentially pass any class name as commentable_type and you would let them find it in the DB... Never trust user input to the point where you use it to execute any kind of code!
With that we can start building our create action:
class CommentsController < ApplicationController
before_action :set_commentable
def create
#comment = #commentable.comments.new(comment_params) do |comment|
if current_user
comment.user = current_user
comment.first_name = current_user.first_name
comment.last_name = current_user.last_name
end
end
if #comment.save
respond_to do |format|
format.json { head :created, location: #comment }
format.html { redirect_to #commentable, success: 'Comment created' }
end
else
respond_to do |format|
format.html { render :new }
format.json { render json: #comment.errors, status: 422 }
end
end
end
private
# ...
def comment_params
params.require(:comment).permit(:first_name, :last_name, :body, :humanizer_answer, :humanizer_question_id)
end
end
In Rails when the user submits a form you do not redirect the user back to the form - instead you re-render the form and send it as a response.
While you could have your CommentsController render the show view of whatever the commentable is it will be quite brittle and may not even provide a good user experience since the user will see the top of the post they where commenting. Instead we would render app/views/comments/new.html.erb which should just contain the form.
Also pay attention to how we are responding. You should generally avoid using redirect_to :back since it relies on the client sending the HTTP_REFERRER header with the request. Many clients do not send this!
Instead use redirect_to #commentable or whatever resource you are creating.
In your original code you have totally mixed up JSON and HTML responses.
When responding with JSON you do not redirect or send flash messages.
If a JSON POST request is successful you would either:
Respond with HTTP 201 - CREATED and a location header which contains the url to the newly created resource. This is preferred when using SPA's like Ember or Angular.
Respond with HTTP 200 - OK and the resource as JSON in the response body. This is often done in legacy API's.
If it fails do to validations you should respond with 422 - Unprocessable Entity - usually the errors are rendered as JSON in the response body as well.
Added.
You can scrap your Comment.build_from method as well which does you no good at all and is very idiosyncratic Ruby.
class PostsController < ApplicationController
before_action :authenticate_user!, except: [:index, :show]
def show
#post = Post.friendly.find(params[:id])
#new_comment = #post.comments.new
end
end
Don't use line contiuation (\) syntax like that - use parens.
Don't:
new \
:commentable => obj,
:body => comment,
:user_id => user_id,
:first_name => first_name,
:last_name => last_name
Do:
new(
foo: a,
bar: b
)
Added 2
When using form_for with nested resources you pass it like this:
<%= form_for([commentable, comment]) do |f| %>
<% end %>
This will create the correct url for the action attribute and bind the form to the comment object. This uses locals to make it resuable so you would render the partial like so:
I'm assuming your form_for submits a POST request which triggers the HTML format in CommentsController#create:
def create
#comment = build_comment(comment_params)
respond_to do |format|
if #comment.save
make_child_comment
format.html
format.json { redirect_to(:back, :notice => 'Comment was successfully added.')}
else
format.html
format.json { redirect_to(:back, :flash => {:error => #comment.errors}) }
end
end
end
So, if #comment.save fails, and this is an HTML request, the #create method renders create.html. I think you want to render Posts#show instead.
Keep in mind that if validations fail on an object (Either by calling save/create, or validate/valid?), the #comment object will be populated with errors. In other words calling #comment.errors returns the relevant errors if validation fails. This is how your form is able to display the errors in #new_comment.errors.
For consistency, you'll need to rename #new_comment as #comment in the posts#show action, otherwise you'll get a NoMethodError on Nil::NilClass.
TL;DR: You're not rendering your form again with your failed #comment object if creation of that comment fails. Rename to #comment in posts, and render controller: :posts, action: :show if #comment.save fails from CommentsController#create
I have figured out the answer myself with the help of others here.
The reason is that I messed up with the JSON format and html format (typical noobie error)
To be able to display the errors using the code I need to change two places ( and change #comment to #new_comment as per #Anthony's advice).
1.
routes.rb:
resources :comments, defaults: { format: 'html' } # I set it as 'json' before
2.
CommentsController.rb:
def create
#new_comment = build_comment(comment_params)
respond_to do |format|
if #new_comment.save
make_child_comment
format.html { redirect_to(:back, :notice => 'Comment was successfully added.') }
else
commentable = commentable_type.constantize.find(commentable_id)
format.html { render template: 'posts/show', locals: {:#post => commentable} }
format.json { render json: #new_comment.errors }
end
end
end

Why does app go to the create.html.erb view after post submission instead of show.html.erb view? rails 4

Right now in my rails 4 app after the user submits a post I want the app to redirect/render the show view, but right now it just goes to the create view. Here is my Posts controller:
class PostsController < ApplicationController
def show
#post = Post.find(params[:id]) # show
end
end
def new
#post = Post.new
end
def create
#post = Post.new(post_params)
private
def post_params
params.require(:post).permit(:Title, :Body) #whatsoever your post has
end
#redirect_to post_path(#post)
if #post.save
redirect_to #post.find(params[:id])
else
render :new
end
end
Here are my routes:
Rails.application.routes.draw do
root :to => "pages#index"
devise_for :users
resources :users
resources :pages
resources :posts
end
Thanks for your help.
Looks like there's a few bits of code in wrong places - end statements, and your final if statement at the bottom, which doesn't seem to be in any method.
Something like this should work:
class PostsController < ApplicationController
def show
#post = Post.find(params[:id]) # show
end
def new
#post = Post.new
end
def create
#post = Post.new(post_params)
if #post.save
redirect_to #post.find(params[:id])
else
render :new
end
end
private
def post_params
params.require(:post).permit(:title, :body) #whatsoever your post has
end
end
First, the redirect is wrong. It should be simply:
redirect_to #post
After fixing that, it could be re-rendering because there was an error validating the #post. If #post.save fails the form will be re-rendered, but the URL will stay pointed at the create action.

Resources