jsonapi_resource GET call not returning data - ruby-on-rails

I am new to rails. I have been trying to so a follow along project. Basically trying out CRUD API.
My routes.rb
Rails.application.routes.draw do
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
get '/', to: 'status#index'
jsonapi_resource :authors
end
ApplicationController.rb
class ApplicationController < ActionController::API
include JSONAPI::ActsAsResourceController
end
AuthorResource
class AuthorResource < JSONAPI::Resource
attributes :first, :last
end
Schema.rb
ActiveRecord::Schema.define(version: 2023_02_02_083747) do
create_table "authors", force: :cascade do |t|
t.string "first"
t.string "last"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
end
When I try to add authors via POST call, data is getting added to DB. But GET call returns a error response
POST CALL REQ/RES
{
"data":{
"type":"authors",
"attributes":{
"first":"Yuval",
"last":"Harari"
}
}
}
{
"data": {
"id": "4",
"type": "authors",
"links": {
"self": "http://localhost:3000/authors/4"
},
"attributes": {
"first": "Yuval",
"last": "Harari"
}
}
}
Response for GET call
{
"errors": [
{
"title": "Record not found",
"detail": "The record identified by could not be found.",
"code": "404",
"status": "404"
}
]
}
ERROR IN CONSOLE
ActionController::RoutingError (No route matches [GET] "/authors/4"):
Can anybody help me understand the problem please ? I am using rails v 6.1.7

Related

is there a way to render comments and replies inclusive of the user in ruby on rails

I am working on a commenting system, where the comments and replies are on the same table, with replies having a reply_to column set to the id of the parent comment
class CreateComments < ActiveRecord::Migration[7.0]
def change
create_table :comments do |t|
t.text :content
t.references :user, null: false, foreign_key: true
t.bigint :reply_to
t.integer :upvote_count
t.timestamps
end
end
end
In my comments_controller I have the following action and a helper function respectively to aid with querying the comments and replies inclusive of the user that created them.
class Api::CommentsController < ApplicationController
before_action :authenticate_user!, only: [:create, :destroy]
def index
#comments = Comment.where(reply_to: nil).includes(:user).order(created_at: :desc)
#comments = #comments.map do |comment|
comment.as_json.merge(replies: get_replies(comment.id))
end
render json: #comments, include: :user, status: :ok
end
private
# get all replies for a comment
def get_replies(comment_id)
Comment.where(reply_to: comment_id).includes(:user).order(created_at: :desc)
end
end
With this query, I am only getting the user included in the replies but not the parent comment as shown below
[
{
"id": 1,
"content": "hey this is a comment 1",
"user_id": 2,
"reply_to": null,
"upvote_count": null,
"created_at": "2022-09-06T19:00:38.996Z",
"updated_at": "2022-09-06T19:00:38.996Z",
"replies": [
{
"id": 3,
"content": "hey this is a reply to comment 1",
"user_id": 1,
"reply_to": 1,
"upvote_count": null,
"created_at": "2022-09-06T19:07:53.729Z",
"updated_at": "2022-09-06T19:07:53.729Z",
"user": {
"id": 1,
"provider": "email",
"uid": "madmax#user.name",
"allow_password_change": false,
"avatar": "0",
"email": "madmax#user.name",
"created_at": "2022-09-06T18:18:02.558Z",
"updated_at": "2022-09-07T21:53:13.804Z"
}
}
]
},
{
"id": 2,
"content": "hey this is a comment comment 2",
"user_id": 2,
"reply_to": null,
"upvote_count": null,
"created_at": "2022-09-06T19:04:14.433Z",
"updated_at": "2022-09-06T19:04:14.433Z",
"replies": []
}
]
My goal is to have the user included in both the parent comment and also the replies

