How to paginate Rabl's collections - ruby-on-rails

I have this template:
# app/views/posts/index.rabl
collection #posts => :posts
attributes :id, :title, :subject
child(:user) { attributes :full_name }
node(:read) { |post| post.read_by?(#user) }
Witch returns:
{
"posts": [
{
"post": {
"id": 5,
"title": "...",
"subject": "...",
"user": {
"full_name": "..."
},
"read": true
}
}
]
}
And I would like to add to add some pagination params in order to rendering this:
{
"posts": [
{
"post": {
"id": 5,
"title": "...",
"subject": "...",
"user": {
"full_name": "..."
},
"read": true
}
}
],
"total": 42,
"total_pages": 12
}
Any ideas? Many thanks!

Sorry for my noob question, whitch was answered by the README. Here's an example of pagination:
object false
node(:total) {|m| #posts.total_count }
node(:total_pages) {|m| #posts.num_pages }
child(#posts) do
extends "api/v1/posts/show"
end
Note: I'm using Kaminari for pagination.

When searching for kaminari and rabl this is the first and pretty much only relevant result. As such, I would like to leave here a solution according to the HAL Specification that generates links like this.
So first, start with the view:
# api/v1/posts/index.rabl
object false
child(#posts) do
extends 'api/v1/posts/show'
end
node(:_links) do
paginate #posts
end
Then proceed to define the paginate method:
# app/helpers/api_helper
module ApiHelper
def paginate(collection)
current_page_num = collection.current_page
last_page_num = collection.total_pages
{
:first => first_page,
:previous => previous_page(current_page_num),
:self => current_page(current_page_num),
:next => next_page(current_page_num, last_page_num),
:last => last_page(last_page_num)
}
end
def first_page
{ :href => url_for(:page => 1) }
end
def previous_page(current_page_num)
return nil if current_page_num <= 1
{ :href => url_for(:page => current_page_num-1) }
end
def current_page(current_page_num)
{ :href => url_for(:page => current_page_num) }
end
def next_page(current_page_num, last_page_num)
return nil if current_page_num >= last_page_num
{ :href => url_for(:page => current_page_num+1) }
end
def last_page(last_page_num)
{ :href => url_for(:page => last_page_num) }
end
end
And finally, include the helper in the necessary controllers. The helper could be included in a Api::BaseController, from which all API controllers inherit:
helper :api
I could not have done this without Zag zag..'s solution, so.. Thank you so much!

note, for will_paginate 3.0.0 the following works:
node(:total) {|m| #posts.total_entries }
node(:total_pages) {|m| (#posts.total_entries.to_f / #posts.per_page).ceil }
node(:page_num){|m| #posts.current_page}

This might be what you are looking for ;)
object false
node :comments do
partial('posts/index', object: #posts)
end
node(:pagination) do
{
total:#posts.count,
total_pages: 20
}
end

Related

Merge two hashes in ruby

I have two collections of hashes
and_filters = [{:filter=>:brand, :value=>"Fila"}, {:filter=>:brand, :value=>"Adidas"}]
or_filters = [{:filter=>:gender, :value=>"Hombre"}]
and i need make like the following struct
:_or => [
{ :_and => [
{:gender => "Hombre"},
{:brand => "Adidas"}]
},
{ :_and => [
{:gender=>"Hombre"},
{:brand=>"Fila"}]
}
]
For this i did
query[:_or] = []
or_filters.each do |or_f|
query[:_or] << {
:_and => [
and_filters.map do |and_f|
{and_f[:filter] => and_f[:value]}
end
{ or_f[:filter] => or_f[:value] }
]
}
end
but an error Expected: { shows in code. Apparently the second loop is badly syntactically
It's not pretty, but I believe this gives the desired results:
{_or: or_filters.each_with_object([]) do |or_filter, or_filter_ary|
or_filter_hsh = {or_filter[:filter] => or_filter[:value]}
and_filters.each do |and_filter|
and_filter_hsh = {and_filter[:filter] => and_filter[:value]}
or_filter_ary << {_and: [or_filter_hsh, and_filter_hsh]}
end
end
}
Which gives:
{:_or => [
{ :_and => [
{:gender=>"Hombre"},
{:brand=>"Fila"}
]},
{ :_and => [
{:gender=>"Hombre"},
{:brand=>"Adidas"}
]}
]}
It looks like you want every combination of the given and_filters with the given or_filters. In that case, and assuming you don't care about order (:gender before :brand vs. the other way around) Array#product is your friend:
result = {
_or: and_filters.product(or_filters).map do |a|
{ _and: a.map {|filter:, value:| { filter => value }} }
end
}
# => {
# :_or => [
# {:_and => [{:brand=>"Fila"}, {:gender=>"Hombre"}]},
# {:_and => [{:brand=>"Adidas"}, {:gender => "Hombre"}]}
# ]
# }
See it in action on repl.it: https://repl.it/#jrunning/HorizontalDirectCharmap
Thats what i was looking for
query = {}
query[:_or] = or_filters.map do |or_f|
and_filters_aux = and_filters.dup
and_filters_aux << or_f
{ :_and => and_filters_aux.map{|hsh| {hsh[:filter] => hsh[:value]} } }
end
https://repl.it/repls/ShyLateClients

Custom JSON response with acts_as_taggable_on

I just implemented acts_as_taggable_on in my app and now I'm trying to trim my JSON response so it doesn't return everything for the Model in question. Here's what my as_json method looks like:
def as_json(options={})
super(:only => [:serial_number],
:include => {
:device_functions => { :only => [:can_scan, :can_brute] },
:scan_options => { :methods => :scan_ip_list}
}
)
end
Which currently returns:
{
"serial_number": "abcdefg12345",
"device_functions": [
{
"can_scan": true
}
],
"scan_options": [
{
"id": 1,
"device_id": 11,
"created_at": "2016-02-05T02:26:26.090Z",
"updated_at": "2016-02-05T02:26:26.090Z",
"scan_ip_list": [
"10.10.10.100-110",
"10.10.10.1"
]
}
]
}
I want to get rid of extra data that I don't need, such as id, device_id, created_at and updated_at now.
Also, using :only => worked find for the :device_functions response, but I had to use :methods => for :scan_options since I'm using acts_as_taggable_on... at least that's what I read and was the only option that returned something (I tried :only => and :include => as well but they returned an empty hash:
{
"serial_number": "abcdefg12345",
"device_functions": [
{
"can_scan": true
}
],
"scan_options": [
{}
]
}
You just need to add the :only option to your :scan_options hash too:
# ...
:scan_options => { :methods => :scan_ip_list, :only => :scan_ip_list }
Also, FWIW, you should probably merge into option in case you ever want to supply some of your own options, so:
# ...
super options.merge( :only => ...etc.

How to combine two as_json methods to one correctly?

In my Model I have a working as_json method as follows:
def as_json(options = {})
super(options.merge(include: [:user, comments: {include: :user}]))
end
This method is for including users in comments.
Now I need to add almost the same thing in the same model for answers:
def as_json(options = {})
super(options.merge(include: [:user, answers: {include: :user}]))
end
How do I combine these two as_json methods, so that I have one as_json method?
Don't laugh but I am struggling with this for 3 days.
This is one of the reasons why you should not use the built-in to_json to serialize ActiveRecord models.
Instead, you should delegate the task to another object called serializer. Using a serializer allows you to have illimitate representations (serializations) of the same object (useful if the object can have different variants such as with/without comments, etc) and separation of concerns.
Creating your own serializer is stupid simply, as simple as having
class ModelWithCommentsSerializer
def initialize(object)
#object = object
end
def as_json
#object.as_json(include: [:user, comments: {include: :user}]))
end
end
class ModelWithAnswersSerializer
def initialize(object)
#object = object
end
def as_json
#object.as_json(include: [:user, answers: {include: :user}]))
end
end
Of course, that's just an example. You can extract the feature to avoid duplications.
There are also gems such as ActiveModelSerializers that provides that feature, however I prefer to avoid them as they tend to provide a lot of more of what most of users really need.
Why are you trying to override core Rails functionality - not good practice unless absolutely necessary.
--
This says the following:
To include associations use :include:
user.as_json(include: :posts)
# => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
# "created_at" => "2006/08/01", "awesome" => true,
# "posts" => [ { "id" => 1, "author_id" => 1, "title" => "Welcome to the weblog" },
# { "id" => 2, "author_id" => 1, "title" => "So I was thinking" } ] }
You could call:
#answers.as_json(include :users)
--
Ohhhhhhhh:
Second level and higher order associations work as well:
user.as_json(include: { posts: {
include: { comments: {
only: :body } },
only: :title } })
# => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
# "created_at" => "2006/08/01", "awesome" => true,
# "posts" => [ { "comments" => [ { "body" => "1st post!" }, { "body" => "Second!" } ],
# "title" => "Welcome to the weblog" },
# { "comments" => [ { "body" => "Don't think too hard" } ],
# "title" => "So I was thinking" } ] }
So looks like you could call:
#answers.to_json(include: comments: { include: :users })
def as_json(other_arg, options = {})
as_json(options.merge(include: [:user, other_arg: {include: :user}]))
end
And then you can call:
MyModel.as_json(:comments)
MyModel.as_json(:answers)

Custom json key in ruby on rails

I have an as_json method I'm overriding to include an association.
def as_json(options)
super((options || { }).merge({
:include => {:otherobject => {:include => :category}}
}))
end
So this give me json like this
{
"prop1": "val1",
"prop2": "val2",
"otherobject": {"category": {} }
}
I need to adjust the json to look like this because of my processing code.
{
"prop1": "val1",
"prop2": "val2",
"otherobject": { "otherobject": { "category": {} } }
}
I basically need to wrap the data with another key. How can I do this in rails?
def as_json(options = {})
old = super(options.merge({ :include => {:otherobject => {:include => :category}}))
new = old.slice!(:otherobject)
new[:otherobject] = old
new
end

How to Post array of data all at one time?

I have a Rails 3 Application that is trying to post an array of users, all at one time. I am trying to post through the Postman REST client. If I tried to post a single user at a time it works well, but it is not possible to post multiple users at a time.
This is my User model:
class User < ActiveRecord::Base
attr_accessible :name,age,email,mobile,gender
end
And my User controller:
respond_to :html , :json
def create
#user = User.new(params[:user])
if #user.save
render :json => { :status => :ok, :message => "User Created Successfully"}.to_json
end
end
User posting data in JSON format for multiple users:
{
user:[
{
"name":"abc",
"age": 23,
"email": "abc#gmail.com",
"mobile": 9876543210,
"gender":"M"
},
{
"name":"def",
"age": 26,
"email": "def#gmail.com",
"mobile": 9876543210,
"gender":"F"
}
]
}
Is it possible to do this in Rails?
I tried:
def create
#userlist = User.new(params[:user])
#userlist.each do |u|
u.save
end
render :json => { :status => :ok, :message => "User Created Successfully"}.to_json
end
but the data is not saved.
Is there any solution?
Nested attributes saving under User:
{
"users" :[
{
"name":"abc",
"age": 23,
"email": "abc#gmail.com",
"mobile": 9876543210,
"gender":"M",
"projects":
[
{
"projectname":"abc",
"type":"abc"
},
{
"projectname":"def",
"type":"abc"
},
{
"projectname":"ghi",
"type":"abc"
}
]
},
{
"name":"def",
"age": 26,
"email": "def#gmail.com",
"mobile": 9876543210,
"gender":"F",
"projects":
[
{
"projectname":"abc",
"type":"abc"
},
{
"projectname":"def",
"type":"abc"
},
{
"projectname":"ghi",
"type":"abc"
}
]
}
]
}
As seen here, I'd suggest you bulk insert (depending on the likely amount of users that will be passed at a time), using this gem:
def create
users = []
#userlist = params[:users]
#userlist.each do |u|
user = User.new(u)
users << user
end
User.import(users)
render :json => { :status => :ok, :message => "User(s) Created Successfully"}
end
Ok, i see your edit of the posted params. so do it in your controller like this:
def create
#userlist = params[:users]
#userlist.each do |u|
user = User.new(u)
user.save!
end
render :json => { :status => :ok, :message => "User Created Successfully"}.to_json
end

Resources