jsonb column in rails table - ruby-on-rails

I have a column in rails
t.jsonb "supported_currencies", default: [], null: false
I have to store data in column like that in loop
[ { "currency": "USD", "rate": 1.3 },{ "currency": "ZAR", "rate": 1.03 },
{ "currency": "ZAD", "rate": 1.93 } ]
or
[ { "currency": "USD", "rate": 1.3 },{ "currency": "ZAR", "rate": 1.03 } ]
from form it can select multiple currency and rate.
How I save data init like that to create table.

If I understand you need to store some arrays in the database
For this purpose you can use such column
t.text :supported_currencies, array: true, default: [], null: false
Just as examle that you can use it as usual array:
record.supported_currencies << { currency: "USD", rate: 1.3 }
record.supported_currencies << { currency: "ZAR", rate: 1.03 }
record.save

Related

Graphql mutation error: "Field 'createUser' is missing required arguments: input"

I'm trying to follow along this article on how to create a mutation on a rails server using GraphQl https://www.howtographql.com/graphql-ruby/4-authentication
However, I'm stuck at the CreateUser Mutation step, I get the follow error hash when trying it out in GraphiQL:
{
"errors": [
{
"message": "Field 'createUser' is missing required arguments: input",
"locations": [
{
"line": 45,
"column": 3
}
],
"path": [
"mutation CreateUser",
"createUser"
],
"extensions": {
"code": "missingRequiredArguments",
"className": "Field",
"name": "createUser",
"arguments": "input"
}
},
{
"message": "Field 'createUser' doesn't accept argument 'username'",
"locations": [
{
"line": 46,
"column": 5
}
],
"path": [
"mutation CreateUser",
"createUser",
"username"
],
"extensions": {
"code": "argumentNotAccepted",
"name": "createUser",
"typeName": "Field",
"argumentName": "username"
}
},
{
"message": "Field 'createUser' doesn't accept argument 'authProvider'",
"locations": [
{
"line": 47,
"column": 5
}
],
"path": [
"mutation CreateUser",
"createUser",
"authProvider"
],
"extensions": {
"code": "argumentNotAccepted",
"name": "createUser",
"typeName": "Field",
"argumentName": "authProvider"
}
},
{
"message": "Variable $username is declared by CreateUser but not used",
"locations": [
{
"line": 44,
"column": 1
}
],
"path": [
"mutation CreateUser"
],
"extensions": {
"code": "variableNotUsed",
"variableName": "username"
}
},
{
"message": "Variable $email is declared by CreateUser but not used",
"locations": [
{
"line": 44,
"column": 1
}
],
"path": [
"mutation CreateUser"
],
"extensions": {
"code": "variableNotUsed",
"variableName": "email"
}
},
{
"message": "Variable $password is declared by CreateUser but not used",
"locations": [
{
"line": 44,
"column": 1
}
],
"path": [
"mutation CreateUser"
],
"extensions": {
"code": "variableNotUsed",
"variableName": "password"
}
}
]
}
I just followed the code in the article, my files:
create_user.rb
module Mutations
class CreateUser < BaseMutation
# often we will need input types for specific mutation
# in those cases we can define those input types in the mutation class itself
class AuthProviderSignupData < Types::BaseInputObject
argument :credentials, Types::AuthProviderCredentialsInput, required: false
end
argument :username, String, required: true
argument :auth_provider, AuthProviderSignupData, required: false
type Types::UserType
def resolve(username: nil, auth_provider: nil)
User.create!(
username: username,
email: auth_provider&.[](:credentials)&.[](:email),
password: auth_provider&.[](:credentials)&.[](:password)
)
end
end
end
user_type.rb
module Types
class UserType < BaseObject
field :id, ID, null: false
field :email, String, null: false
field :username, String, null: false
field :photo, String, null: true
field :phone, String, null: false
field :island, IslandType, null: false, method: :island
field :archipel, ArchipelType, null: false, method: :archipel
field :created_at, String, null: false
field :updated_at, String, null: false
end
end
I have no clue where this 'input' thing is coming from.
Without realizing I inilialized my project with a configuration that used Relay.
By commenting this code inside my **_schema.rb file it worked again.
# Opt in to the new runtime (default in future graphql-ruby versions)
# use GraphQL::Execution::Interpreter
# use GraphQL::Analysis::AST
# Add built-in connections for pagination
# use GraphQL::Pagination::Connections
As well as these lines inside base_mutation.rb and replaces with these.
# class BaseMutation < GraphQL::Schema::RelayClassicMutation
# argument_class Types::BaseArgument
# field_class Types::BaseField
# input_object_class Types::BaseInputObject
# object_class Types::BaseObject
# end
class BaseMutation < GraphQL::Schema::Mutation
null false
end
If you're not interested in commenting out fields... I ran across the same error. For whatever reason input is the name of the key you pass your arguments into as a hash/object.
Example from using this tutorial:https://www.howtographql.com/graphql-ruby/3-mutations/
mutation {
createLink(input: {
url: "foo",
description:"bar"
}) {
url
description
}
}

