User app updates

This commit is contained in:
2014-03-27 21:51:16 +01:00
parent 6af7dc74af
commit e179f6e582
37 changed files with 362 additions and 53 deletions
+2 -2
View File
@@ -77,8 +77,8 @@ group :development do
gem 'thin' gem 'thin'
gem 'faye' gem 'faye'
gem 'pry-rails' gem 'pry-rails'
gem 'spring' #gem 'spring'
gem 'spring-commands-rspec' #gem 'spring-commands-rspec'
end end
group :test do group :test do
-5
View File
@@ -348,9 +348,6 @@ GEM
railties (>= 3.0, < 4.1) railties (>= 3.0, < 4.1)
slim (~> 2.0) slim (~> 2.0)
slop (3.5.0) slop (3.5.0)
spring (1.1.2)
spring-commands-rspec (1.0.1)
spring (>= 0.9.1)
sprockets (2.11.0) sprockets (2.11.0)
hike (~> 1.2) hike (~> 1.2)
multi_json (~> 1.0) multi_json (~> 1.0)
@@ -437,8 +434,6 @@ DEPENDENCIES
simplecov simplecov
simply_stored! simply_stored!
slim-rails slim-rails
spring
spring-commands-rspec
thin thin
turnip turnip
uglifier (>= 1.0.3) uglifier (>= 1.0.3)
@@ -0,0 +1,5 @@
Ember.Handlebars.helper 'time', (time, params..., options = {})->
return '' unless time
iso = time.toISOString()
tag = if options.bare then iso else $("<span data-time=\"#{iso}\"></span>").text(moment(iso).format(options.format || 'dd D MMM HH:MM')).get(0).outerHTML
new Handlebars.SafeString tag
@@ -58,15 +58,17 @@ function setTranslations(selector){
var list = $('#top-navigation-list'); var list = $('#top-navigation-list');
list.find('.locale').show(); list.find('.locale').show();
list.find('.locale-'+$locale).hide(); list.find('.locale-'+$locale).hide();
moment.lang($locale);
if(selector){ if(selector){
$(selector).find('[data-t]').each(function(){$(this).html(t($(this).data('t'), $(this).data('tAttributes')))}) $(selector).find('[data-t]').each(function(){$(this).html(t($(this).data('t'), $(this).data('tAttributes')))})
$(selector).find('*[data-time]').each(function(){
$(this).text(moment($(this).data('time')).format($(this).data('timeFormat') || 'dd D MMM HH:MM'))
})
}else{ }else{
$('[data-t]').each(function(){$(this).html(t($(this).data('t'),$(this).data('tAttributes')))}) $('[data-t]').each(function(){$(this).html(t($(this).data('t'),$(this).data('tAttributes')))})
$('*[data-time]').each(function(){
$(this).text(moment($(this).data('time')).format($(this).data('timeFormat') || 'dd D MMM HH:MM'))
})
} }
moment.lang($locale);
$('[data-time]').each(function(){
$(this).text(moment($(this).data('time')).format($(this).data('timeFormat') || 'dd D MMM HH:MM'))
})
$('.datepicker').datepicker("option", $.datepicker.regional[$locale]) $('.datepicker').datepicker("option", $.datepicker.regional[$locale])
} }
@@ -0,0 +1,80 @@
@$translations =
en:
models: <%= I18n.t('activemodel.models', locale: :en).to_json %>
attributes: <%= I18n.t('activemodel.attributes', locale: :en).to_json %>
helpers: <%= I18n.t('helpers', locale: :en).to_json %>
pagination: <%= I18n.t('views.pagination', locale: :en).to_json %>
nl:
models: <%= I18n.t('activemodel.models', locale: :nl).to_json %>
attributes: <%= I18n.t('activemodel.attributes', locale: :nl).to_json %>
helpers: <%= I18n.t('helpers', locale: :nl).to_json %>
pagination: <%= I18n.t('views.pagination', locale: :nl).to_json %>
@t = (path, vars={}) ->
#result = undefined
#m = undefined
#translatable = undefined
#isafety = undefined
#replacable = undefined
parts = path.split(".")
#accessor = "$translations.#{$locale}[\"#{parts.join("\"][\"")}\"]"
result = $translations[$locale]
try
result = result[part] for part in parts
catch err
result = parts[parts.length - 1].capitalize()
return "" if result is ""
return parts[parts.length - 1].capitalize() unless result
# allow t('test', {var: 'Benjamin'}) statements
# with $translations.nl.test = "Hello %{var}"
for variable, value of vars
result = result.replace("%{#{variable}}", value)
isafety = 0
while result.indexOf("${") > -1
m = result.match(/\${([\w\.]+(\|\w+)?)}/)
if m[2]
translatable = m[1].replace(m[2], "")
operation = $transformation_mappings[m[2].substr(1) or m[2].substr(1)]
else
translatable = m[1]
operation = null
replacable = t(translatable)
replacable = replacable[operation]() if operation
result = result.replace(m[0], replacable)
break if isafety > 10 # referencing other translations may cause infinite loops
isafety += 1
result
@setLocale = (locale, options={}) ->
locale ||= Qstorage.getItem('locale') || options.default || 'en'
Qstorage.setItem "locale", locale
@$locale = locale
setTranslations()
@setTranslations = (selector) ->
list = $("#top-navigation-list")
list.find(".locale").show()
list.find(".locale-" + $locale).hide()
moment.lang $locale
if selector
$(selector).find("[data-t]").each ->
$(this).html t($(this).data("t"), $(this).data("tAttributes"))
$(selector).find("*[data-time]").each ->
$(this).text moment($(this).data("time")).format($(this).data("timeFormat") or "dd D MMM HH:MM")
else
$("[data-t]").each ->
$(this).html t($(this).data("t"), $(this).data("tAttributes"))
$("*[data-time]").each ->
$(this).text moment($(this).data("time")).format($(this).data("timeFormat") or "dd D MMM HH:MM")
$(".datepicker").datepicker "option", $.datepicker.regional[$locale]
$transformation_mappings =
downcase: "toLowerCase"
upcase: "toUpperCase"
@@ -1,6 +1,7 @@
@App = Ember.Application.create @App = Ember.Application.create
LOG_TRANSITIONS: true LOG_TRANSITIONS: true
rootElement: '#ember-app-container' rootElement: '#ember-app-container'
Ember.$.ajaxPrefilter (options) -> Ember.$.ajaxPrefilter (options) ->
if options.type.toUpperCase() == 'GET' if options.type.toUpperCase() == 'GET'
if auth_token = Qstorage.getItem('auth_token') if auth_token = Qstorage.getItem('auth_token')
@@ -13,6 +14,7 @@ Ember.$.ajaxPrefilter (options) ->
options.data ||= {} options.data ||= {}
options.data.auth_token = auth_token options.data.auth_token = auth_token
true true
Ember.$.ajaxSetup Ember.$.ajaxSetup
error: (jqXHR, textStatus, errorThrown)-> error: (jqXHR, textStatus, errorThrown)->
console.log "Error: #{textStatus}: #{errorThrown}" console.log "Error: #{textStatus}: #{errorThrown}"
@@ -5,6 +5,7 @@
#= require shared-ember-helpers/all #= require shared-ember-helpers/all
#= require_directory ./modifications #= require_directory ./modifications
#= require ./app #= require ./app
#= require_directory ./modules
#= require user/quser #= require user/quser
#= require_tree . #= require_tree .
@EmberENV = {FEATURES: {'query-params-new': true}} @EmberENV = {FEATURES: {'query-params-new': true}}
@@ -1,10 +1,10 @@
App.ActiveListController = Ember.ArrayController.extend App.ActiveListController = Ember.ObjectController.extend
orders: (-> #orders: (->
@get('list.orders') #@get('list.orders')
).property('list.orders') #).property('list.orders')
list: (-> #list: (->
@get('controllers.application.list') #@get('controllers.application.list')
).property('controllers.application.list') #).property('controllers.application.list')
displayTotal: (-> @get('list.orders.length') and @get('list.orders.length') > 1 ).property('list.orders.length') displayTotal: (-> @get('model.orders.length') and @get('model.orders.length') > 1 ).property('model.orders.length')
@@ -0,0 +1 @@
App.ListController = Ember.ObjectController.extend {}
@@ -0,0 +1,4 @@
App.ListsIndexController = Ember.ArrayController.extend App.PaginationModule,
actions:
showList: (list)->
@transitionToRoute 'list', list
@@ -9,8 +9,12 @@ App.ProductOrdersController = Ember.ArrayController.extend
orderProducts: -> orderProducts: ->
order = @store.createRecord('order', list: @get('controllers.application.list')) order = @store.createRecord('order', list: @get('controllers.application.list'))
new_product_orders = @store.all('product_order').filterProperty('order', null) new_product_orders = @store.all('product_order').filterProperty('order', null)
new_product_orders.forEach (po) -> po.setOrder(order) order.get('product_orders').pushObjects(new_product_orders)
order.save() order.save().then ->
# The new versions will be returned in the json response
#new_product_orders.invoke 'rollback'
#new_product_orders.invoke 'transitionTo', 'loaded.saved'
new_product_orders.invoke 'deleteRecord'
@transitionToRoute 'active_list' @transitionToRoute 'active_list'
#orders = @store.all('product_order').toArray() #orders = @store.all('product_order').toArray()
@@ -3,6 +3,14 @@ App.List = DS.Model.extend
orders: DS.hasMany('order') orders: DS.hasMany('order')
needs_help: attr('boolean') needs_help: attr('boolean')
needs_payment: attr('boolean') needs_payment: attr('boolean')
supplier_name: attr('string')
price: attr('number')
extended_version: attr('boolean')
supplier_orders_in_process_count: attr('number')
supplier_orders_placed_count: attr('number')
total: (-> total: (->
@get('orders').getEach('total').reduce(((sum, total) -> sum + total), 0) @get('orders').getEach('total').reduce(((sum, total) -> sum + total), 0)
).property('orders.@each.total') ).property('orders.@each.total')
is_extended_version: ->
@get('extended_version')
@@ -0,0 +1,37 @@
App.PaginationModule = Ember.Mixin.create
#modelClass: ->
#@modelInfo.class
modelNameForStore: ->
#@modelInfo.store
@get('content.type.typeKey')
storeMetadata: (k) ->
#res = @store.typeMapFor(@modelClass()).metadata
res = @store.metadataFor @get('content.type.typeKey')
res = res[k] if k
res
setStoreMetadata: (k,v) ->
#res = @store.typeMapFor(@modelClass()).metadata
res = @store.metadataFor @get('content.type.typeKey')
res[k] = v
loadMore: ->
page = @storeMetadata('page') + 1
@setStoreMetadata('page',page)
@store.findQuery(@modelNameForStore(),page: page)
page
hasMoreFunc: ->
page = @storeMetadata('page')
total = @storeMetadata('total_pages')
#unfiltered = @storeMetadata('unfiltered_total_pages')
unfiltered = total
#console.debug "page #{page} total #{total} unfiltered #{unfiltered}"
!unfiltered || page < unfiltered
actions:
showMore: ->
page = @loadMore()
@set 'lastKnownPage', page
hasMore: (-> @hasMoreFunc()).property('lastKnownPage','firstObject','@each','filtered.@each')
@@ -3,8 +3,6 @@
App.Router.reopen App.Router.reopen
#location: 'history' #location: 'history'
rootURL: '/user' rootURL: '/user'
activate: (a,b,c)->
debugger
App.Router.map -> App.Router.map ->
@route 'select_qrcode' @route 'select_qrcode'
@@ -12,3 +10,5 @@ App.Router.map ->
@route 'active_list' @route 'active_list'
@route 'list_products' @route 'list_products'
@route 'list_products_for_table' @route 'list_products_for_table'
@resource 'lists', ->
@resource 'list', path: ':list_id'
@@ -1,3 +1 @@
App.ActiveListRoute = Ember.Route.extend App.ActiveListRoute = Ember.Route.extend {}
model: ->
@controllerFor('application').get('list.orders')
@@ -1,7 +1,7 @@
App.ApplicationRoute = Ember.Route.extend App.ApplicationRoute = Ember.Route.extend
setupController: (controller)-> setupController: (controller)->
#@controllerFor('product_orders').set 'model', @store.filter('product_order', (po)-> !po.get('order')) # does not work (yet) #@controllerFor('product_orders').set 'model', @store.filter('product_order', (po)-> !po.get('order')) # does not work (yet)
@controllerFor('product_orders').set 'model', @store.filter('product_order', (po)-> !po.get('placed')) @controllerFor('product_orders').set 'model', @store.filter('product_order', (po)-> !po.get('id'))
controller.secured -> controller.secured ->
faye = new Faye.Client(event_host) faye = new Faye.Client(event_host)
user_id = Qstorage.getItem('user_id') user_id = Qstorage.getItem('user_id')
@@ -9,7 +9,16 @@ App.ApplicationRoute = Ember.Route.extend
console.log e console.log e
@events[e.event].call(@) if @events[e.event] @events[e.event].call(@) if @events[e.event]
@store.find('list', 'current').then (list)=> @store.find('list', 'current').then (list)=>
#@store.find('list', 'current').deleteRecord() # gets not replaced, buty stays as dummy
# A list record with id current and with the content of the returned list is created
# at the moment remove the dummy list, this should be resolved by Ember eventually
if error_list = @store.all('list').findBy('id', 'current')
error_list.rollback()
error_list.transitionTo 'loaded.saved'
error_list.unloadRecord()
@set 'list', list @set 'list', list
@controllerFor('active_list').set('model', list)
unauthorized: -> unauthorized: ->
Qstorage.setItem('auth_token', '') Qstorage.setItem('auth_token', '')
@@ -0,0 +1,5 @@
App.ListRoute = Ember.Route.extend
model: (options)->
@store.find 'list', options.list_id
afterModel: (model)->
model.reload() unless model.is_extended_version()
@@ -0,0 +1,5 @@
App.ListsRoute = Ember.Route.extend
model: ->
@store.find 'list'
setupController: (controller, model)->
controller.set('alreadyLoaded', true)
@@ -2,6 +2,32 @@
DS.RESTAdapter.reopen DS.RESTAdapter.reopen
namespace: 'user' namespace: 'user'
DS.ActiveModelSerializer.reopen
serializeBelongsTo: (record, json, relationship) ->
console.log "Serialize belongsTo #{record.toString()}"
key = relationship.key
belongsTo = Ember.get(record, key)
key = (if @keyForRelationship then @keyForRelationship(key, "belongsTo") else key)
if relationship.options.embedded is "always"
json[key] = belongsTo.serialize()
else
@_super record, json, relationship
serializeHasMany: (record, json, relationship) ->
console.log "Serialize hasMany #{record.toString()}"
key = relationship.key
hasMany = Ember.get(record, key)
relationshipType = DS.RelationshipChange.determineRelationshipType(record.constructor, relationship)
if relationship.options.embedded is "always"
if hasMany and relationshipType is "manyToNone" or relationshipType is "manyToMany" or relationshipType is "manyToOne"
json[key] = []
hasMany.forEach (item, index) ->
json[key].push item.serialize()
else
@_super record, json, relationship
DS.Model.reopen
created_at: DS.attr('date')
updated_at: DS.attr('date')
App.ApplicationSerializer = DS.ActiveModelSerializer App.ApplicationSerializer = DS.ActiveModelSerializer
App.CustomAdapter = DS.RESTAdapter.extend App.CustomAdapter = DS.RESTAdapter.extend
@@ -1,15 +1,15 @@
.row .row
h2=t 'active_list.title' h2=t 'active_list.title'
if list.orders if orders
ul.active_list-orders ul.active_list-orders
each order in list.orders each order in orders
li class=order.state li class=order.state
= order.display = order.display
span.currency= currency order.total span.currency= currency order.total
if displayTotal if displayTotal
li.total li.total
= t 'total' = t 'total'
span.currency= currency list.total span.currency= currency model.total
else else
p p
span=t 'active_list.no_orders_explanation' span=t 'active_list.no_orders_explanation'
@@ -31,6 +31,9 @@
li li
=link-to 'active_list' =link-to 'active_list'
span= t 'active_list.title' span= t 'active_list.title'
li
=link-to 'lists'
span= t 'models.plural.list'
section.main-section section.main-section
if notice if notice
#notice.alert-box{action clearNotice} data-alert=true #notice.alert-box{action clearNotice} data-alert=true
@@ -0,0 +1,10 @@
.row
h2=t 'models.list'
p Hoi
= controller
if model.extended_version
h3 Extended
else
h3 Not extended
link-to 'lists'
span Go to lists
@@ -0,0 +1 @@
= outlet
@@ -0,0 +1,11 @@
.row
h2=t 'models.plural.list'
each list in controller
.lists-overview-entry{action showList list}
span.created_at=time list.created_at
span.price.currency= currency list.price
span.name= list.supplier_name
hr
.clearfix
if hasMore
button{action showMore} Show more
@@ -10,12 +10,15 @@
#= require js-routes #= require js-routes
#= require_directory . #= require_directory .
#= require_self #= require_self
(($) ->
origAppend = $.fn.append
$.fn.append = -> origAppend.apply(@, arguments).trigger("append")
)(jQuery)
@Qstorage = localStorage @Qstorage = localStorage
$.extend($translations.en, <%= I18n.t('user', locale: :en).to_json %>); $.extend($translations.en, <%= I18n.t('user', locale: :en).to_json %>);
$.extend($translations.nl, <%= I18n.t('user', locale: :nl).to_json %>); $.extend($translations.nl, <%= I18n.t('user', locale: :nl).to_json %>);
$(document).foundation() $(document).foundation()
setLocale('en') setLocale()
$ -> $ ->
$('.main-section').css 'min-height', ($(window).height() - $('.tab-bar:first').outerHeight()) $('.main-section').css 'min-height', ($(window).height() - $('.tab-bar:first').outerHeight())
@@ -0,0 +1,13 @@
@import foundation
.lists-overview-entry
+grid-column(12)
@media #{$medium-only}
+grid-column(6)
@media #{$large-up}
+grid-column(4)
.created_at
float: right
.price
float: right
clear: right
+panel($padding: 4px)
+16 -3
View File
@@ -1,12 +1,25 @@
module Users module Users
class ListsController < Users::ApplicationController class ListsController < Users::ApplicationController
def index
#lists = current_user.lists.include_relation(:supplier, :table)
lists = List.for_user(current_user, page: params[:page], per_page: 2).include_relation(:supplier, :table)
#lists.include_relation(:supplier)
render json: lists, each_serializer: UserListSerializer, meta: {total_pages: lists.total_pages, page: lists.current_page} #, root: :lists
end
#EMBER #EMBER
def current def current
list = current_user.active_list list = current_user.active_list
[list].include_relation(supplier: {product_categories: :products}, orders: :product_orders) # table also when it is a real array :)
render json: json_response(not_present: true) and return unless list.present? render json: json_response(not_present: true) and return unless list.present?
render json: { render json: list, serializer: UserExtendedListSerializer
list: list.serialized_with_status_join_requests_and_supplier_counters, end
}.merge(ActiveModel::ArraySerializer.new(list.supplier.product_categories, each_serializer: ProductCategorySerializer, root: :product_categories).as_json)
def show
list = List.find(params[:id])
[list].include_relation(supplier: {product_categories: :products}, orders: :product_orders) # table also when it is a real array :)
render json: list, serializer: UserExtendedListSerializer
end end
end end
end end
@@ -0,0 +1,11 @@
module Users
class OrdersController < Users::ApplicationController
respond_to :json
def create
converted_order = params[:order][:product_orders].each_with_object({}){|po, o| o[po[:product_id]] = po[:quantity] }
list = current_user.active_list
order = list.place_order converted_order
render json: order, serializer: OrderSerializer
end
end
end
+14 -2
View File
@@ -343,11 +343,19 @@ class List
def supplier_counter_info def supplier_counter_info
{ {
supplier_orders_in_process_count: Qwaiter::Counter.get(Supplier.orders_in_process_counter_key(supplier_id)), supplier_orders_in_process_count: supplier_orders_in_process_count,
supplier_orders_placed_count: Qwaiter::Counter.get(Supplier.orders_placed_counter_key(supplier_id)) supplier_orders_placed_count: supplier_orders_placed_count
} }
end end
def supplier_orders_in_process_count
Qwaiter::Counter.get(Supplier.orders_in_process_counter_key(supplier_id))
end
def supplier_orders_placed_count
Qwaiter::Counter.get(Supplier.orders_placed_counter_key(supplier_id))
end
def has_active_orders? def has_active_orders?
Order.count_active_for_supplier_and_list(supplier_id, id) > 0 Order.count_active_for_supplier_and_list(supplier_id, id) > 0
end end
@@ -370,6 +378,10 @@ class List
@join_requests_as_json = h @join_requests_as_json = h
end end
def product_categories
supplier.product_categories
end
def with_info_as_json def with_info_as_json
return @with_info_as_json if @with_info_as_json.present? return @with_info_as_json if @with_info_as_json.present?
hl = as_json hl = as_json
+1
View File
@@ -1,5 +1,6 @@
class ProductOrder class ProductOrder
include SimplyStored::Couch include SimplyStored::Couch
include ActiveModel::SerializerSupport
property :quantity, type: Fixnum property :quantity, type: Fixnum
property :price, type: Float property :price, type: Float
+4 -4
View File
@@ -1,9 +1,9 @@
class ListSerializer < Qwaiter::Serializer class ListSerializer < Qwaiter::Serializer
# user ids for facebook pictures # user ids for facebook pictures
embed :ids #embed :ids
attributes :state, :needs_help, :needs_payment, :is_paid, :price, :table_id, :table_number, :section_id, :has_active_orders, :user_ids attributes :state, :needs_help, :needs_payment, :is_paid, :price, :table_id, :table_number, :section_id, :has_active_orders, :user_ids
def has_active_orders #def has_active_orders
object.has_active_orders? #object.has_active_orders?
end #end
end end
+14 -10
View File
@@ -1,14 +1,18 @@
class OrderSerializer < Qwaiter::Serializer class OrderSerializer < Qwaiter::Serializer
embed :ids embed :ids, include: true
attributes :state, :list_id, :section_id, :product_orders, :price attributes :state, :list_id, :section_id #, :price
def product_orders # todo, put this logic in Ember
@product_orders ||= object.product_orders.include_relation(:product) .map do |product_order| #def product_orders
{product_name: product_order.product.name, id: product_order.id, quantity: product_order.quantity, price: product_order.price} #@product_orders ||= object.product_orders.include_relation(:product) .map do |product_order|
end #{product_name: product_order.product.name, id: product_order.id, quantity: product_order.quantity, price: product_order.price}
end #end
#end
def price # todo, put this logic in Ember
product_orders.inject(0){|sum, po| sum + po[:quantity] * po[:price]}.round(2) #def price
end #product_orders.inject(0){|sum, po| sum + po[:quantity] * po[:price]}.round(2)
#end
has_many :product_orders
end end
@@ -0,0 +1,5 @@
# Used for user ember1
class ProductOrderSerializer < Qwaiter::Serializer
embed :ids
attributes :order_id, :product_id, :quantity, :price
end
@@ -0,0 +1,25 @@
#class ListExtendedSerializer
#render json: {
#list: list.serialized_with_status_join_requests_and_supplier_counters.merge(order_ids: list.orders.map(&:id)),
#}
#.merge(ActiveModel::ArraySerializer.new(list.supplier.product_categories, each_serializer: ProductCategorySerializer, root: :product_categories).as_json)
#.merge(ActiveModel::ArraySerializer.new(list.orders, each_serializer: OrderSerializer, root: :orders).as_json)
#end
class UserExtendedListSerializer < Qwaiter::Serializer
# user ids for facebook pictures
self.root = :list
embed :ids, include: true
attributes :extended_version, :state, :needs_help, :needs_payment, :is_paid, :price,
:table_id, :table_number, :section_id, :user_ids,
:supplier_orders_in_process_count, :supplier_orders_placed_count
def has_active_orders
object.has_active_orders?
end
has_many :orders
has_many :product_categories
def extended_version
true
end
end
+12
View File
@@ -0,0 +1,12 @@
class UserListSerializer < Qwaiter::Serializer
# user ids for facebook pictures
#embed :ids
attributes :state, :needs_help, :needs_payment, :is_paid, :price, :table_id, :table_number, :section_id, :user_ids, :supplier_name
#def has_active_orders
#object.has_active_orders?
#end
def supplier_name
object.supplier.name
end
end
+3 -1
View File
@@ -71,11 +71,13 @@ Qwaiter::Application.routes.draw do
namespace :users, path: '/user' do namespace :users, path: '/user' do
resources :product_categories, only: [:index] resources :product_categories, only: [:index]
resources :lists, only: [] do resources :lists, only: [:index, :show] do
collection do collection do
get :current get :current
end end
end end
resources :orders, only: [:create]
end end
+1
View File
@@ -1,5 +1,6 @@
module Qwaiter module Qwaiter
class Serializer < ActiveModel::Serializer class Serializer < ActiveModel::Serializer
attribute :_id, key: :id attribute :_id, key: :id
attributes :created_at, :updated_at
end end
end end