Hi all very noob question.
I'm trying to store data in a react calendar but it needs to store it using JSON.
I've noticed that when you scaffold, rails automatically also gives you a JSON version.
In my case - http://localhost:3000/users/1/bookings.json
Which returns [{"first_name":"Fake Name","booking_time":"2019-04-22T02:03:00.000Z","pick_up_time":"2019-04-22T02:03:00.000Z"}] in JSON.
I know how to fetch JSON data from a external URL and parse it through however all these external URL's are public whereas in my case the bookings are private.
Is there a way for me to fetch from bookings.json and store it in a variable and also by making it private where I wouldn't need to publicise it?
class HomeController < ApplicationController
def dashboard
#lookup_booking = ???("/users/1/bookings.json")???
end
end
React dashboard
<%= react_component("Booking", { booking: #lookup_booking})%>
You could make a local request to the Bookings JSON endpoint the same way you'd make any external request - using something like HTTParty or Faraday might work:
#lookup_booking = HTTParty.get(user_bookings_url(1))
But this won't be authenticated, so it'll need the same authentication as any other request.
It's a little weird to do it this way unless whatever is generating the bookings is a separate service, or if you want it to be one. If you're going to be using one codebase, you might want to do something similar to what arieljuod suggested in the comments, and simply share the code.
You could break the BookingsController code into an ActiveSupport::Concern or a module, or a Service Object (or, more simply, a method on the User class) and that would then allow you to cleanly share the code between the BookingsController and HomeController. It might look something like this:
# app/services/lookup_user_bookings.rb
class LookupUserBookings
def self.bookings_as_json(user_id)
# complicated logic to find user bookings goes here...
bookings.as_json
end
end
# bookings_controller.rb
class BookingsController
def index
#bookings = LookupUserBookings.bookings_as_json(current_user)
render json: #bookings
end
end
# home_controller
class HomeController
def dashboard
#bookings = LookupUserBookings.bookings_as_json(current_user)
end
end
# dashboard.html.erb
<%= react_component("Booking", { booking: #bookings.to_json })%>
Related
I'm trying to learn processing JSON with Ruby. I went through quite a few tutorial but got confused even more, so I try to learn by doing it.
Here, I'm trying to get user's data from Instagram and display in my View. I have access to the JSON below but how do I reach certain fields and username or loop posts?
# users_controller.rb
require 'httparty'
class UsersController < ApplicationController
include HTTParty
def show
#user = User.find(params[:id])
fetch_instagram("elonofficiall")
end
private
def fetch_instagram(instagram_username)
url = "https://www.instagram.com/#{instagram_username}/?__a=1"
#data = HTTParty.get(url)
return #data
end
end
# show.html.erb
# the code below is just to try if I get anything
<%= #data %>
<% #data.each do |data| %>
<p><%= data %></p>
<% end %>
https://www.instagram.com/elonofficiall/?__a=1
First off don't do HTTP calls straight from your controller.
Instead create a separate class that "talks" to the instagram API:
# app/clients/instagram_client.rb
class InstagramClient
include HTTParty
base_uri 'https://www.instagram.com'
format :json
def initialize(**options)
#options = options
end
def user(username, **opts)
options = #options.reverse_merge(
'__a' => 1 # wtf is this param?
).reverse_merge(opts)
response = self.class.get("/#{username}", options)
if response.success?
extract_user(response)
else
Rails.logger.error("Fetching Instagram feed failed: HTTP #{response.code}")
nil
end
end
private
def extract_user(json)
attributes = response.dig('graphql', 'user')&.slice('id', 'biography')
attributes ? InstagramUser.new(attributes) : nil
end
end
And an class that normalizes the API response for consumption in your application:
# app/models/instagram_user.rb
class InstagramUser
include ActiveModel::Model
include ActiveModel::Attributes
attribute :id
attribute :biography
attribute :username
# etc
end
This is just a straight up Rails model that is not persisted in the database. ActiveModel::Model and ActiveModel::Attributes let you pass a hash of attributes for mass assignment just like you do with ActiveRecord backed models.
This gives you an object you can simply test by:
InstagramClient.new.feed('elonofficiall')
And you would integrate it into your controller like so:
class UsersController < ApplicationController
def show
#user = User.find(params[:id])
fetch_instagram(#user.instagram_username)
end
private
def fetch_instagram(instagram_username)
#instagram_user = InstagramClient.new.user(#user.instagram_username)
end
end
Mixing HTTParty into your controller is a straight up bad idea as controllers are tricky to test and the controller already has the responsibility of responding to client requests and does not need more.
Also processing a "raw" JSON response in your view is not a great practice as it creates a hard coupling between the external API and your application and adds far to much complexity to your views. Normalize the data first in a separate object.
<% if #instagram_user %>
<p><%= #instagram_user.username %></p>
<% end %>
If you want to actually get the media items from the users instagram feed you need to make another HTTP request to GET https://graph.facebook.com/{ig-user-id}/media. See the official API documentation.
Best to take it out of Rails first off. That only convolutes things. Many devs reply Rails === Ruby. It is not.
Write a simple ruby script and test her out:
rec = HTTParty.get(url)
puts rec.headers # content-type is 'text/html' and should be 'application/json'
res = rec.parsed_response # getting error
puts res.class
The issue with Instagram is that I think it's forcing a bad content-header back to the user. It might be a security layer. I can't seem to force the reply into json or a hash either. So that's why httparty is not working for me.
I have ruby on rails app and my controller should process request which creates many objects. Objects data is passed from client via json using POST method.
Example of my request (log from controller):
Processing by PersonsController#save_all as JSON
Parameters: {"_json"=>[{"date"=>"9/15/2014", "name"=>"John"},
{"date"=>"9/15/2014", "name"=>"Mike"}], "person"=>{}}
So i need to save these two users but i have some issues:
How to verify strong parameters here? Only Name and Date attributes can be passed from client
How can I convert String to Date if i use Person.new(params)?
Can i somehow preprocess my json? For example i want to replace name="Mike" to name="Mike User" and only then pass it in my model
I want to enrich params of every person by adding some default parameters, for example, i want to add status="new_created" to person params
First of all I'd name the root param something like "users", then it gives a structure that is all connected to the controller name and the data being sent.
Regarding strong params. The config depends of your rails app version. <= 3.x doesn't have this included so you need to add the gem. If you're on >= 4.x then this is already part of rails.
Next in your controller you need to define a method that will do the filtering of the params you need. I should look something like:
class PeopleController < ApplicationController
def some_action
# Here you can call a service that receives people_params and takes
# care of the creation.
if PeopleService.new(people_params).perform
# some logic
else
# some logic
end
end
private
def base_people_params
params.permit(people: [:name, :date])
end
# Usually if you don't want to manipulate the params then call the method
# just #people_params
def people_params
base_people_params.merge(people: normalized_params)
end
# In case you decided to manipulate the params then create small methods
# that would that separately. This way you would be able to understand this
# logic when returning to this code in a couple of months.
def normalized_params
return [] unless params[:people]
params[:people].each_with_object([]) do |result, person|
result << {
name: normalize_name(person[:name]),
date: normalize_date(person[:date]),
}
end
end
def normalize_date(date)
Time.parse(date)
end
def normalize_name(name)
"#{name} - User"
end
end
If you see that the code starts to get to customized take into a service. It will help to help to keep you controller thin (and healthy).
When you create one reason at the time (and not a batch like here) the code is a bit simpler, you work with hashes instead of arrays... but it's all pretty much the same.
EDIT:
If you don't need to manipulate a specific param then just don't
def normalized_params
return [] unless params[:people]
params[:people].each_with_object([]) do |result, person|
result << {
name: person[:name],
date: normalize_date(person[:date]),
}
end
end
I am working on a Rails API backend with a separate Rails/Angular front-end codebase. The responses from the Rails API must be structured in a certain way to match with the front-end flash messages. A(n) (slightly boiled down) example controller response is
render json: {status: "Unauthorized", code: "AUTH.UNAUTHORIZED", fallback_msg: "Unauthorized request"}
so basically all my controllers are full of this, and sometimes when there are 5 possible responses for a method (ex: if a user resets their email the response can be invalid password, invalid email, email is already registered etc). A coworker suggested abstracting these methods out into the model, so the model is response for sending back these messages and then the controller can just have
def ctrl_method
user = User.update(password: password)
render json: user, status(user)
end
(where status is another method that provides the HTTP status code based on the object's status attribute)
My question is is this best practice? I understand Rails MVC and feel like the responsibility of sending the json message belongs to the controller not the model.
I say you're both right. The controller should have the responsibility of sending the message, and the methods should be abstracted out--just not into the model, more like a class that lives in /lib.
This should make testing easier as well.
If you want to deal with ActiveRecord errors I think you use errors.full_messages and use the same code and status for such errors (status: 'Forbidden', code: '').
Note that you should customize your messages in locale files see guide. But it's useful if you want to translate your app in different languages.
Success messages can be inferred from the controller and the action (see below).
class ApplicationController < ActionController::Base
...
def render_errors(object)
render json: {status: 'Forbidden', code: 'WRONG_DATA', fallback_msg: object.errors.full_messages.join(", ")}
end
def render_success(message = nil)
message ||= I18n.t("#{self.controller_name}.message.#{self.action_name}"
render json: {status: 'OK', code: 'OK', fallback_msg: message}
end
end
class SomeController < ApplicationController
def update
if #some.update(params)
render_success
else
render_errors(#some)
end
end
end
Without knowing any more details, I would think about utilizing concerns to deal with the statuses. This allows business logic to be encapsulated without muddying up your models. So you could have
module Concerns
module Statuses
def deal_with_show
# business logic goes here to generate the status
end
def deal_with_index
# business logic goes here to generate the status
end
def deal_with_create
# business logic goes here to generate the status
end
def default_status
# set a default status here
end
end
end
and then in the controllers
class MyController < ApplicationController
include Concerns::Statuses
def index
render json: deal_with_index
end
end
Of course, that breakdown of statuses in the concern is arbitrary. It can be handled however makes sense: by method, by verb, or by some other distinction.
This is a good bit on concerns.
I have a Queries Controller which handles the results of an API request and I'm trying to pass that api object to another controller without having to persist the information to the database (the point of the app is returning a list of movies available in a certain zipcode and then allowing a user to view those results and create an event with friends around that movie, so there's no need to save the movie information in my database when the api call is made since it returns a lot of movies)
Here is my create method in the Queries Controller:
def create
#query = Query.new
#query.zip = query_params['zip']
#query.date = query_params['date']
#results = data(#query.zip, #query.date)
redirect_to results_path, :results => #results
end
and the results method which it gets passed to
def results
end
and then the corresponding Results view where I am just trying to display the results object:
<h3>Index</h3>
<%= results %>
MVC
My immediate thought is your thoughts are against the MVC programming pattern (on which Rails is based):
Controllers are meant to take a request from HTTP, and use it to manipulate the data on your screen through a model. The model is then able to provide the data you require, which can be passed to the view.
Sending requests inter-controller is against this pattern IMO - you'll be much better manipulating the data in a single controller action, and then pulling that data from the model.
Having said that, I think you are doing things relatively well, although you may wish to consider refactoring at system-level.
--
Fix
You should be able to pass your instance variable to your results action through the params object, as described by mjhlobdell:
#app/controllers/queries_controller.rb
Class QueriesController < ApplicationController
def query
...
redirect_to results_path(results: #results)
end
end
#app/controllers/results_controller.rb
Class ResultsController < ApplicationController
def results
#results = params[:results]
end
end
You should be able to use this to pass the data you need.
An alternative way would be to manipulate / render the response directly in the Queries create method, as here:
#app/controllers/queries_controller.rb
Class QueriesController < ApplicationController
def create
...
render :results
end
end
Try passing the results in the params hash to the results method
def create
#query = Query.new
#query.zip = query_params['zip']
#query.date = query_params['date']
#results = data(#query.zip, #query.date)
redirect_to results_path(:results => #results)
end
Then:
def results
#results = params[:results]
end
In the view:
<h3>Index</h3>
<%= #results %>
I have a user maintenance page. This page has a list of users where the admin can do bulk updates on the users he selects. Bulk updates include: activate, deactivate, and update roles to.
Should there be one URL I POST to, such as /users/bulk_update.json, where I then pass in the list of IDs and the method type. And in my bulk_update action, I update the IDs according to the method.
Or should there be a URL for each method, such /users/bulk_update_activate, /users/bulk_update_deactivate, and /users/bulk_update_roles?
The quick answers is: it depends! :)
If your updates type share many of the code logic.
1) Use filter:
class FirstController < ApplicationController
# Other controller code
before_filter :prepare_update
after_filter :finalize_update
def bulk_update_activate
# Do something here
end
def bulk_update_deactivate
# Do something here
end
end
2) Use a single action:
class SecondController < ApplicationController
# Other controller code
def bulk_update
case params[:operation]
when :activate then
# Do something here
when :deactivate then
# Do something here
end
end
end
If your updates are completely indipendent, then you should write different actions.
In my projects I usually find myself in using the first approach.
Hope it will be useful.