Build a grouped JSON object

I am building a Rails/Backbone app and I will show users in a table.
I want to create a JSON tree which is grouped on the user ID/Name.
This is how it looks now
[{
"total_entries": 2
},
{
"entries": [{
"id": 21,
"status": "pending",
"starts_at_date": "2018-02-02",
"starts_at_time": "12:00",
"ends_at_date": "2018-02-02",
"ends_at_time": "12:00",
"description": "",
"ttype": "vacation",
"sum": 0,
"user": {
"id": 1,
"fullname": "Marcus Lurem"
},
"timetype": null,
"pause": 0,
"can_manage": true
},
{
"id": 22,
"status": "pending",
"starts_at_date": "2018-02-07",
"starts_at_time": "12:00",
"ends_at_date": "2018-02-07",
"ends_at_time": "12:00",
"description": "",
"ttype": "doctor",
"sum": 0,
"user": {
"id": 2,
"fullname": "Anna Palmgren"
},
"timetype": null,
"pause": 0,
"can_manage": true
}
]
}
]
I need it to be grouped on the name.
This is how I build the JSON object now.
json.array! [0,1] do |index|
if index == 0
json.total_entries #total
else
json.entries #events do |event|
json.extract! event, :id, :starts_at_date, :starts_at_time, :ends_at_date, :ends_at_time, :description, :ttype
json.sum event.sum
json.user event.user, :id, :fullname
json.can_manage true
end
end
end
Update
Should look like this more or less.
Marcus Lurem
Id
Status
starts_at_date
description
Anna Palmgren
Id
Status
starts_at_date
description
You can use something like this:
json.entries #events.group_by(&:user) do |user, events|
json.user :id, :fullname
json.events events do |event|
json.extract! event, :id, :starts_at_date, :starts_at_time, :ends_at_date, :ends_at_time, :description, :ttype
end
end

Function Score attribute to rank searches based on clicks not working with elastic search and rails

