I'm trying to let Angular JS and Ruby on Rails communicate for sharing some data in an example application.
I generated a resource called Entries, and made this controller:
class EntriesController < ApplicationController
respond_to :json
def index
respond_with Entry.all
end
def show
respond_with Entry.find(params[:id])
end
def create
respond_with Entry.create(params[:entry])
end
def update
respond_with Entry.update(params[:id], params[:entry])
end
def destroy
respond_with Entry.destroy(params[:id])
end
end
This generates a response in json with the Entries data. I seeded the Entries database:
# This file should contain all the record creation needed to seed the database with its default values.
# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
#
# Examples:
#
# cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }])
# Mayor.create(name: 'Emanuel', city: cities.first)
Entry.create!(name: "name1")
Entry.create!(name: "name2")
Entry.create!(name: "name3")
Entry.create!(name: "name4")
Entry.create!(name: "name5")
Entry.create!(name: "name6")
Entry.create!(name: "name7")
I created some entries. I need to let Angular receive them. This is my js file:
var rafflerApp = angular.module('rafflerApp', ["ngResource"]);
rafflerApp.controller('RaffleCtrl', function ($scope, $resource) {
//entries list
Entry = $resource("/entries/:id", {id: "#id"}, {update: {method: "PUT"}})
$scope.entries = Entry.query();
//add a name to the list
$scope.addEntry = function(entry){
entry = Entry.save($scope.newEntry);
$scope.entries.push(entry);
$scope.newEntry = {}
}
//draw a winner
$scope.selectWinner = function(draw){
pool = [];
angular.forEach($scope.entries, function(entry){
if (!entry.winner){
pool.push(entry)
}
});
if (pool.length > 0){
entry = pool[Math.floor(Math.random()*pool.length)]
entry.winner = true
entry.$update(entry)
$scope.lastWinner = entry
}
}
});
If I try the application, I don't receive any js errors, but the list is empty. If I try to navigate to /entries/(casual :id), I receive this error:
ActionController::UnknownFormat in EntriesController#index
and it highlights this part of the code:
def index
respond_with Entry.all
end
Why did I receive this error? How can I let Ruby on Rails communicate data with Angular JS? I am also adding my routes file:
Rails.application.routes.draw do
resources :entries
root to: "raffle#index"
end
Update your route.rb to render JSON by default instead HTML
resources :entities, defaults: {format: :json}
Related
I have a button click that triggers the following function. It calls the below asynchronous request.
<script>
function buy_now(user_id, deal_id) {
var purchase_confirmed = confirm("Are you sure you want to make this purchase?");
var theUrl = "/orders/create?user_id=" + user_id + "&deal_id=" + deal_id;
if(purchase_confirmed){
var xmlHttp = new XMLHttpRequest();
xmlHttp.open( "GET", theUrl, true ); // true for asynchronous request
xmlHttp.send( null );
return xmlHttp.responseText;
}
}
</script>
orders_controller.rb
class OrdersController < ApplicationController
def create
#order = Order.new(user_id: orders_params[:user_id], deal_id: orders_params[:deal_id])
unless #order.save
url = "/deals/today_deal?user_id=" + orders_params[:user_id]
redirect_to url, status: :unprocessable_entity
end
end
private
def orders_params
params.permit(:user_id, :deal_id)
end
end
create.html.erb
<h1> Order successful </h1>
routes.rb
ails.application.routes.draw do
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
root 'deals#today_deal'
get '/deals', to: 'deals#index'
get '/deals/today', to: 'deals#today_deal'
get '/deals/new', to: 'deals#new'
get '/deals/:id', to: 'deals#show'
post '/deals/create', to: 'deals#create'
get '/orders/create', to: 'orders#create'
#set all incorrect paths to go to the root path
match '*path' => redirect('/'), via: :get
end
The problem is that once the create action(shown above) in the orders_controller is called and the subsequent view(shown above) is rendered the screen is not updated with the new view. I understand that I don't understand some concept here. It would great if you could point me to some resources to understand what I am doing wrong here. Thank you for your time.
In my rails application I have categories which are classified based on location. I want to cache these categories on serializer level by using jsonapi-serializer's caching method cache_options store: Rails.cache, namespace: 'jsonapi-serializer', expires_in: 3.hours. The issue here is that when I call it for the first time to retrieve categories in City A for example then later try to retrieve categories from City B it will still show City A's data. What I'm trying to do now is to pass city to serializer then add it to namespace using something like this namespace: "jsonapi-serializer/categories/#{:city}" in order to differentiate between them but I'm not able to find a way to do so yet.
Currently I have this block of code to retrieve categories based on location then serialize and render a json
category = Category.active_categories(headers['User-Location'])
unless category.empty?
generic_category = Category.all_category
categories = generic_category, category
render CategorySerializer.new(categories.flatten, { params: { user_location: headers['User-Location'] } })
end
While Category.active_categories and Category.all_category are caching the queries as below:
active_categories:
def self.active_categories(user_location)
Rails.cache.fetch("active_categories/#{user_location}", expires_in: 3.hours) do
Category.where(status: 'active')
.joins(:sellers).where(sellers: { status: 'active', is_validated: true })
.joins(sellers: :cities).where(cities: { name_en: user_location })
.joins(sellers: :products).where(products: { status: 'available' })
.where.not(products: { quantity: 0 }).with_attached_cover
.order(featured: :desc).uniq.to_a
end
end
all_category:
def self.all_category
Rails.cache.fetch('all_category', expires_in: 24.hours) do
Category.where(name_en: 'All').with_attached_cover.first
end
end
Found the solution already. Just override the caching method as the example below:
require 'active_support/cache'
class MySerializer
include JSONAPI::Serializer
cache_options(store: Rails.cache, namespace: 'jsonapi-serializer', expires_in: 3.hours)
def self.record_cache_options(options, fieldset, include_list, params)
return super(options, fieldset, include_list, params) if params[:user_location].blank?
opts = options.dup
opts[:namespace] += ':' + params[:user_location]
opts
end
end
Having some issues putting these puzzle pieces together... I'm scraping a website to get an array of strings and I want the array to get sent back to my React client for use. Here's what I have
index.js
componentDidMount() {
const { restaurant } = this.state
axios.post('/api/scraper', { restaurant: restaurant })
.then((res) => {
console.log(res.data);
})
}
app/controllers/api/scraper_controller.rb
class Api::ScraperController < ApplicationController
respond_to :json
def create
#info = helpers.get_info(params[:restaurant])
respond_with #info
end
end
app/helpers/api/scraper_helper.rb
module Api::ScraperHelper
def get_info(restaurant)
puts restaurant
require 'openssl'
doc = Nokogiri::HTML(open('http://www.subway.com/en-us/menunutrition/menu/all', :ssl_verify_mode => OpenSSL::SSL::VERIFY_NONE))
#items = []
doc.css('.menu-cat-prod-title').each do |item|
#items.push(item.text)
end
end
end
The whole idea is to get the #items array sent back to my axios request on my React page
Your actual code will just return 0, because the result of applying each in a Nokogiri::XML::NodeSet in this case is 0, and is what you're leaving as the last executed "piece of code" within your method, so Ruby will return this.
If you add #items in the last line, then this will be returned, and you'll get ["Black Forest Ham", "Chicken & Bacon Ranch Melt", ...] that I guess is what you need:
#items = []
doc.css('.menu-cat-prod-title').each { |item| #items.push(item.text) }
#items
Note you could also do a map operation on doc.css('.menu-cat-prod-title'), which can then be assigned to any instance variable:
def get_info(restaurant)
...
doc.css('.menu-cat-prod-title').map(&:text)
end
I guess to return the data from create you could use something like render json: { items: #items }, as items contains an array of menues.
I am sort of new to rails and I am trying to upload images directly to S3 with Shrine. I got direct uploads to S3 to work perfectly, however, when I introduced jquery file upload and upload an image, chrome console throws
this error
at me. I'm not sure what I'm doing wrong and I can't seem to find a solution anywhere online. I get that it's a presign error and it's probably not finding the cache link but I don't know how to resolve that.
EDIT: This was solved by including the presign code in the Routes file and altering the storage location in the uploads.js to the correct location. Now, however, I have an issue with the files being rolled back when they attempt to upload.
I'm using the cloud based ide C9,
This is my uploads.js file:
$(document).on("turbolinks:load", function(){
$("[type=file]").fileupload({
add: function(e, data) {
console.log("add", data);
data.progressBar = $('<div class="progress"><div class="determinate"
style="width: 70%"></div></div>').insertBefore("form")
var options = {
extension: data.files[0].name.match(/(\.\w+)?$/)[0], //set the
file extention
_: Date.now() //prevent caching
};
$.getJSON("/autos/upload/cache/presign", options, function(result) {
console.log("presign", result);
data.formData = result['fields'];
data.url = result['url'];
data.paramName = "file";
data.submit()
});
},
progress: function(e, data) {
console.log("progress", data);
var progress = parseInt(data.loaded / data.total * 100, 10);
var percentage = progress.toString() + '%'
data.progressBar.find(".progress-bar").css("width",
percentage).html(percentage);
},
done: function(e, data) {
console.log("done", data);
data.progressBar.remove();
var image = {
id: data.formData.key.match(/cache\/(.+)/)[1], // we have to
remove the prefix part
storage: 'cache',
metadata: {
size: data.files[0].size,
filename: data.files[0].name.match(/[^\/\\]+$/)[0], // IE return full
path
mime_type: data.files[0].type
}
}
form = $(this).closest("form");
form_data = new FormData(form[0]);
form_data.append($(this).attr("name"), JSON.stringify(image))
$.ajax(form.attr("action"), {
contentType: false,
processData: false,
data: form_data,
method: form.attr("method"),
dataType: "json"
}).done(function(data) {
console.log("done from rails", data);
});
}
});
});
My routes.rb file includes:
mount ImageUploader::UploadEndpoint => "/images/upload"
mount Shrine.presign_endpoint(:cache) => "/autos/upload/cache/presign"
I have a model which accepts these images as well as other fields called Autos, this is included in the Autos file:
include ImageUploader[:image]
My Autos Controller is:
class AutosController < ApplicationController
before_action :find_auto, only: [:show, :edit, :update, :destroy]
def index
#autos = Auto.all.order("created_at DESC")
end
def show
end
def new
#auto = current_user.autos.build
end
def create
#auto = current_user.autos.build(auto_params[:auto])
if #auto.save
flash[:notice] = "Successfully created post."
redirect_to autos_path
else
render 'new'
end
end
def edit
end
def update
if #auto.update(auto_params[:auto])
flash[:notice] = "Successfully updated post."
redirect_to auto_path(#auto)
else
render 'edit'
end
end
def destroy
#auto.destroy
redirect_to autos_path
end
private
def auto_params
params.require(:auto).permit(:title, :price, :description, :contact, :image, :remove_image)
end
def find_auto
#auto = Auto.find(params[:id])
end
end
Assuming your image_uploader.rb has the ImageUploader class defined and given that your presign endpoint is something like /autos/upload/cache/presign, your routes.rb should have the presign route defined like so:
mount ImageUploader.presign_endpoint(:cache) => '/autos/upload/cache/presign'
I hope this single change in the route file would make you able to get the presign object that should contain 3 keys: url, fields and headers
# GET /autos/upload/cache/presign
{
"url": "https://my-bucket.s3-eu-west-1.amazonaws.com",
"fields": {
"key": "cache/b7d575850ba61b44c8a9ff889dfdb14d88cdc25f8dd121004c8",
"policy": "eyJleHBpcmF0aW9uIjoiMjAxNS0QwMToxMToyOVoiLCJjb25kaXRpb25zIjpbeyJidWNrZXQiOiJzaHJpbmUtdGVzdGluZyJ9LHsia2V5IjoiYjdkNTc1ODUwYmE2MWI0NGU3Y2M4YTliZmY4OGU5ZGZkYjE2NTQ0ZDk4OGNkYzI1ZjhkZDEyMTAwNGM4In0seyJ4LWFtei1jcmVkZW50aWFsIjoiQUtJQUlKRjU1VE1aWlk0NVVUNlEvMjAxNTEwMjQvZXUtd2VzdC0xL3MzL2F3czRfcmVxdWVzdCJ9LHsieC1hbXotYWxnb3JpdGhtIjoiQVdTNC1ITUFDLVNIQTI1NiJ9LHsieC1hbXotZGF0ZSI6IjIwMTUxMDI0VDAwMTEyOVoifV19",
"x-amz-credential": "AKIAIJF55TMZYT6Q/20151024/eu-west-1/s3/aws4_request",
"x-amz-algorithm": "AWS4-HMAC-SHA256",
"x-amz-date": "20151024T001129Z",
"x-amz-signature": "c1eb634f83f96b69bd675f535b3ff15ae184b102fcba51e4db5f4959b4ae26f4"
},
"headers": {}
}
When upload starts, you will now find this object in developer console instead of the previous 404 not found error.
UPDATE
I think you are very close to the solution. In your create/update actions, use auto_params[:auto] instead of auto_params
You would also like to check the RoR guide on Association Basics for collection methods
I think you following the tutorial of gorails direct upload s3
in you gem file make sure you use the right roda version
gem 'roda', "~> 2.29.0"
I want to move the below logic to somewhere else so I can use it both in my controller and in a rake task.
My controller action looks something like this:
def show
#user = User.find(params[:id])
#account = # load account
#sales = # load sales
..
render :json => {
"user": user,
"account": #account.map do |a|
JSON.parse(a.to_json(include: :addresses))
end,
"sales": #sales.map do |s|
JSON.parse(s.to_json(include: :products))
end
}
end
Basically the point is that I have to traverse the associations so the JSON has all of the data in it.
How can I move this logic somewhere else so I can then call it in my controller action and also in a rake task.
Extract the code to a presenter or use ActiveModel::Serializers, so that the controller and the Rake task call this new class.
class UserPresenter
def initialize(user, account, sales)
#user = user
#account = account
#sales = sales
end
def as_json(*)
{
"user": #user,
"account": #account.map do |a|
JSON.parse(a.to_json(include: :addresses))
end, # or #account.as_json(include: :addresses))
"sales": #sales.map do |s|
JSON.parse(s.to_json(include: :products))
end # or #sales.as_json(include: :products))
}
end
end
# In the controller
render json: UserPresenter.new(#user, #account, #sales)