Add dependent field to Mongoid's output - ruby-on-rails

I have two models:
class City
include Mongoid::Document
field :alternate_names, type: String
field :coordinates, type: Array
field :name, type: String
index({ coordinates: '2d' }, { min: -180, max: 180 })
belongs_to :country
end
and
class Country
include Mongoid::Document
field :iso_3166_code, type: String
field :name, type: String
has_many :cities
end
In the controller I use
#cities = City.where(alternate_names: /#{params[:query].downcase}/).limit(10)
to receive cities list.
Here is an JSON output for each city:
...
"country_id": {
"$oid": "56fc453eae3bbe5c2abcd933"
}
...
How can I get country instead of it's country_id?

I found a solution.
#cities = City.where(alternate_names: /#{params[:query].downcase}/).includes(:country).limit(10)
render json: #cities, except: :country_id, include: :country

Related

How I need to use geo_point type with Chewy?

§Hi guys,
i have problem with geo_point type definition
My Chewy Index code:
class CoordinatesIndex < Chewy::Index
define_type Coordinate.includes( :location ) do
field :location, type: 'geo_point'
field :user_id, type: 'integer'
field :start_at
field :finish_at
field :created_at
end
end
Also I have relation coordinate has_one location, location belongs_to coordinate.
Location model fields:
lat, lon
Error tha i get:
{"type"=>"mapper_parsing_exception", "reason"=>"failed to parse field [location] of type [geo_point]", "caused_by"=>{"type"=>"parse_exception", "reason"=>"field must be either [lat], [lon] or [geohash]"}}
Why?
Thanks
For future Googler's, this is works fine:
field :location, type: 'geo_point', value: ->{ { lat: location.lat, lon: location.lon } }
instead of
field :location, type: 'geo_point'

How to include child associations when serializing to json?

Before using fast_jsonapi gem I was doing this:
render json: school.to_json(include: [classroom: [:students]])
My SchoolSerializer looks like:
class SchoolSerializer
include FastJsonapi::ObjectSerializer
attributes :name, :description, :classroom
end
How would I get the students included in the JSON result?
Also, the classroom association is including but it is displaying all the properties, is there a way to map the classroom property to a ClassroomSerializer ?
class School < ApplicationRecord
belongs_to :classroom
end
class Classroom < ApplicationRecord
has_many :students
end
class SchoolSerializer
include FastJsonapi::ObjectSerializer
attributes :name, :description
belongs_to :classroom
end
# /serializers/classroom_serializer.rb
class ClassroomSerializer
include FastJsonapi::ObjectSerializer
attributes :.... #attributes you want to show
end
Also you can add additional association to your School model, to access Students.
like this
has_many :students, through: :classroom
and then include it in School serializer directly.
Update: also please note that you can directly point to serializer class you need. (if you want to use class with different name from model as example).
class SchoolSerializer
include FastJsonapi::ObjectSerializer
attributes :name, :description
belongs_to :classroom, serializer: ClassroomSerializer
end
render json: SchoolSerializer.new(school, include: "classrooms.students")
The difference being the use of "include" when rendering the serializer. This tells the Serializer to add a key "included" to the returned JSON object.
class SchoolSerializer
include FastJsonapi::ObjectSerializer
belongs_to :classroom
has_many :students, through: :classroom
attributes :school_name, :description
end
StudentSerializer
include FastJsonapi::ObjectSerializer
belongs_to :classroom
belongs_to :school
attributes :student_name
end
render json: SchoolSerializer.new(school).serialized_json
will return a series of students with only the top level identifiers in the form
data: {
id: "123"
type: "school"
attributes: {
school_name: "Best school for Girls",
description: "Great school!"
...
},
relationships: {
students: [
{
id: "1234",
type: "student"
},
{
id: "5678",
type: "student"
}
]
}
}
whereas the include: "classroom.students" will return the full serialized Student Records in the form:
data: {
id: "123"
type: "school"
attributes: {
school_name: "Best school for Girls"
...
},
relationships: {
classroom: {
data: {
id: "456",
type: "classroom"
}
},
students: [
{
data: {
id: "1234",
type: "student"
}
},
{
data: {
id: "5678",
type: "student"
}
}
]
},
included: {
students: {
data {
id: "1234",
type: "student",
attributes: {
student_name: "Ralph Wiggum",
...
},
relationships: {
school: {
id: "123",
type: "school"
},
classroom: {
id: "456",
type: "classroom"
}
}
},
data: {
id: "5678",
type: "student",
attributes: {
student_name: "Lisa Simpson",
...
},
relationships: {
school: {
id: "123",
type: "school"
},
classroom: {
id: "456",
type: "classroom"
}
}
}
},
classroom: {
// Effectively
// ClassroomSerializer.new(school.classroom).serialized_json
},
}
}

