I have to show data from this website: https://baconipsum.com/json-api/ , but I don't know where to write the code of it in my app. What code do I have to write for controllers and in views?
Setup faraday:
bacon = Faraday.new("https://baconipsum.com/") do |f|
f.response :json # automatically parse responses as json
end
Send a request in the controller:
#bacon = bacon.get("api/", type: 'all-meat', sentences: 1).body # => ["Leberkas frankfurter chicken tongue."]
Use it in the view:
<% #bacon.each do |meat| %>
<p>
<%= meat %>
</p>
<% end %>
https://lostisland.github.io/faraday/usage/
Update
There are many ways to set it up. Very simple set up could look like this:
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
private
# NOTE: this method will be accessible in any controller
# that inherits from ApplicationController
def baconipsum
# NOTE: memoize for a bit of performance, in case you're
# making multiple calls to this method.
#baconipsum ||= Faraday.new("https://baconipsum.com/") do |f|
f.response :json
end
end
end
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
def show
#article = Article.find(params[:id])
# I'm not quite sure what you're trying to do with it;
# change this to fit your use case.
#bacon = baconipsum.get("api/", type: 'all-meat').body
end
end
# app/views/articles/show.html.erb
<% #bacon.each do |meat| %>
<p> <%= meat %> </p>
<% end %>
Related
I was success to get data from external API in rails,
# products_controller.rb
def load_detail_product
#response = HTTParty.get("http://localhost:3000/api/v1/products/#{params[:id]}",:headers =>{'Content-Type' => 'application/json'})
#detail_products = #response.parsed_response
#product = #detail_products['data']['product']
#product.each do |p|
#product_id = p['id']
end
end
In view, when I want create update form I just do like this
<%= link_to 'Edit', edit_product_path(#product_id) %>
but when I call it to _form.html.erb Im geing this error
undefined method `model_name' for #<Hash:0x00007ffb71715cb8>
# _form.html.erb
<%= form_for #product, authenticity_token: false do |form| %>
<div class="field">
<%= f.label :name %>
<%= f.text_field :name %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
how I get data from external API and put it on form_for and update the data?
my response API
# localhost:3000/api/v1/products/1
{
"messages": "Product Loaded Successfully",
"is_success": true,
"data": {
"product": [
{
"id": 1,
"name": "Chair",
"price": "200000",
"created_at": "2022-03-22T09:24:40.796Z",
"updated_at": "2022-03-22T09:24:40.796Z"
}
]
}
}
# for update PUT localhost:3000/api/v1/products/1
{
"product":
{
"name":"Chair",
"price":20000
}
}
The main problem here is that you're not creating an abstraction layer between your application and the outside collaborator. By performing a HTTP query directly from your controller and passing the results straight to the view you're making a strong coupling between your application and the API and any changes to the collaborator can break large portions of your application. This creates a fat controller and pushes all the complexity of dealing with the API responses down into the view which both are very bad things.
I would start by actually creating a model that represents a the data in your application:
# app/models/product.rb
class Product
include ActiveModel::Model
include ActiveModel::Attributes
attribute :id
attribute :name
# ...
def persisted?
true
end
end
Note that it doesn't inherit from ApplicationRecord - this is a model thats not backed by a database table and instead just uses the API's that rails provide to make it quack like a model - this means it will work right out the box with forms:
<%= form_with(model: #product) do |f| %>
<div class="field">
<%= f.label :name %>
<%= f.text_field :name %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
The persisted? method tells the helpers that we updating a model and that it should route to product_path(id) and use PATCH.
But you also need to move the HTTP call out of the controller into a separate class:
# app/clients/products_client.rb
class ProductsClient
include HTTParty
base_url "http://localhost:3000/api/v1/products/"
format :json
attr_reader :response
# Get a product from the remote API
# GET /api/v1/products/:id
def show(id)
#response = self.class.get(id)
if #response.success?
#product = Product.new(product_params) # normalize the API data
else
nil # #todo handle 404 errors and other problems
end
end
# Send a PATCH request to update the product on the remote API
# PATCH /api/v1/products/:id
def update(product)
#response = self.class.patch(
product.id,
body: product.attributes
)
# #todo handle errors better
#response.success?
end
private
def product_params
#response['data']['product'].slice("id")
end
end
This isn't necissarily the only way or even the right way to write this class. The main point is just that you should not be burdoning your controller with more responsibilies. It has tons of jobs already.
This http client is the only component that should be touching the application boundy and have knowledge of the API. It can be tested in isolation and stubbed out when needed.
Your controller then "talks" to the API only though this client:
class ProductsController < ApplicationController
before_action :set_product, only: [:edit, :update] # ...
# GET /products/:id/edit
def edit
end
# PATCH /products/:id
def update
#product.assign_attributes(product_params)
if #product.valid? && ProductsClient.new.update(product)
redirect_to "/somewhere"
else
render :edit
end
end
private
def set_product
#product = ProductsClient.new.get(params[:id])
# resuse the existing error handling
raise ActiveRecord::RecordNotFound unless #product
end
def product_params
params.require(:product)
.permit(:name) # ...
end
end
I am trying display the task related to logged in user but on my html page nothing show except the tag data
task_controller.rb
class TaskController < ApplicationController
def all_task
if current_user.present?
#all_task = Task.find_by_user_id(#current_user.id)
render template: 'task/allTask'
end
end
end
routes.rb
get 'all_task' => 'task#all_task'
task.erb
<p>All Task</p>
<% if user_signed_in? %>
<%#all_task.daily_task %>
<%#all_task.date %>
<%#all_task.created_at %>
<%end %>
Start by setting up an assocation between users and tasks:
class User < ApplicationRecord
# ...
has_many :tasks
end
Then setup the route and controller:
get '/user/tasks', to: 'users/tasks#index', as: :user_tasks
# app/controllers/users/tasks_controller.rb
module Users
class TasksController < ApplicationRecord
before_action :authenticate_user!
# display all the tasks belonging to the currently signed in user
# GET /user/tasks
def index
#tasks = current_user.tasks
end
private
# You don't need this if your using Devise
def authenticate_user!
unless current_user
redirect_to '/path/to/your/login',
notice: 'Please sign in before continuing'
end
end
end
end
Note that when you have a route like this that displays resources that belong to the current user you should use a callback to bail early and redirect the user to sign in instead of using if current_user.present? and giving a response which is meaningless to the user. This code should be DRY:ed into your ApplicationController (even better yet is to not reinvent the auth wheel).
You can link to the users tasks with:
<% if current_user.present? %>
<%= link_to 'My tasks', user_tasks_path %>
<% end %>
In your view you need to iterate across the returned tasks:
# app/views/users/tasks/index.html.erb
<p>All Tasks</p>
<% if #tasks.any? %>
<% #tasks.each do |task| %>
<%= task.daily_task %>
<%= task.date %>
<%= task.created_at %>
<% end %>
<% else %>
<p>You don't have any tasks.</p>
<% end %>
You can cut duplication here by using partials.
Can you make sure if the instance variable #current_user is defined? If not, try the following:
class TaskController < ApplicationController
def all_task
if current_user.present?
#all_task = Task.find_by_user_id(current_user.id)
render template: 'task/allTask'
end
end
end
instead of
class TaskController < ApplicationController
def all_task
if current_user.present?
#all_task = Task.find_by_user_id(#current_user.id)
render template: 'task/allTask'
end
end
end
I'm working on messaging system between User and AdminUser. The User part is ready now I'm struggling how to allow Admin to send a reply to a conversation started by a User, inside of ActiveAdmin.
Code below:
# app/admin/conversations.rb
ActiveAdmin.register Conversation do
decorate_with ConversationDecorator
# ...
controller do
def show
super
#message = #conversation.messages.build
end
end
end
app/views/admin/conversations/_show.html.erb
# ...
<%= form_for [#conversation, #message] do |f| %>
<%= f.text_area :body %>
<%= f.text_field :messageable_id, value: current_user.id, type: "hidden" %>
<%= f.text_field :messageable_type, value: "#{current_user.class.name}", type: "hidden" %>
<%= f.submit "Send Reply" %>
<% end %>
<% end %>
Which gives me an error:
First argument in form cannot contain nil or be empty
Extracted source (around line #51):
51 <%= form_for [#conversation, #message] do |f| %>
When I tried to debug it turned out #message = nil inside of _show.html.erb. How is that possible if I defined #message inside of ActiveAdmin controller ?
[EDIT]
In case you're curious, ConversationController below:
class ConversationsController < ApplicationController
before_action :authenticate_user!
def index
#admins = AdminUser.all
#conversations = Conversation.all
end
def new
#conversation = Conversation.new
#conversation.messages.build
end
def create
#conversation = Conversation.create!(conversation_params)
redirect_to conversation_messages_path(#conversation)
end
end
#routes
resources :conversations do
resources :messages
end
Normally you set up instance variables in your controller, and then Rails later does an implicit render of the view once the controller method completes.
However, it is possible to do an explicit render of the view, by calling something like render action: or render template: while the controller method is running, and presumably this is happening within the call to super.
See the Layout and Rendering Rails Guide for more information.
You'll need to move the assignment to be before the call to super.
You may also need to replace #conversation with resource in the ActiveAdmin controller (this is an ActiveAdmin/InheritedResources gem thing).
I have a little bit of a newbie question.
I have a basic form with a dropdown list that looks like the following :
## apply.html.erb
<%= form_for #category do |f| %>
<%= f.label 'parent' , 'Category' %>
<%= f.select :category, [["foo", 0 ], ["bar", 1 ]] %>
<% end %>
The dropdown list values are "foo" and "bar".
I am trying to pull values directly from a database. The problem is that I have no idea how to organize the controller and the model.
Here is the controller :
## Welcome_controller.rb
class WelcomeController < ApplicationController
def index
end
def apply
#category = 'foobar'
end
end
I have not generated the controller yet. I can not find any convincing answers to my question or tutorial on the internet.
Any idea how I can make it happen?
** EDIT **
So I have been doing some edits.Here is what I have:
The view
## apply.html.erb
<%= form_for #category, as: :category do |f| %>
<%= f.label 'Categories' %>
<%= f.select :category, #category %>
<% end %>
The controller :
## welcome_controller.rb
def apply
#category = Category.new
#categories = Category.pluck(:id, :name)
end
The model :
## Category.rb
class Category < ApplicationRecord
end
I get the following in the terminal :
ActionView::Template::Error (undefined method `categories_path' for #<#<Class:0x007ffb7c5a2808>:0x007ffb7c2814f8>):
3: <div id="category_block">
4: <span>What would you like to get financed ?</span>
5:
6: <%= form_for #category, as: :category do |f| %>
7: <%= f.label 'Categories' %>
8: <%= f.select :category, #category %>
9: <% end %>
app/views/welcome/apply.html.erb:6:in `_app_views_welcome_apply_html_erb___747255529581383389_70359048261520'
It looks like the problem comes from #category = Category.new because when I replace Category.new with a string like ' foobar', the error disappears.
Any idea how to fix this problem?
You should read more about the documentation. This is a pretty basic question, and you'll probably have the answer if you actually take the time to read and learn.
That said, the info you provided is lacking so I'll just assume.
First, I assume you have a Category model. It should be placed in app/models/category.rb:
def Category
...
end
Next, you're not actually querying anything from the database. You can do it this way:
## app/controllers/welcome_controller.rb
class WelcomeController < ApplicationController
def index
end
def apply
#category = Category.new
#categories = Category.pluck(:name, :id)
end
end
Category.new generates a new instance of the Category (category.rb) object.
Category.pluck(:name, :id) generates this array: [['foo', 1], ['bar', 2]], where name and id are attributes of the Category model (just change it to your liking).
Once you have your array stored in #categories, you can use it in your form like this:
## app/views/welcome/apply.html.erb
<%= form_for #category do |f| %>
<%= f.label 'parent' %>
<%= f.select :category, #categories %>
<% end %>
You're probably not planning to actually create a form for a Category considering that you have a dropdown for categories so you should just change it to something else. Also, take note of the file names. They're all lowercase and separated by underscores. (Welcome_controller.rb should be welcome_controller.rb)
** EDIT **
Now that you've added an object to the form_for, rails will automatically assign a path for your form unless you change it. For this case, the path is categories_path. Read more here.
What you need to do is modify your routes.rb and add the following line:
resources :categories
That line will automatically generate routes that provide a mapping between HTTP verbs and URLs to controller actions.
Next you need to create a CategoriesController:
## app/controllers/categories_controller.rb
class CategoriesController < ApplicationController
def create
#category = Category.new(category_params)
if #account_user.save
redirect_to apply_path
else
render :new
end
end
private
def category_params
params.require(:category).permit! # MODIFY THIS
end
end
Anyway, this is basically a walkthrough and you should be figuring this out yourself. There are a lot of resources out there -- it's not hard to find one. You can also check this out: video.
I have an instance variable #posts_by_month defined in two controllers and is used in two views:
Posts controller:
class PostsController < ApplicationController
def index
#posts = Post.all
#posts_by_month = Post.all.group_by { |post| post.created_at.strftime("%L") }
respond_to do |format|
format.html # index.html.erb
format.json { render json: #posts }
end
end
.
.
end
Archives controller:
class ArchivesController < ApplicationController
def index
#posts_by_month = Post.all.group_by { |post| post.created_at.strftime("%B") }
end
end
Posts index view:
<h1>Listing posts</h1>
.
.
.
<div>
<% #posts_by_month.each do |millisecond, posts| %>
<p><%= millisecond %>milliseconds <%= posts.count %></p>
<% end %>
</div>
Archives index view:
<% #posts_by_month.each do |monthname, posts| %>
<%= monthname %>
<ul>
<% posts.each do |post| %>
<h3><%= post.title %></h3>
<p><%= post.body %></p>
<% end %>
</ul>
<% end %>
I have two questions. Is there any way I can define the #posts_by_month instance variable so that I can have it available to both views without repeating it in each respective controller?
Secondly, is there any way that the millisecond part of <p><%= millisecond %>milliseconds <%= posts.count %></p> can be made into a link that leads to the archive view?
Note: In my app millisecond will be replaced by month as in the archive view.
When an action was executed, aka rendered, the instance is over. There is no more instance variable.
The instance variables in View are not real instance variables. View and Controller are in different classes, how can they share instance? The reality is, what Rails does is to copy those instance variable from Controller instance to View instance exactly.
So the answer in your question is: No.
But you can still dry your code by a private method in application controller, to share with PostsController and ArchiveController.
class ApplicationController
private
def posts_by_time(arg)
Post.all.group_by { |post| post.created_at.strftime(arg) }
end
end
class PostsController < ApplicationController
def index
#posts = posts_by_time "%L"
# ...
end
end
class ArchievesController < ApplicationController
def index
#posts = posts_by_time "%B"
# ...
end
end
Yes you can reduce duplicacy of same variable. One way is to use filters:
Define a mthod inside application controllers:
class ApplicationController < ActionController::Base
private
def find_post_by_month
#posts_by_month = Post.all.group_by { |post| post.created_at.strftime("%L") }
end
end
Then inside archive and posts controllers:
class ArchivesController < ApplicationController
before_filter :find_post_by_month, :only => :index
...
end
class PostsController < ApplicationController
before_filter :find_post_by_month, :only => :index
...
end
This will give you value in #posts_by_month variable.
And, for making link of mentioned text, you should use this code:
<p><%= link_to "#{millisecond} milliseconds", path %></p> # Replace path with your url