Getting undefined method `sorted_by' when trying to use filterrific gem on a rails app

I'm trying to use the filterrific gem on a rails app to filter cities by a price lower than $1000 for example. - https://github.com/jhund/filterrific
but can't seem to set it up, i've added the code to the model and controllers but I get undefined method `sorted_by' for #<City::ActiveRecord_Relation:0x00007fc191173040> Did you mean? sort_by
Model -
class City < ApplicationRecord
has_one :guide, dependent: :destroy
filterrific(
default_filter_params: { sorted_by: 'created_at_desc' },
available_filters: %i[
sorted_by
search_query
with_created_at_gte
]
)
end
Controller -
class CitiesController < ApplicationController
def index
#cities = City.all
(#filterrific = initialize_filterrific(
City,
params[:filterrific]
)) || return
#cities = #filterrific.find.page(params[:page])
respond_to do |format|
format.html
format.js
end
end
Schema -
create_table "cities", force: :cascade do |t|
t.string "name"
t.string "internet"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "weather"
t.string "image"
t.string "country"
t.string "price"
end
It looks like you have copied and pasted the example from the documentation without really understanding what you are trying to do.
The error message is coming from your default_filter_params here:
filterrific(
default_filter_params: { sorted_by: 'created_at_desc' }, <<<
...
)
For this to work you need a sorted_by scope which takes a parameter 'created_at_desc'. There are examples in the documentation here: http://filterrific.clearcove.ca/pages/active_record_scope_patterns.html
An example for the sorted_by scope would be:
scope :sorted_by, (lambda do |sort_option|
direction = (sort_option =~ /desc$/) ? 'desc' : 'asc'
case sort_option.to_s
when /^created_at_/
order("cities.created_at #{ direction }")
when /^name_/
order("cities.name #{ direction }")
else
raise(ArgumentError, "Invalid sort option: #{ sort_option.inspect }")
end
end)
to filter by price you will also need a scope like so:
scope :with_price_lte, (lambda do |price|
where('price >= ?', price)
end)
so your model filterrific clause should look like:
filterrific(
default_filter_params: { sorted_by: 'created_at_desc' },
available_filters: %i[
sorted_by
with_price_lte
]
)
There's more to it as you have to have a filterrific form in your view which returns the parameters for your scopes and an index.js.erb view which updates your list of cities, but this should help you get a little further.

How to set attributes with different names than a DB schema

I am a newbie Ruby developer. I cannot figure out how to create an ActiveRecord model with different attributes names than defined in a DB schema
Consider the following schema
create_table "sync_tasks", force: :cascade do |t|
t.string "name"
t.string "path"
t.string "task_type"
t.string "status"
t.boolean "async", default: false
t.boolean "direct_download", default: true
t.datetime "created_at", null: false
t.datetime "completed_at"
t.datetime "updated_at", null: false
end
And I have the following payload
{
"name" : "Sync /var/www/",
"path" : "/var/www",
"directDownload": true,
"async" : false,
"taskType" : "directory"
}
And trying to create my model like that
class SyncTask < ApplicationRecord
TYPE_DB='db'
TYPE_FILE='file'
TYPE_DIRECTORY='directory'
def initialize(params)
# super
#task_type = params[:taskType]
#direct_download = params[:directDownload]
#path = params[:path]
#status = params[:status]
#async = params[:async]
end
end
When I try to save it throws an error
<NoMethodError: undefined method `[]' for nil:NilClass>
Also I am not able to access field like that
new_task = SyncTask.new(allowed_task_params)
new_task.task_type
It throws the following error
#<NoMethodError: undefined method `task_type' for #<SyncTask not initialized>>
In case I uncomment the super call it gives another error
#<ActiveModel::UnknownAttributeError: unknown attribute 'taskType' for SyncTask.>
What I am doing wrong ? How can I use different attributes names and initialize the model by myself ?
Thanks
You can transform the keys , for example:
=> payload = { "name": "Sync /var/www/", "path": "/var/www", "directDownload": true, "taskType": "directory" }
=> h = payload.transform_keys { |key| key.to_s.underscore } # only since v4.0.2
=> h = Hash[payload.map { |(k, v)| [k.to_s.underscore, v] }] # before v.4.0.2
#> {"name"=>"Sync /var/www/", "path"=>"/var/www", "direct_download"=>true, "task_type"=>"directory"}
=> new_task = SyncTask.new(h)
You shouldn't use the initialize method on AR models. If you still need to use initialize, use after_initialize hook. Because with the initialize we have to declare the super, so it is best to use the callback.

activerecord-postgis-adapter: Encode WKT

Using activerecord-postgis-adapter, how can I parse / encode wkt in results from database query?
I've got a simple model of Places:
class CreatePlaces < ActiveRecord::Migration[5.1]
def change
create_table :places do |t|
t.string :name
t.st_point :coords, :geographic => true
t.timestamps
end
change_table :places do |t|
t.index :coords, using: :gist
end
end
end
I get all places in following way:
class PlacesController < ApplicationController
def index
#places = Place.all
render json: #places.to_json
end
end
But my JSON in response contains WKT:
[
{
"id": 1,
"name": "test name 1",
"coords": "POINT (50.324192 19.037805)",
"created_at": "2017-09-07T20:29:19.203Z",
"updated_at": "2017-09-07T20:29:19.203Z"
}
]
I can map #places and encode coords like this:
class PlacesController < ApplicationController
def index
#places = Place.all
#places.map { |k,v| k.coords = RGeo::GeoJSON.encode(k.coords, json_parser: :json) }
render json: #places.to_json
end
end
And then I get what I wanted - encoded/parsed coords in GeoJSON form:
[
{
"id": 1,
"name": "test name 1",
"coords": {
"type": "Point",
"coordinates": [
50.324192,
19.037805
]
},
"created_at": "2017-09-07T20:29:19.203Z",
"updated_at": "2017-09-07T20:29:19.203Z"
}
]
Is it right way to encode POINT?
Add this code to one of your initializers:
RGeo::ActiveRecord::GeometryMixin.set_json_generator(:geojson)
See https://stackoverflow.com/a/6221804