FactoryGirl.create does not work while upgrading mongoid version from 5 to 6. Below is the issue I run into while running rspec test

This is the place where is create a test table:
factory :reward_scheme, class: RewardsModels::RewardScheme do
uid { ExpectedData::COSTA_UID }
scheme_type { "bricks" }
reward_type { "menu"}
company_address { FactoryGirl.build(:company_address) }
reward_config { FactoryGirl.build(:reward_config) }
brand { FactoryGirl.build(:brand) }
master_offers { [ FactoryGirl.build(:master_offer) ] }
master_specials { [ FactoryGirl.build(:master_special) ] }
url "http://costa.com"
after(:create) do |reward_scheme|
reward_scheme.stores << FactoryGirl.create(:store)
reward_scheme.user_cards << FactoryGirl.create(:user_card)
end
end
The logs are as follows:
CoreModels::Transaction
Failure/Error: reward_scheme.stores << FactoryGirl.create(:store)
Mongoid::Errors::Validations:
message:
Validation of RewardsModels::Store failed.
summary:
The following errors were found: Reward scheme can't be blank
resolution:
Try persisting the document with valid data or remove the validations.
# ./spec/factories/reward_scheme.rb:15:in `block (3 levels) in <top (required)>'
# ./spec/core/transaction_spec.rb:6:in `block (2 levels) in <top (required)>'
This is how the model file looks like:
module UserModels
class Store
include Mongoid::Document
include Mongoid::Timestamps
field :reward_scheme_id, type: String
field :store_id, type: String
field :store_name, type: String, default: "HQ"
field :reward_scheme_name, type:String
field :about, type: String, default: "MyGravity - loyalty begins with trust"
field :logo, type: String, default: 'https://static.mygravity.co/partners/logo.svg'
field :splash_screen_url, type: String, default: "https://static.mygravity.co/assets/SplitShire_Blur_Background_XVI.jpg"
field :awaiting_update, type: Boolean, default:false
embeds_one :location, class_name:'UserModels::Location'
embeds_one :open_hours, class_name:'UserModels::OpenHours'
embeds_one :optional, class_name:'UserModels::Optional'
embeds_many :specials, class_name:'UserModels::Special'
embeds_many :offers, class_name:'UserModels::Offer'
before_create :set_defaults
def set_defaults
self.location = UserModels::Location.new unless self.location
self.optional = UserModels::Optional.new unless self.optional
end
end
class Location
include Mongoid::Document
field :longitude, type: Float, default: -0.131425
field :latitude, type: Float, default: 51.507697
field :address_line_1, type: String, default: 'Impact Hub - Westmister'
field :post_code, type: String, default: 'SW1Y 4TE'
field :city_town, type: String, default: 'London'
embedded_in :store
end
class OpenHours
include Mongoid::Document
field :monday, type: String
field :tuesday, type: String
field :wednesday, type: String
field :thursday, type: String
field :friday, type: String
field :saturday, type: String
field :sunday, type: String
field :sunday_1, type: String
embedded_in :store
end
class Special
include Mongoid::Document
# Need this to search
field :special_id, type: Integer
field :special_uid, type: Integer
field :title, type: String
field :text, type: String
embedded_in :store
before_save :set_special_uid
def set_special_uid
self.special_uid = self.special_id
end
def attributes
# p super
# p Hash[super.map{|k,v| [(alais[k] || k), v]}]
hash = super
alais = {'special_id' => 'id'}
hash.keys.each do |k,v|
hash[ alais[k] ] = hash['special_id'].to_s if alais[k]
# Need this as special_id is mapped in the iOS to a string...
hash['special_id'] = hash['special_id'].to_s if k == 'special_id'
end
hash
end
end
class Offer
include Mongoid::Document
field :name, type: String
field :offer_id, type: Integer
field :value, type: Float, default:0.0 # monetary value
field :points, type: Integer
field :icon_url, type: String
field :icon_name, type: String
embedded_in :store
def attributes
# p super
# p Hash[super.map{|k,v| [(alais[k] || k), v]}]
hash = super
alais = {'offer_id' => 'id'}
hash.keys.each { |k,v| hash[ alais[k] ] = hash['offer_id'] if alais[k] }
hash
end
end
class Optional
include Mongoid::Document
field :email, type: String, default:""
field :twitter, type: String, default:""
field :telephone, type: String, default:""
field :wifi_password, type: String, default:""
embedded_in :store
end
end
Any leads regarding the code changes required for upgrading to mongoid 6 is highly appreciated.
Thanks

