Rails - How to generate a single Json with these 2 tables - ruby-on-rails

I have 2 tables, order and product_orders
they are related, in product_order it has the order_id
when I render the Json "render json: order", it comes out correct,
if I do "render json: p_order", it also outputs correct.
But I needed to generate the order json, with the items "quantity, unity_price and total from the Product_order table, how would I do that?
any suggestions or links for understanding/study?
def call_command
orders = Order.all
product_orders = ProductOrder.all
order = orders.map do |t|
build = { order_id: "#{t.id}",
created_at_id: "#{t.created_at}",
user_id: "#{t.user_id}",
desk_id: "#{t.desk_id}",
status: "#{t.status}",
subtotal: "#{t.subtotal}",
total: "#{t.total}"
}
end
p_order = product_orders.map do |p|
build = { #product_order_id: "#{p.id}",
# created_at_id: "#{p.created_at}",
order_id: "#{p.order_id}",
product_it: "#{p.product_id}",
quantity: "#{p.quantity}",
unity_price: "#{p.unit_price}",
total: "#{p.total}"
}
end
render json: order
end

Assuming that your Order and ProductOrder models are setup like this:
class Order < ActiveRecord::Base
has_many :product_orders
end
class ProductOrder < ActiveRecord::Base
belongs_to :order
end
You can use the includes in your render call, like the following:
#orders = Order.all
render json: #orders, include: ['product_orders']
You can also use the ActiveModel Serializers as #Benjamin suggested:
#orders.as_json(include: :product_orders)

Related

Returning resources with different types for rails Restful API