I have implemented the function score attribute in my document model which contains a click field that keeps tracks of a number of view per document. Now I want the search results to get more priority and appear at the top based on the clicks per search
My document.rb code
require 'elasticsearch/model'
def self.search(query)
__elasticsearch__.search(
{
query: {
function_score: {
query: {
multi_match: {
query: query,
fields: ['name', 'service'],
fuzziness: "AUTO"
}
},
field_value_factor: {
field: 'clicks',
modifier: 'log1p',
factor: 2
}
}
}
}
)
end
settings index: { "number_of_shards": 1,
analysis: {
analyzer: {
edge_ngram_analyzer: { type: "custom", tokenizer: "standard", filter:
["lowercase", "edge_ngram_filter", "stop", "kstem" ] },
}
},
filter: { ascii_folding: { type: 'asciifolding', preserve_original: true
},
edge_ngram_filter: { type: "edgeNGram", min_gram: "3", max_gram:
"20" }
}
} do
mapping do
indexes :name, type: "string", analyzer: "edge_ngram_analyzer",
term_vector: "with_positions"
indexes :service, type: "string", analyzer: "edge_ngram_analyzer",
term_vector: "with_positions"
end
end
end
Search View is here
<h1>Document Search</h1>
<%= form_for search_path, method: :get do |f| %>
<p>
<%= f.label "Search for" %>
<%= text_field_tag :query, params[:query] %>
<%= submit_tag "Go", name: nil %>
</p>
<% end %>
<% if #documents %>
<ul class="search_results">
<% #documents.each do |document| %>
<li>
<h3>
<%= link_to document.name, controller: "documents", action: "show",
id: document._id %>
</h3>
</li>
<% end %>
</ul>
<% else %>
<p>Your search did not match any documents.</p>
<% end %>
<br/>
When I search for Estamp, I get the results follow in the following order:
Franking and Estamp # clicks 5
Notary and Estamp #clicks 8
So clearly when the Notary and Estamp had more clicks it does not come to the top of the search.How can I achieve this?
This is what I get when I run it on the console.
POST _search
"hits": {
"total": 2,
"max_score": 1.322861,
"hits": [
{
"_index": "documents",
"_type": "document",
"_id": "13",
"_score": 1.322861,
"_source": {
"id": 13,
"name": "Franking and Estamp",
"service": "Estamp",
"user_id": 1,
"clicks": 7
},
{
"_index": "documents",
"_type": "document",
"_id": "14",
"_score": 0.29015404,
"_source": {
"id": 14,
"name": "Notary and Estamp",
"service": "Notary",
"user_id": 1,
"clicks": 12
}
}
]
Here the score of the documents is not getting updated based on the clicks
Without seeing your indexed data it's not easy to answer. But looking at the query one thing comes to my mind, I'll show it with short example:
Example 1:
I've indexed following documents:
{"name":"Franking and Estampy", "service" :"text", "clicks": 5}
{"name":"Notary and Estamp", "service" :"text", "clicks": 8}
Running the same query you provided gave this result:
"hits": {
"total": 2,
"max_score": 4.333119,
"hits": [
{
"_index": "script",
"_type": "test",
"_id": "AV2iwkems7jEvHyvnccV",
"_score": 4.333119,
"_source": {
"name": "Notary and Estamp",
"service": "text",
"clicks": 8
}
},
{
"_index": "script",
"_type": "test",
"_id": "AV2iwo6ds7jEvHyvnccW",
"_score": 3.6673431,
"_source": {
"name": "Franking and Estampy",
"service": "text",
"clicks": 5
}
}
]
}
So everything is fine - document with 8 clicks got higher scoring (_score field value) and the order is correct.
Example 2:
I noticed in your query that name field is boosted with high factor. So what would happen if I had following data indexed?
{"name":"Franking and Estampy", "service" :"text", "clicks": 5}
{"name":"text", "service" :"Notary and Estamp", "clicks": 8}
And result:
"hits": {
"total": 2,
"max_score": 13.647502,
"hits": [
{
"_index": "script",
"_type": "test",
"_id": "AV2iwo6ds7jEvHyvnccW",
"_score": 13.647502,
"_source": {
"name": "Franking and Estampy",
"service": "text",
"clicks": 5
}
},
{
"_index": "script",
"_type": "test",
"_id": "AV2iwkems7jEvHyvnccV",
"_score": 1.5597181,
"_source": {
"name": "text",
"service": "Notary and Estamp",
"clicks": 8
}
}
]
}
Although Franking and Estampy has only 5 clicks, it has much much higher scoring than the second document with greater number of clicks.
So the point is that in your query, the number of clicks is not the only factor that has an impact on scoring and final order of documents. Without the real data it's only a guess from my side. You can run the query yourself with some REST client and check scoring/field/matching phrases.
Update
Based on your search result - you can see that document with id=13 has Estamp term in both fields (name and service). That is the reason why this document got higer scoring (it means that in the algorithm of calculating scoring it is more important to have the term in both fields than have higher number of clicks). If you want clicks field to have bigger impact on the scoring, try to experiment with factor (probably should be higher) and modifier ("modifier": "square" could work in your case). You can check possible values here.
Try for example this combination:
{
"query": {
"function_score": {
... // same as before
},
"field_value_factor": {
"field": "clicks" ,
"modifier": "square",
"factor": 3
}
}
}
}
Update 2 - scoring based only on number of clicks
If the only parameter that should have an impact on scoring should be the value in clicks field, you can try to use "boost_mode": "replace" - in this case only function score is used, the query score is ignored. So the frequency of Estamp term in name and service fields will have no impact on the scoring. Try this query:
{
"query": {
"function_score": {
"query": {
"multi_match": {
"query": "Estamp",
"fields": [ "name", "service"],
"fuzziness": "AUTO"
}
},
"field_value_factor": {
"field": "clicks",
"factor": 1
},
"boost_mode": "replace"
}
}
}
It gave me:
{
"took": 2,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"failed": 0
},
"hits": {
"total": 2,
"max_score": 5,
"hits": [
{
"_index": "script",
"_type": "test",
"_id": "AV2nI0HkJPYn0YKQxRvd",
"_score": 5,
"_source": {
"name": "Notary and Estamp",
"service": "Notary",
"clicks": 5
}
},
{
"_index": "script",
"_type": "test",
"_id": "AV2nIwKvJPYn0YKQxRvc",
"_score": 4,
"_source": {
"name": "Franking and Estamp",
"service": "Estamp",
"clicks": 4
}
}
]
}
}
This may be the one you are looking for (note the values "_score": 5 and "_score": 4 are matching the number of clicks).

