How to include class name when calling to_json in rails3? - ruby-on-rails-3

I'm trying to implement autocomplete that lets user to pick from list of 2 different kind of models.
This is how my controller looks:
def ac
arr = []
arr << Foo.all
arr << Bar.all
render json: arr.to_json
end
Which renders:
[[{"id":1, "name":"foo name"}], [{"id":1, "name":"bar name"}]]
How to include class name and get something like this:
[
[{"id":1, "name":"foo name", "class_name":"Foo"}],
[{"id":1, "name":"bar name", "class_name":"Bar"}]
]
?

If you don't mind doing a bit of extra work you can do smth like that with :methods option of as_json method (and to_json as well):
class Foo
def class_name
self.class.name
end
end
arr = Foo.all.map { |foo| foo.as_json(:methods => [:class_name]) }
puts arr.to_json
#=> [{ "id": 1, "name": "foo name", "class_name": "Foo" }]
If you have ActiveRecord::Base.include_root_in_json set to true (that is default afaik) then you'll get hashes like
{ "foo": { "id": 1, "name": "foo name" } }
If you want it to be exactly the class name you can pass :root option:
foo = Foo.last
puts foo.to_json(:root => foo.class.name)
#=> { "Foo": { "id": 1, "name": "foo name" } }
Note that both these solutions do not allow you simply to call to_json on an array of records. To overcome that and make class_name included by default you can override serializable_hash method in your model like that:
def serializable_hash(*)
super.merge('class_name' => self.class.name)
end
If you wrap it into a module you can include it in any model you want and get class_name included into the result of as_json or to_json without passing any extra options to these methods. You can modify the implementation a bit to respect :except option if you want to exclude class_name in some cases.

ended up adding 1 additional step:
arr << Foo.all
arr << Bar.all
arr.flatten!
arr=arr.collect{|itm| {"id":"#{itm.class.to_s}:#{itm.id}", "value":itm.name}}
then I just spit it out:
render json: arr.to_json()
as a result I get:
[{"id":"Foo:1", "value":"Foo #1"},{"id":"Bar:1", "value":"Bar #1"}]

Related

Karate json key list variable assignment

New to Karate, and JSON, for that matter, but I've got a variable like:
response {
entries {
products [
{
names [
"Peter Parker",
"Tony Stark",
"Captain America"
]
},
{
names [
"Thomas Tinker",
"Jimmy Johnson",
"Mama Martha"
]
}
]
}
}
match each response.entries.products[*].names returns a list like:
["Peter Parker","Tony Stark","Captain America","Thomas Tinker","Jimmy Johnson","Mama Martha"]
But I'd like to assign that output to a variable, such as:
* def variable = response.entries.products[*].names
that would hold a similar value. When I use the above line, I get the following error:
Expected an operand but found *
Is it possible to achieve that, or something similar? If so, how?
Thanks!
Yes, there is syntax for that:
* def variable = $response.entries.products[*].names
Read the docs: https://github.com/intuit/karate#get

How to use Schema.from_dict() for nested dictionaries?

I am trying to create a Schema class using nested dictionaries that has some list as elements. However when I do a dumps() Only the top level elements are dumped.
Have a rest api that returns a list of certain things,eg. list of users. but the schema is such that certain aggregate details are sent at the top level, the data looks something like this. This is what i am expecting as output:
{
"field1": 5,
"field2": false,
"field3": {
"field4": 40,
"field5": [
{
"field6": "goo goo gah gah",
"field7": 99.341879,
"field8": {
"field9": "goo goo gah gah",
"field10": "goo goo gah gah"
}
}]
}
}
Heres my code:
MySchema = Schema.from_dict(
{
"field1": fields.Int(),
"field2": fields.Bool(),
"field3": {
"field4": fields.Int(),
"field5": [
{
"field6": fields.Str(),
"field7": fields.Float(),
"field8": {
"field9": fields.Str(),
"field10": fields.Str()
}
}]
}
}
)
#Then use it like:
response = MySchema().dumps(data)
Actual result:
"{\"field1\": 5, \"field2\": false}"
Option 1
You're looking for several nested schemas, interconnected through fields.Nested:
from marshmallow import Schema, fields
Field8Schema = Schema.from_dict({
"field9": fields.Str(),
"field10": fields.Str()
})
Field5Schema = Schema.from_dict({
"field6": fields.Str(),
"field7": fields.Float(),
"field8": fields.Nested(Field8Schema),
})
Field3Schema = Schema.from_dict({
"field4": fields.Int(),
"field5": fields.List(fields.Nested(Field5Schema))
})
MySchema = Schema.from_dict({
"field1": fields.Int(),
"field2": fields.Bool(),
"field3": fields.Nested(Field3Schema),
})
MySchema().dump(data)
# {'field2': False,
# 'field1': 5,
# 'field3': {'field4': 40,
# 'field5': [{'field6': 'goo goo gah gah',
# 'field8': {'field9': 'goo goo gah gah', 'field10': 'goo goo gah gah'},
# 'field7': 99.341879}]}}
Option 2
If the nesting won't be that deep, it might be simpler to use decorators, i.e. nest and unnest data as suggested in the docs:
class UserSchema(Schema):
#pre_load(pass_many=True)
def remove_envelope(self, data, many, **kwargs):
namespace = 'results' if many else 'result'
return data[namespace]
#post_dump(pass_many=True)
def add_envelope(self, data, many, **kwargs):
namespace = 'results' if many else 'result'
return {namespace: data}
It feels it fits your case nicely.
Comments
I'd suggest not to use from_dict as it is less readable for such a complex data, and instead switch to a class-based schema.
There's plenty of good examples of nesting in the docs.