Store multiple values in database

I have two tables in my SQLite database. On table called movies and a table called trailers. The movie table has a few columns,
create_table "movies", force: :cascade do |t|
t.string "title"
t.string "release_date"
t.string "image"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "movie_id"
t.string "imdb_rating"
end
I want to add trailers to my movies. I've tried that by adding a column called trailers into my movies table, and then store multiple results in 1 column. But that didn't look like the right way to go.
So I've created the trailers table.
create_table "trailers", force: :cascade do |t|
t.string "movie_id"
t.string "link"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
My idea was to save every trailer link with the movie_id value so I maybe I could merge the two tables in 1 JSON file that I could use in my Angular app.
To retrieve the trailer data I've created a service,
service.trailer = function(youtube_link){
return httpPromise(
baseUrl + youtube_link + '/videos?api_key=a8f7039633f2065942***a28d7cadad4&callback=JSON_CALLBACK'
)
};
The data returned from that service looks like this,
{"id":157336,"results":[
{"id":"53db3c790e0a26189a000d09","iso_639_1":"en","key":"ePbKGoIGAXY","name":"Trailer 3","site":"YouTube","size":1080,"type":"Trailer"},
{"id":"550df44b9251413554004d43","iso_639_1":"en","key":"KlyknsTJk0w","name":"Own it today","site":"YouTube","size":720,"type":"Trailer"},
{"id":"533ec6fcc3a3685448009ccc","iso_639_1":"en","key":"nyc6RJEEe0U","name":"Teaser","site":"YouTube","size":720,"type":"Trailer"},
{"id":"5376ab510e0a26141c0005a8","iso_639_1":"en","key":"zSWdZVtXT7E","name":"Trailer","site":"YouTube","size":720,"type":"Trailer"},
{"id":"545da247c3a3685362005187","iso_639_1":"en","key":"Lm8p5rlrSkY","name":"Trailer 2","site":"YouTube","size":1080,"type":"Trailer"}
]}
So now I'm trying to save the data into the trailer table.
var mappedData = dataYoutube.results.map(function(r) {
var obj = {}
obj["movie_id"] = dataYoutube.id;
obj["link"] = r.key
return obj;
});
console.log(mappedData);
createTrailer.create({
movie_id: mappedData.movie_id,
link: mappedData.key
})
And the createTrailer function in my service,
app.factory('createTrailer', ['$http', function($http){
return{
create: function(trailer){
return $http.post('/trailers.json', trailer);
}
};
}])
On the Rails backend I've created the routes,
resources :trailers, only: [:create, :destroy, :index, :show]
A trailers_controller.rb
class TrailersController < ApplicationController
def index
respond_with Trailer.all
end
def create
end
end
And a trailer_model.rb
class Trailer < ActiveRecord::Base
belongs_to :movie
end
Currently when I do the save action I get an error in my rails console,
Started POST "/trailers.json" for 127.0.0.1 at 2015-12-04 16:04:25 +0100
Processing by TrailersController#create as JSON
Parameters: {"movie_id"=>[{"movie_id"=>210577, "link"=>"Ym3LB0lOJ0o"}], "link"=>[{"movie_id"=>210577, "link"=>"Ym3LB0lOJ0o"}], "trailer"=>{"movie_id"=>[{"movie_id"=>210577, "link"=>"Ym3LB0lOJ0o"}], "link"=>[{"movie_id"=>210577, "link"=>"Ym3LB0lOJ0o"}]}}
User Load (10.9ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT 1 [["id", 1]]
Completed 500 Internal Server Error in 15ms (ActiveRecord: 17.1ms)
ActionView::MissingTemplate (Missing template trailers/create, application/create with {:locale=>[:en], :formats=>[:json], :variants=>[], :handlers=>[:erb, :builder, :raw, :ruby, :coffee, :haml, :jbuilder]}. Searched in:
* "/home/alucardu/sites/movieseat/app/views"
* "/home/alucardu/.rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/devise-3.5.2/app/views"
):
Based on your data above, you can make that an array of objects, each object having the movie_id and link column:
var mappedData = data.results.map(function(r) {
var obj = {}
obj["movie_id"] = data.id;
obj["link"] = r.key
return obj;
});
Fiddle: http://jsfiddle.net/muat8brp/

Resources