I was just wondering if the following is possible: I have model with nested associative models. I want to be able to render json: on current_user.reports.minned and have it eager_load the plucked values from each model. How can I accomplish this?
Here I use only 2 models as an example. In reality, the solution needs to work for n+1 nested models.
Does not work:
class Report
has_many :templates
def minned
self.pluck(:id, :title)
self.templates = templates.minned
end
end
class Template
belongs_to :report
def minned
self.pluck(:id, :name, :sections, :columns)
end
end
....
# reports.minned.limit(limit).offset(offset)
# This should return something like:
[{
'id': 0,
'title': 'Rep',
'templates': [{
'id': 0,
'name': 'Temp'
'sections': [],
'columns': []
}]
},
{
'id': 1,
'title': 'Rep 1',
'templates': [{
'id': 0,
'name': 'Temp',
'sections': [],
'columns': []
},
{
'id': 1,
'name': 'Temp 1',
'sections': [],
'columns': []
}]
}]
Thanks for any help.
Edit:
I will add that I found a way to do this by overriding as_json for each model, but this applies the plucking to all requests. I need to have control over which requests give what pieces of information.
# in Report model
def as_json(options={})
super(:id, :title).merge(templates: templates)
end
# in Template model
def as_json(options={})
super(:id, :name, :sections, :columns)
end
Thanks to eirikir, this is all I need to do:
Report model
def self.minned
includes(:templates).as_json(only: [:id, :title], include: {templates: {only: [:id, :name, :sections, :columns]}})
end
Then when using this with pagination order, limit or anything like that, just drop it at the end:
paginate pre_paginated_reports.count, max_per_page do |limit, offset|
render json: pre_paginated_reports.order(id: :desc).limit(limit).offset(offset).minned
end
Now I'm not overriding as_json and have complete control over the data I get back.
If I understand correctly, you should be able to achieve this specifying the output in the options given to as_json:
current_user.reports.includes(:templates).as_json(only: [:id, :title], include: {templates: {only: [:id, :name, :sections, :columns]}})
Related
I assume this is a fairly simple syntax issue, but can't seem to solve it.
I have a query that returns a nested JSON object. In its current form, it does not return 'credits'.
user_data['harvests'] = #harvests.to_json(include: [ :user_public_data, :awards, comments: {include: :user}, credits: {include: :user} ])
However, if I reverse the order of 'credits' and 'comments', it does not return 'comments'. What's the issue here?
user_data['harvests'] = #harvests.to_json(include: [ :user_public_data, :awards, credits: {include: :user}, comments: {include: :user} ])
Hard to find documentation on this, but found a solution, by using as_json rather than to_json.
user_data['harvests'] = #harvests.as_json(include: {user_public_data: {}, awards: {}, comments: {include: :user}, credits: {include: :user} }
I have 2 resources - post and author. post has an author. When I fetch the post details, I would like to selectively include the details of the author.
Example -
GET /authors/1
{
'id': 1,
'name': 'Some name',
'email': 'admin#example.com',
'address': 'Some address',
'mobile': '',
'language': 'ruby'
}
GET /posts/1
I would like to have -
{
'id': 1,
'created_at': '2017-01-01 10:00:00',
'views': 7500,
'seo_score': 4,
'author': {
'id': 1,
'name': 'Some name',
'email': 'admin#example.com'
}
}
instead of,
{
'id': 1,
'created_at': '2017-01-01 10:00:00',
'views': 7500,
'seo_score': 4,
'author': {
'id': 1,
'name': 'Some name',
'email': 'admin#example.com',
'address': 'Some address',
'mobile': '',
'language': 'ruby'
}
}
I would like to know if I can selectively choose the attributes of the association in the serializers, by using include, exclude, only etc.
I know I can write a separate serializer and map it to the association while defining it, but just to include 2 specific attributes I don't think adding a separate class makes sense.
I also know that I can override the association and fetch all the attributes and selectively choose a few; but I would like to know if Serializers support this by default.
you can create a new association with your selected columns like this
belongs_to :author_with_partial_details, -> { select [:id, :name, :email] }, :class_name => "Author"
and use it something like this
Post.find(params[:id]).includes(:author_with_partial_details)
Alternatively you can also use jbuilder gem to create your own JSON structure, here is the link to the gem https://github.com/rails/jbuilder
In some cases nested associations are embedded in the JSON, in others they are not. So far so good, that behaves the way I want. But I want that in the cases where they aren't embedded the IDs of the nested associations are still emitted.
E.g.:
class FooSerializer < ActiveModel::Serializer
attributes :id, :x, :y
belongs_to :bar
end
class BarSerializer < ActiveModel::Serializer
attributes :id, :z
end
When I serialize a Foo object without include: [:bar] I want the result to look like:
{
"id": 123
"x": 1,
"y": 2,
"bar": 456
}
And if bar would be a polymorphic association I'd like something like that:
{
"id": 123
"x": 1,
"y": 2,
"bar": {"id": 456, "schema": "Bar"}
}
Actually I would like the IDs to be strings ("id": "123") because they should be black boxes for the API consumer and definitely not use JavaScript's Number type (which is double precision floating point!).
How do I do that? I didn't find any information about that.
Define attribute id this way in FooSerializer to get it as a string:
attribute :id do
object.to_s
end
"When I serialize a Foo object without include: [:bar] I want the result to look like:"
attribute :bar do
object.bar.id.to_s
end
"And if bar would be a polymorphic association I'd like something like that:"
attribute :bar do
{id: object.barable_id.to_s, schema: object.barable_type}
end
NOTE: I haven't tested this.
I found a way to do that by using a BaseSerializer like this:
class BaseSerializer < ActiveModel::Serializer
attributes :id, :type
def id
if object.id.nil?
nil
else
object.id.to_s
end
end
def type
# I also want all the emitted objects to include a "type" field.
object.class.name
end
def self.belongs_to(key, *args, &block)
attribute key do
assoc = object.class.reflect_on_association(key)
foreign_id = object.send(assoc.foreign_key)
if foreign_id.nil?
nil
elsif assoc.polymorphic?
{
type: object.send(assoc.foreign_type),
id: foreign_id.to_s
}
else
foreign_id.to_s
end
end
super
end
end
I'm facing a case when a need to display information contained in my join table. For example:
# == Schema Information
#
# Table name: quality_inspections
#
# id, content
#
# =============================================================================
class QualityInspection
has_many :inspection_inspectors
has_many :inspector, through: :inspection_inspectors
end
# == Schema Information
#
# Table name: inspection_inspectors
#
# quality_inspection_id, inspector_id, inspection_date
#
# =============================================================================
class InspectionInspector
belongs_to :quality_inspection
belongs_to :user, foreign_key: :inspector_id
end
Then, I'd like to have the following json:
{
"quality_inspectors": [{
"id": 1,
"content": "foo",
"inspectors": [{
"id": 1, // which is the user's id
"first_name": "Bar",
"last_name": "FooFoo",
"inspection_date": "random date"
}]
}]
}
For now, I'm doing the following in my serializer:
module Api::V1::QualityInspections
class InspectorSerializer < ActiveModel::Serializer
type :inspector
attributes :id, :first_name, :last_name, :inspection_date
def id
inspector.try(:public_send, __callee__)
end
def first_name
inspector.try(:public_send, __callee__)
end
def last_name
inspector.try(:public_send, __callee__)
end
private
def inspector
#inspector ||= object.inspector
end
end
end
Do you have any better solution ? Or maybe I'm not using the right methods on my Serializer ?
Anyway, I'm really stuck when it came to display information on a join table. Oddly, I'd the same issue when using cerebris/jsonapi-resources.
EDIT: Linked issue on GH: https://github.com/rails-api/active_model_serializers/issues/1704
I don't think you need to put additional methods such as id, first_name, last_name. Instead, use association within your serializer to get the appropriate JSON data as mentioned afore.
I'm looking for some advice on the best way forward with my app which i have began to integrate elasticsearch for the first time. Im a bit of a beginner in rails but keen to dive in so forgive any glaring errors!
I followed a tutorial http://www.sitepoint.com/full-text-search-rails-elasticsearch/ and have also implemented some additional elasticsearch dsl features from reading documentation etc.. im just not convinced i'm there yet. (I certainly need to move out of the model, as currently most sits in the Product active record model.)
I am trying to implement a search on the Product model with ability to partial word search, fuzzy search (misspellings). From what I understand, I am able to set my own analyzers and filters for the elasticsearch, which I have done and currently reside in the Product model. I would like to move these to a more sensible location too, once I have established if indeed I am actually doing this correctly. I do get results when i search but im including things like deleting the index, creating a new index with mapping all in the end of the product model, if what i have below is not "the correct way", what better way is there than what i have to 1, implement elastic search using rails 2, seperate concerns more efficiently.
thanks and much appreciated
CODE:
lib/tasks/elasticsearch.rake:
require 'elasticsearch/rails/tasks/import'
View:
<%= form_tag search_index_path, class: 'search', method: :get do %>
<%= text_field_tag :query, params[:query], autocomplete: :off, placeholder: 'Search', class: 'search' %>
<% end %>
Gems i used:
gem 'elasticsearch-model', git: 'git://github.com/elasticsearch/elasticsearch-rails.git'
gem 'elasticsearch-rails', git: 'git://github.com/elasticsearch/elasticsearch-rails.git'
Search Controller:
class SearchController < ApplicationController
def index
if params[:query].nil?
#products = []
else
#products = Product.search(params[:query])
end
end
end
Product Model:
require 'elasticsearch/model'
class Product < ActiveRecord::Base
# ElasticSearch
include Elasticsearch::Model
include Elasticsearch::Model::Callbacks
settings index: {
number_of_shards: 1,
analysis: {
filter: {
trigrams_filter: {
type: 'ngram',
min_gram: 2,
max_gram: 10
},
content_filter: {
type: 'ngram',
min_gram: 4,
max_gram: 20
}
},
analyzer: {
index_trigrams_analyzer: {
type: 'custom',
tokenizer: 'standard',
filter: ['lowercase', 'trigrams_filter']
},
search_trigrams_analyzer: {
type: 'custom',
tokenizer: 'whitespace',
filter: ['lowercase']
},
english: {
tokenizer: 'standard',
filter: ['standard', 'lowercase', 'content_filter']
}
}
}
} do
mappings dynamic: 'false' do
indexes :name, index_analyzer: 'index_trigrams_analyzer', search_analyzer: 'search_trigrams_analyzer'
indexes :description, index_analyzer: 'english', search_analyzer: 'english'
indexes :manufacturer_name, index_analyzer: 'english', search_analyzer: 'english'
indexes :type_name, analyzer: 'snowball'
end
end
# Gem Plugins
acts_as_taggable
has_ancestry
has_paper_trail
## -99,6 +146,33 ## def all_sizes
product_attributes.where(key: 'Size').map(&:value).join(',').split(',')
end
def self.search(query)
__elasticsearch__.search(
{
query: {
query_string: {
query: query,
fuzziness: 2,
default_operator: "AND",
fields: ['name^10', 'description', 'manufacturer_name', 'type_name']
}
},
highlight: {
pre_tags: ['<em>'],
post_tags: ['</em>'],
fields: {
name: {},
description: {}
}
}
}
)
end
def as_indexed_json(options={})
as_json(methods: [:manufacturer_name, :type_name])
end
end
# Delete the previous products index in Elasticsearch
Product.__elasticsearch__.client.indices.delete index: Product.index_name rescue nil
# Create the new index with the new mapping
Product.__elasticsearch__.client.indices.create \
index: Product.index_name,
body: { settings: Product.settings.to_hash, mappings: Product.mappings.to_hash }
# Index all article records from the DB to Elasticsearch
Product.import(force: true)
end
If you are using elasticsearch for searching,then i will recommend gem 'chewy' with elasticsearch server.
For more information just go to the links provided below.
for chewy:
https://github.com/toptal/chewy
Integrate chewy with elasticsearch:
http://www.toptal.com/ruby-on-rails/elasticsearch-for-ruby-on-rails-an-introduction-to-chewy
Thanks
I can recommend searchkick:
https://github.com/ankane/searchkick
Several apps in production running with searchkick and it's easy to use.
Also check out the documentation of searchkick where a search for products is described in detail with facets, suggestions, etc.