Karate : Dynamically input values from embedded expressions in a JSON file

* def mpRequestJson =
"""
{
"entity": '<entity>',
"consent": {
"PHONE": <updategetPhonePref>,
"EMAIL": true,
"POST": false,
"SMS": <updategetSMSPref>
},
"notices": [{
"title": "Privacy policy",
"version": "NA"
}],
"source": "web"
}
"""
Given path '<entity>'
And request mpRequestJson
When method PUT
Then status 200
And match $.consent.PHONE == '<updategetPhonePref>'
And match $.consent.SMS == '<updategetSMSPref>'
Examples:
|entity | updategetPhonePref|updategetSMSPref|
|xyz| #(updategetPhonePref)|#(updategetSMSPref)|
If i want to store the JSON request in a JSON file rather than in the feature file, what should be my JSON file?
In the JSON use embedded expressions, e.g.
entity: '#(entity)'
Then you can read it from a file:
* def mpRequestJson = read('my.json')
But before the read you should initialize variables that have to be substituted. So you will have some extra lines.
* def entity = '<entity>'
One way to reduce the extra lines is to create a temp JSON:
* def data = { entity: '<entity'>, phone: '<updategetPhonePref>' }
And then you can do this in the JSON:
entity: '#(data.entity)'
Read the docs on data driven tests also please.

RoR model.where() using pg array type