Parsing and then reading specific values of JSON / HTTParty::Response

I want to be able to retrieve the tier and division from this code, however when using the response object from HTTParty and doing res[0]["#{id}"]["tier"] it comes up with "cannot implicitly convert string to integer", which means it expects an integer, but I don't know where
This is the response I get (I'm doing it in a loop which is why I'm putting in the ID with "#{id}")
{"37714607": [
{
"queue": "RANKED_SOLO_5x5",
"name": "Diana's Patriots",
"entries": [{
"leaguePoints": 32,
"isFreshBlood": false,
"isHotStreak": false,
"division": "IV",
"isInactive": false,
"isVeteran": false,
"losses": 65,
"playerOrTeamName": "Wicked7000",
"playerOrTeamId": "37714607",
"wins": 59
}],
"tier": "GOLD"
},
{
"queue": "RANKED_TEAM_5x5",
"name": "Nasus's Justicars",
"entries": [{
"leaguePoints": 81,
"isFreshBlood": false,
"isHotStreak": false,
"division": "V",
"isInactive": false,
"isVeteran": false,
"losses": 73,
"playerOrTeamName": "Pink Fedoras",
"playerOrTeamId": "TEAM-5ffedf90-45ba-11e4-9e4b-c81f66db8bc5",
"wins": 73
}],
"tier": "SILVER"
},
{
"queue": "RANKED_TEAM_3x3",
"name": "Cassiopeia's Marksmen",
"entries": [{
"leaguePoints": 0,
"isFreshBlood": false,
"isHotStreak": true,
"division": "I",
"isInactive": false,
"isVeteran": false,
"losses": 3,
"playerOrTeamName": "The Booty Brothers",
"playerOrTeamId": "TEAM-53a65b60-ff2d-11e4-9e51-c81f66dba0e7",
"wins": 7
}],
"tier": "BRONZE"
}
]}
As your json something like below
{"37714607": [
{
"queue": "RANKED_SOLO_5x5",
"name": "Diana's Patriots",
"entries": [{
"leaguePoints": 32,
"isFreshBlood": false,
"isHotStreak": false,
"division": "IV",
"isInactive": false,
"isVeteran": false,
"losses": 65,
"playerOrTeamName": "Wicked7000",
"playerOrTeamId": "37714607",
"wins": 59
}],
"tier": "GOLD"
},
so it will first id = "37714607" then an array start([) the array contains hashes so first hash has "tier" key
so it should be
tiers = []
res["#{id}"].each do |result| #id = 37714607
tiers << result["tier"]
end
Seems like you need to do res[id.to_s][0]["tier"] instead – first take the root key, then first element (you did it vice versa).

Elasticsearch Facet List doesn't Match Results

Problem
When I filter by a particular facet, that specific field's facets are correctly filtered in the result but the other facet fields remain the same. Best way to explain this is with the query and the response.
Query
{
query: {
match_all: {}
},
filter: {
and: [{
term: {
"address.state": "oregon"
}
}]
},
facets: {
"address.city": {
terms: {
field: "address.city"
},
facet_filter: {}
},
"address.state": {
terms: {
field: "address.state"
},
facet_filter: {
and: [{
term: {
"address.state": "oregon"
}
}]
}
},
"address.country": {
terms: {
field: "address.country"
},
facet_filter: {}
}
}
}
Result
{
"took": 2,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"failed": 0
},
"hits": {
"total": 1,
"max_score": 1,
"hits": [
{
"_index": "races",
"_type": "race",
"_id": "6",
"_score": 1,
"_source": {
"id": 6,
"name": "Eugene Marathon",
"description": "...",
"created_at": "2015-05-24T19:41:45.043Z",
"updated_at": "2015-05-24T19:41:45.046Z",
"address": {
"race_id": 6,
"id": 7,
"line1": null,
"line2": null,
"city": "Eugene",
"state": "oregon",
"country": "united_states",
"zip": null,
"user_id": null,
"created_at": "2015-05-24T19:41:45.044Z",
"updated_at": "2015-05-24T19:41:45.044Z"
},
"race_years": []
}
}
]
},
"facets": {
"address.city": {
"_type": "terms",
"missing": 0,
"total": 7,
"other": 0,
"terms": [
{
"term": "long beach",
"count": 1
},
{
"term": "lincoln",
"count": 1
},
{
"term": "las vegas",
"count": 1
},
{
"term": "jackson",
"count": 1
},
{
"term": "eugene",
"count": 1
},
{
"term": "duluth",
"count": 1
},
{
"term": "denver",
"count": 1
}
]
},
"address.state": {
"_type": "terms",
"missing": 0,
"total": 1,
"other": 0,
"terms": [
{
"term": "oregon",
"count": 1
}
]
},
"address.country": {
"_type": "terms",
"missing": 0,
"total": 7,
"other": 0,
"terms": [
{
"term": "united_states",
"count": 7
}
]
}
}
}
So as you can see it returns all the address.city facets even though the only result is located in Eugene. It is also returning a count of 7 on the united_states. Why would it be returning all of these extra facets and with incorrect counts? My ruby mapping is found below.
Ruby Mapping
settings index: {
number_of_shards: 1,
analysis: {
analyzer: {
facet_analyzer: {
type: 'custom',
tokenizer: 'keyword',
filter: ['lowercase', 'trim']
}
}
}
} do
mapping do
indexes :name, type: 'string', analyzer: 'english', boost: 10
indexes :description, type: 'string', analyzer: 'english'
indexes :address do
indexes :city, type: 'string', analyzer: 'facet_analyzer'
indexes :state, type: 'string'
indexes :country, type: 'string'
end
end
end
This is the normal behavior of facets when ran against a filter. From the official documentation:
There’s one important distinction to keep in mind. While search
queries restrict both the returned documents and facet counts, search
filters restrict only returned documents — but not facet counts.
In your case, your query matches all documents (i.e. match_all) so the facet counts are counted against all documents, too.
Change your query to this and your facet counts will change (in this case you don't need the facet_filter anymore):
{
query: {
term: {
"address.state": "oregon"
}
},
facets: {
"address.city": {
terms: {
field: "address.city"
}
},
"address.state": {
terms: {
field: "address.state"
}
},
"address.country": {
terms: {
field: "address.country"
}
}
}
}
Another thing worth noting is that facets are deprecated and have been replaced by the much more powerful aggregations.

Resources