I am working on implementing a search endpoint with ruby based on a json request sent from the client which should have the form GET /workspace/:id/searches? filter[query]=Old&filter[type]=ct:Tag,User,WokringArea&items=5
The controller looks like this
class SearchesController < ApiV3Controller
load_and_authorize_resource :workspace, class: "Company"
load_and_authorize_resource :user, through: :workspace
load_and_authorize_resource :working_area, through: :workspace
def index
keyword = filtered_params[:query].delete("\000")
keyword = '%' + keyword + '%'
if filtered_params[:type].include?('User')
#users = #workspace.users.where("LOWER(username) LIKE LOWER(?)", keyword)
end
if filtered_params[:type].include?('WorkingArea')
#working_areas = #workspace.working_areas.where("LOWER(name) LIKE LOWER(?)", keyword)
end
#resources = #working_areas
respond_json(#resources)
end
private
def filtered_params
params.require(:filter).permit(:query, :type)
end
def ability_klasses
[WorkspaceAbility, UserWorkspaceAbility, WorkingAreaAbility]
end
end
respond_json returns the resources with a json format and it looks like this
def respond_json(records, status = :ok)
if records.try(:errors).present?
render json: {
errors: records.errors.map do |pointer, error|
{
status: :unprocessable_entity,
source: { pointer: pointer },
title: error
}
end
}, status: :unprocessable_entity
return
elsif records.respond_to?(:to_ary)
#pagy, records = pagy(records)
end
options = {
include: params[:include],
permissions: permissions,
current_ability: current_ability,
meta: meta_infos
}
render json: ApplicationRecord.serialize_fast_apijson(records, options), status: status
end
Now the issue is the response is supposed to look like this:
{
data: [
{
id: 32112,
type: 'WorkingArea'
attributes: {}
},
{
id: 33321,
type: 'User',
attributes: {}
},
{
id: 33221,
type: 'Tag'
attributes: {}
}
How can I make my code support responding with resources that have different types?
You can define a model, not in your database, that is based on the results from the API. Then you include some of the ActiveModel modules for more features.
# app/models/workspace_result.rb
class WorkspaceResult
include ActiveModel::Model
include ActiveModel::Validations
include ActiveModel::Serialization
attr_accessor(
:id,
:type,
:attributes
)
def initialize(attributes={})
filtered_attributes = attributes.select { |k,v| self.class.attribute_method?(k.to_sym) }
super(filtered_attributes)
end
def self.from_json(json)
attrs = JSON.parse(json).deep_transform_keys { |k| k.to_s.underscore }
self.new(attrs)
end
end
Then in your API results you can do something like:
results = []
response.body["data"].each do |result|
results << WorkspaceArea.from_json(result)
end
You can also define instance methods on this model, etc.

How to render a dependency(project.tasks) in a two model (project + task) json result?

Given that each project has_many :tasks, I hope to render the project.task within the json result.
However, the json output also include a list of individual tasks as part of the result. See below:
#tasks = Task.all.reject do |i|
i.project.inbox == false || i.completion_status == 100
end
#projects = Project.all.reverse.reject do |i|
i.inbox == true || i.completion_status == 100
end
#all = #tasks + #projects
respond_to do |format|
format.html
format.json { paginate json: #all.sort_by(&:created_at).reverse,
per_page: 25 }
end
This means that if I simply include:
respond_to do |format|
format.html
format.json { paginate json: #all.sort_by(&:created_at).reverse,
:include => [:tasks => {:only => :id}],
per_page: 25 }
end
Rails will throw an error of undefined method tasks for Task:0x007fa0ad8d3858 since tasks does not have a task method.
How can I have the project.tasks appear in a json result which also include individual tasks result? Thank you.
Consider using active_model_serializers gem. After installing you can define a serializer for Project model like so:
class ProjectSerializer < ActiveModel::Serializer
attributes :id, :created_at, :tasks
def tasks
object.tasks.map(&:id)
end
end
Note: There might be any attributes you need. It's just an example.
Then you can do:
#projects = Project.all.reverse.reject do |i|
i.inbox == true || i.completion_status == 100
end
serialized_projects = ActiveModelSerializers::SerializableResource.new(#projects, each_serializer: ProjectSerializer).as_json
It will return you an array:
[{:id => 1, :created_at => "2017-07-13 08:13:20", tasks => [1, 2, 3, ...]}, ...]
Then for json response you can concat #tasks and serialized_projects:
all_for_json = #tasks + serialized_projects
And finally you can sort it like this:
all_for_json.sort_by { |record| record[:created_at] }.reverse
Note that you should do exactly record[:created_at], because projects are hashes, not active record models.
But I don't think this is a good idea to mix hashes and active record models in one array. So there is another solution.
You can also define a serializer for Task model:
class TaskSerializer < ActiveModel::Serializer
attributes :id, :created_at
end
Note: There might be any attributes you need. It's just an example.
And override code like this:
#tasks = Task.all.reject do |i|
i.project.inbox == false || i.completion_status == 100
end
#projects = Project.all.reverse.reject do |i|
i.inbox == true || i.completion_status == 100
end
respond_to do |format|
format.html do
#all = #tasks + #projects
end
format.json do
serialized_tasks = ActiveModelSerializers::SerializableResource.new(#tasks, each_serializer: TaskSerializer).as_json
serialized_projects = ActiveModelSerializers::SerializableResource.new(#projects, each_serializer: ProjectSerializer).as_json
all_serialized = serialized_tasks + serialized_projects
paginate json: all_serialized.sort_by { |record| record[:created_at] }.reverse, per_page: 25
end
end
To DRY your code, you can put
ActiveModelSerializers::SerializableResource.new(...).as_json
to separate method. For example:
def serialize_collection(collection, each_serializer)
ActiveModelSerializers::SerializableResource.new(collection, each_serializer: each_serializer).as_json
end
And do serializations like this:
serialized_tasks = serialize_collection(#tasks, TaskSerializer)
serialized_projects = serialize_collection(#projects, ProjectSerializer)
Profits of this solution:
You don't mix active record models and hashes in one array.
You can easily define via serializers which attributes and associations to include and set custom names for them.

how to use render json: with active-model-serializers gem?

i use gem 'active_model_serializers', '~> 0.10.0' for formart json with gem versionist for version api manager
i write clone controller for export json like this :
#app/v1/products_controller
class V1::ProductsController < V1::BaseController
start_offset = params[:offset]
max_products = Product.all.size
products = Product.all.limit(Settings.limit_products_json).offset start_offset
next_number = start_offset.to_i + Settings.limit_products_json
if next_number < max_products
render json: {
products: products,
next_products: Settings.next_products_json + next_number.to_s,
product_end: Settings.product_end_false_json
}
else
render json: {
products: products,
product_end: Settings.product_end_true_json,
product_end_reason: Settings.product_end_reason_json
}
end
end
and in serializers folder i write:
#serializers/v1/product_serializer.rb
class V1::ProductSerializer < ActiveModel::Serializer
attributes :id, :name
end
and result is all attributes of Product to json. But I only want limit result of product to :id and :name as i wrote in class V1::ProductSerializer. How can i do that? Sorry for my bad english!
As far as I know active_model_serializers does not support versioning out of box. Either rename your serializer to ProductSerializer or specify each_serializer option explicitly and put your other parameters in meta:
meta = if next_number < max_products
{
next_products: Settings.next_products_json + next_number.to_s,
product_end: Settings.product_end_false_json
}
else
{
product_end: Settings.product_end_true_json,
product_end_reason: Settings.product_end_reason_json
}
end
render json: products, each_serializer: V1::ProductSerializer, meta: meta

Change Json output, return `icd3_code_id` as `:id`

At the time my controller looks like this:
def search
#icd4 = Icd4Code.search_full(params[:search]).first(20)
render json: { icd: #icd4.as_json(:only => [:bezeichnung, :nummer, :id])}
end
What i would like to change is that my code does not return #icd4.id as :id but instead #icd4.icd3_code_id as :id
So render json: { icd: #icd4 } would look like this:
{"icd":[{"id":6,"nummer":"A00.1","bezeichnung":"Cholera","icd3_code_id":3,"created_at":"2014-02-28T19:38:20.530Z","updated_at":"2014-02-28T19:38:20.530Z"},{"id":7,"nummer":"A00.1","bezeichnung":"El-Tor-Cholera","icd3_code_id":3,"created_at":"2014-02-28T19:38:20.533Z","updated_at":"2014-02-28T19:38:20.533Z"}]}
My actual code render json: { icd: #icd4.as_json(:only => [:bezeichnung, :nummer, :id])} returns this:
{"icd":[{"id":6,"nummer":"A00.1","bezeichnung":"Cholera"},{"id":7,"nummer":"A00.1","bezeichnung":"El-Tor-Cholera"}]}
And i would like this output:
{"icd":[{"id":3,"nummer":"A00.1","bezeichnung":"Cholera"},{"id":7,"nummer":"A00.1","bezeichnung":"El-Tor-Cholera"}]}
How can i achieve this? Thanks
Without a serializer you can iterate through the items and their keys and rename the key when you find yours.
#icd4 = Icd4Code.search_full(params[:search]).first(20)
data = #icd4.as_json(:only => [:bezeichnung, :nummer, :icd3_code_id]).tap do |icd4_json|
icd4_json.each do |icd4_item|
icd4_item.each do |key|
icd4_item[ 'id' ] = icd4_item.delete( key ) if key == 'icd3_code_id'
end
end
end
render json: { icd4: data }
You should definitely take a look at draper and active_model_serializers gems.
http://railscasts.com/episodes/409-active-model-serializers
http://railscasts.com/episodes/286-draper
although I use Draper in a bit different way then Ryan Bates does, usually I do something like this:
render json: item.decorate.as_json
for example as a simplest solution you could have this class:
class Icd4CodeDecorator < Draper::Decorator
decorates :icd4_code
delegate_all
def as_json(options={})
{
id: icd3_code_id,
bezeichnung: bezeichnung,
nummer: nummer
}
end
end
and then in your controller you could just do:
render json: #icd4.decorate.as_json
Although I think it would be better to keep things correct in as_json method and have id value returned for id property and create a decorator class inherited from Draper::CollectionDecorator and define there your custom method, something like:
class Icd4CodesDecorator < Draper::CollectionDecorator
def as_search_json
object.map do |o|
{
id: icd3_code_id,
bezeichnung: bezeichnung,
nummer: nummer
}
end
end
end
and then in your controller you could do:
render json: Icd4CodesDecorator.new(#icd4).as_search_json
This way you can easily create and maintain any number of versions of json output for your models.
The simplest way is to cleverly change the value something like this
def search
#icd4 = Icd4Code.search_full(params[:search]).first(20)
r = icd: #icd4.as_json(:only => [:bezeichnung, :nummer, :icd3_code_id])
final_value = []
r["icd"].each do |h|
f = {}
h.map do |k,v|
if k == 'icd3_code_id'
f["id"] = v
else
f[k] = v
end
end
final_value << f
end
render json: final_value
end

Rails ActiveRecord relation to JSON

I'm using Rails to query data and put it into a hash like so...
class AssignmentsController < ApplicationController
respond_to :json
def index
student = Student.find(current_user.student_id)
#assignments = Hash.new
#assignments["individual"] = Assignment.where(:student_id => student.id)
unless student.group_lesson_ids.nil?
student.group_lesson_ids.each do |g|
group_lesson = GroupLesson.find(g)
#assignments[group_lesson.name] = Assignment.where(:group_lesson_id => g)
end
end
end
end
Then I want Rabl to turn this into JSON to be used by a Marionette app.
Here's the Rabl file
object #assignments
attributes :id, :title, :student_id, :assigned
But when I inspect the JSON in the browser, it just shows me the ActiveRecord relation.
{
"#<ActiveRecord::Relation::ActiveRecord_Relation_Assignment:0x007fa2956b43a8>": [
{ },
{ },
{ }
]
}
I understand this is because of the concept of lazy loading, but what should I do in this situation to make the JSON available to Marionette?
How about this, provided you have relationships between models specified (not tested since I'm not currently using RABL):
class AssignmentsController < ApplicationController
respond_to :json
def index
#student = current_user.student
end
end
RABL template:
object false
node :assignments do
child #student.assignments => :individual
#student.group_lessons.each do |gl|
node(gl.name) { gl.assignments }
end
end

Resources