Access to parent from embedded document on creation (Mongoid)

This trick works with "has_many" relation, but fails with "embeds_many". Any ideas?
class Country
include Mongoid::Document
field :name, type: String
embeds_many :cities
end
class City
include Mongoid::Document
field :name, type: String
field :full_name, type: String, default: ->{ "#{name}, #{country.name}" }
embedded_in :country
end
1.9.3p392 :025 > c = Country.find_or_create_by(name: 'foo')
=> #<Country _id: foo, name: "foo">
1.9.3p392 :026 > c.cities.find_or_create_by(name: 'bar')
NoMethodError: undefined method `city' for nil:NilClass
So, it fails on a line "field :full_name, type: String, default: ->{ "#{name}, #{country.name}" }" becouse country is undefined for that moment
You need to check for country first, then it will return country.name
field :full_name, type: String, default: ->{ "#{name}, " << country.name if country }
I could not get this to work with string interpolation, but append works (which concatenates country.name to str)

Mapping of a geo_point field in ElasticSearch with Tire

I'm trying to index a geo_point field in Elasticsearch with the Tire gem. Here is my Tire mapping for my ActiveRecord model :
class Availability < ActiveRecord::Base
belongs_to :user
attr_accessible :date, :latitude, :longitude
include Tire::Model::Search
include Tire::Model::Callbacks
tire do
mapping do
indexes :id, type: 'integer', index: 'not_analysed'
indexes :user_id, type: 'integer', index: 'not_analysed'
indexes :user_firstname, type: 'string', as: 'user_firstname'
indexes :user_lastname, type: 'string', as: 'user_lastname'
indexes :user_level, type: 'integer', as: 'user_level'
indexes :date, type: 'date'
indexes :location, type: 'geo_type', as: 'location'
end
end
# def location
# "#{latitude},#{longitude}"
# end
def location
[longitude.to_f, latitude.to_f]
end
def user_firstname
user.firstname
end
def user_lastname
user.lastname
end
def user_level
user.level
end
end
When I create the mapping (bundle exec rake environment tire:import CLASS=Availability FORCE=true), Elasticsearch seems to ignore the geo_point type for the location field.
Here is the result of the http://localhost:9200/availabilities/_mapping call :
{
availabilities: {
availability: {
properties: {
date: {...},
id: {...},
location: {
type: "double"
},
user_firstname: {...},
user_id: {...},
user_lastname: {...},
user_level: {...}
}
}
}
}
The location field is indexed as an array of double on the documents (results of http://localhost:9200/availabilities/_search) :
{
id: 8,
...
location: [
2.301643,
48.780651
]
}
When I change the location method to :
def location
"#{latitude},#{longitude}"
end
Which is another solution to index a geo_point field according to the documentation (http://www.elasticsearch.org/guide/reference/mapping/geo-point-type.html), the result for the location mapping is :
location: {
type: "string"
},
And of course the location field is indexed as a string :
{
id: 4,
...
location: "48.780651,2.301643"
}
Any idea why the geo_point is ignored in my mapping ?
Thanks !
The location index type was mistyped.
You used geo_type instead of geo_point:
indexes :location, type: 'geo_point', as: 'location'

Resources