I stumbled on the idea of limiting my tables and associations by using arrays in my models like so.
I'm working on a task assignment app where the user will, in essence, construct a sentence to perform an action. I'm using keywords to help structure the boundaries of the sentence.
Examples include (keywords in brackets):
"[I will] paint the fence" <- makes a new task, assigned to current_user
"[Ask] Huck [to] paint the fence" <- find_or_create task, assign to find_or_create user
"[Archive] painting the fence" <- soft-delete task
So here's my migration:
class CreateKeywords < ActiveRecord::Migration[5.1]
def change
create_table :keywords do |t|
t.string :name, null: false
t.text :pre, array: true, default: []
t.text :post, array: true, default: []
t.string :method, null: false, default: "read" # a CRUD indicator
end
end
end
keyword.post indicates what models could follow the keyword
keyword.pre indicates what models could preceed the keyword
My seed data looks like this:
Keyword.create([
{ name: "i will", post: ["task"] },
{ name: "ask", post: ["user"] },
{ name: "assign", post: ["user", "task"] },
{ name: "find a", post: ["user", "task"] },
{ name: "make a new", post: ["user", "task"], method: "create" },
{ name: "finalize", post: ["task"] },
{ name: "archive", post: ["user", "task"], method: "delete" },
{ name: "update", post: ["user", "task"], method: "update" },
{ name: "for", post: ["user", "task"], pre: ["user", "task"] },
{ name: "to", post: ["user", "task"], pre: ["user", "task"] },
{ name: "and repeat", pre: ["task"] },
{ name: "before", pre: ["task"] },
{ name: "after", pre: ["task"] },
{ name: "on", pre: ["task"] }
])
Now I want to do things like:
key = Keyword.third
Keyword.where(pre: key.post)
But this returns exact matches and I want to do something like:
"Return all keywords where any value of key.post can be found in Keyword.pre"
I haven't had any luck along these lines:
Keyword.where(pre.include? key.post)
I can iterate over all the Keywords and use AND:
results = []
Keyword.all.each do |k|
comb = k.pre & key.post
if comb.present?
results << k
end
end
results.map { |k| k.name }
But this feels bad.
And I'm a bit out of my depth on the SQL necessary to do this.
Any pointers?
There are two things you want to know about:
PostgreSQL's "array constructor" syntax which looks like array['a', 'b', 'c'] rather than the more common '{a,b,c}' syntax.
PostgreSQL's array operators.
Array constructor syntax is convenient because when ActiveRecord sees an array as the value for a placeholder, it expands the array as a comma delimited list which works equally well with x in (?) and x && array[?].
For the operator to use, you want:
all keywords where any value of key.post can be found in Keyword.pre
which is another way of saying that key.post and Keyword.pre overlap and the operator for that is &&. There are also subset (<#) and superset (#>) operators if you need slightly different logic.
Putting that together:
Keyword.where('pre && array[?]', key.post)
or:
Keyword.where('pre && array[:post]', post: key.post)
lacostenycoder, in the comments, is right to be concerned empty arrays. ActiveRecord expands empty arrays to a single null when filling in a placeholder so you could end up doing SQL like:
pre && array[null]
and PostgreSQL won't be able to determine the type of array[null]. You can include a cast:
Keyword.where('pre && array[:post]::text[]', post: key.post)
# --------------------------------^^^^^^^^ tell PostgreSQL that this is an array of text
But, since pre && array[null]::text[] will never be true, you could just skip the whole thing if key.post.empty?.
Empty arrays don't overlap with any other array (not even another empty array) so you don't need to worry about empty arrays beyond what ActiveRecord will do with them. Similarly for pre is null, null && any_array will never be true (it will actually evaluate to null) so there won't be any overlaps with such things.
This is actually much easier that you would expect:
Keyword.where("pre && post")
# testing vs your seed data plus a few I added returns:
#Keyword Load (1.0ms) SELECT "keywords".* FROM "keywords" WHERE (pre && post)
#[<Keyword:0x007fc03c0438b0 id: 9, name: "for", pre: ["user", "task"], post: ["user", "task"], method: "read">,
#<Keyword:0x007fc03c0435b8 id: 10, name: "to", pre: ["user", "task"], post: ["user", "task"], method: "read">,
#<Keyword:0x007fc03c043248 id: 15, name: "foo", pre: ["bar", "baz"], post: ["baz", "boo"], method: "read">,
#<Keyword:0x007fc03c042f28 id: 16, name: "rock", pre: ["anthem"], post: ["ballad", "anthem"], method: "read">,
#<Keyword:0x007fc03c042cf8 id: 17, name: "disco", pre: ["mood"], post: ["ballad", "anthem", "mood"], method: "read">]

Django Rest Framework Displaying Serialized data through Views.py

class International(object):
""" International Class that stores versions and lists
countries
"""
def __init__(self, version, countrylist):
self.version = version
self.country_list = countrylist
class InternationalSerializer(serializers.Serializer):
""" Serializer for International page
Lists International countries and current version
"""
version = serializers.IntegerField(read_only=True)
country_list = CountrySerializer(many=True, read_only=True)
I have a serializer set up this way, and I wish to display serialized.data (which will be a dictionary like this: { "version": xx, and "country_list": [ ] } ) using views.py
I have my views.py setup this way:
class CountryListView(generics.ListAPIView):
""" Endpoint : somedomain/international/
"""
## want to display a dictionary like the one below
{
"version": 5
"country_list" : [ { xxx } , { xxx } , { xxx } ]
}
What do I code in this CountryListView to render a dictionary like the one above? I'm really unsure.
Try this
class CountryListView(generics.ListAPIView):
""" Endpoint : somedomain/international/
"""
def get(self,request):
#get your version and country_list data and
#init your object
international_object = International(version,country_list)
serializer = InternationalSerializer(instance=international_object)
your_data = serializer.data
return your_data
You can build on the idea from here:
http://www.django-rest-framework.org/api-guide/pagination/#example
Suppose we want to replace the default pagination output style with a modified format that includes the next and previous links under in a nested 'links' key. We could specify a custom pagination class like so:
class CustomPagination(pagination.PageNumberPagination):
def get_paginated_response(self, data):
return Response({
'links': {
'next': self.get_next_link(),
'previous': self.get_previous_link()
},
'count': self.page.paginator.count,
'results': data
})
As long as you don't need the pagination, you can setup a custom pagination class which would pack your response in whichever layout you may need:
class CountryListPagination(BasePagination):
def get_paginated_response(self, data):
return {
'version': 5,
'country_list': data
}
Then all you need to do is to specify this pagination to your class based view:
class CountryListView(generics.ListAPIView):
# Endpoint : somedomain/international/
pagination_class = CountryListPagination
Let me know how is this working for you.