Let's say I want to set up a validation contract for addresses, but then I also want to set up a validator for users, and for coffee shops; both of which include an address, is it possible to re-use the AddressContract in UserContract and CoffeeShopContract?
For example, the data I want to validate might look like:
# Address
{
"first_line": "100 Main street",
"zipcode": "12345",
}
# User
{
"first_name": "Joe",
"last_name": "Bloggs",
"address:" {
"first_line": "123 Boulevard",
"zipcode": "12346",
}
}
# Coffee Shop
{
"shop": "Central Perk",
"floor_space": "2000sqm",
"address:" {
"first_line": "126 Boulevard",
"zipcode": "12347",
}
}
Yes you can reuse schemas (See: Reusing Schemas)
It would look something like this:
require 'dry/validation'
class AddressContract < Dry::Validation::Contract
params do
required(:first_line).value(:string)
required(:zipcode).value(:string)
end
end
class UserContract < Dry::Validation::Contract
params do
required(:first_name).value(:string)
required(:last_name).value(:string)
required(:address).schema(AddressContract.schema)
end
end
a = {first_line: '123 Street Rd'}
u = {first_name: 'engineers', last_name: 'mnky', address: a }
AddressContract.new.(a)
#=> #<Dry::Validation::Result{:first_line=>"123 Street Rd"} errors={:zipcode=>["is missing"]}>
UserContract.new.(u)
#=> #<Dry::Validation::Result{:first_name=>"engineers", :last_name=>"mnky", :address=>{:first_line=>"123 Street Rd"}} errors={:address=>{:zipcode=>["is missing"]}}>
Alternatively you can create schema mixins as well e.g.
AddressSchema = Dry::Schema.Params do
required(:first_line).value(:string)
required(:zipcode).value(:string)
end
class AddressContract < Dry::Validation::Contract
params(AddressSchema)
end
class UserContract < Dry::Validation::Contract
params do
required(:first_name).value(:string)
required(:last_name).value(:string)
required(:address).schema(AddressSchema)
end
end
Related
I am currently calling an API to get a publisher's Books. I am then grouping each book into different their respective story Arcs. Everything works great and I am able to retrieve the JSON in my app.
But, I'd like to change my current JSON response and remove the JSON root arcs:
Does anyone know how I can remove the JSON root and specify which book attributes I want?
Also, would a :has many => through association help simplify this and group things how i want?
This is my code.
Models
class Publisher < ActiveRecord::Base
has_many :books
end
class Arc < ActiveRecord::Base
has_many :books
end
class Book < ActiveRecord::Base
belongs_to :arc
belongs_to :book
end
Current Json
{
"arcs": [
{
"books": [
{
"arc_id": 1,
"created_at":"2014-12-27T20:54:46.518Z",
"id": 311,
"publisher_id": 7,
"updated_at":"2015-06-04T20:55:28.190Z"
}
],
"id": 1,
"name": "One-Shot"
}
]
}
How can I change it to this?
[
{
"books": [
{
"arc_id": 1,
"id": 311,
"publisher_id": 7
}
],
"id": 1,
"name": "One-Shot"
}
]
Controller
def index
#publisher = Publisher.find(params[:publisher_id])
#books = #publisher.books.order("positioning")
#results = {arcs: []}
#books.group_by(&:arc).each do |arc, books|
#results[:arcs] << {
id: arc.id,
name: arc.name,
books: books
}
end
respond_to do |format|
format.html
format.json { render :json => #results.to_json }
end
end
I have also tried using rabl in the link below, but it's not working..
https://stackoverflow.com/questions/30660136/include-group-by-parent-model-json-rabl
you could just render back without the arcs e.g.
format.json { render :json => #results[:arcs].to_json }
That being said you could also just change the controller method as well to this but you will have to change how the html response handles #results:
def index
#publisher = Publisher.find(params[:publisher_id])
#books = #publisher.books.order("positioning")
#results = #books.group_by(&:arc).map do |arc, books|
{
id: arc.id,
name: arc.name,
books: books
}
end
respond_to do |format|
format.html
format.json { render :json => #results.to_json }
end
end
This will also give you the desired result because map in this case will just return an Array of the Hashes you have designed.
In ruby how can i parse a json to an array of objects?
Example: i have 2 classes:
class Person
attr_accessor :name, :address, :email, :address
end
And:
class Address
attr_accessor :street, :city, :state, :person
end
When i make a request i get the following json:
{
"data": [
{
"id": 9111316,
"name": "Mason Lee",
"email": "normanodonnell#biospan.com",
"address": {
"state": "American Samoa",
"street": "Cameron Court",
"city": "Wakulla"
}
},
{
"id": 500019,
"name": "Stella Weeks",
"email": "hansenwhitfield#candecor.com",
"address": {
"state": "Nevada",
"street": "Lake Street",
"city": "Wacissa"
}
}
]
}
This json should be parsed into an array of Person.
For now i'm doing:
#json gem
require 'json'
#...
#parse the json and get the 'data'
parsed_json = JSON.parse json
json_data = parsed_json['data']
objects = Array.new
if json_data.kind_of?(Array)
#add each person
json_data.each { |data|
current_person = Person.new
data.each { |k, v|
current_person.send("#{k}=", v)
}
objects.push(current_person)
}
end
#return the array of Person
objects
I have a lot of objects like the above example and do this parse manually is not desirable. There is an automated way to do this?
By "automated way" i mean something like in java with jackson:
ObjectMapper mapper = new ObjectMapper();
List<Person> myObjects = mapper.readValue(json, mapper.getTypeFactory().constructCollectionType(List.class, Person.class));
You can initialize the Person with the hash:
json_data = JSON.parse(json)['data']
json_data.map do |data|
Person.new data
end
class Person
attr_accessor :name, :email, :address
def initialize params
params.each { |k,v| klass.public_send("#{k}=",v) }
end
end
If you want to choose the class dynamically, you can use:
json_data.map do |data|
klass = 'Person'
klass.get_const.new data
Why not just make the method yourself? Example:
require 'json'
def parse_json_to_class_array(data,root_node,to_klass)
json_data = JSON.parse(data)[root_node]
if json_data.is_a?(Array)
objects = json_data.map do |item|
klass = to_klass.new
item.each { |k,v| klass.public_send("#{k}=",v) }
klass
end
end
objects ||= []
end
Then for your example you could call it like so
json ="{\"data\":[
{\"id\":9111316,
\"name\":\"Mason Lee\",
\"email\":\"normanodonnell#biospan.com\",
\"address\":{
\"state\":\"American Samoa\",
\"street\":\"Cameron Court\",
\"city\":\"Wakulla\"
}
},
{\"id\":500019,
\"name\":\"Stella Weeks\",
\"email\":\"hansenwhitfield#candecor.com\",
\"address\":{
\"state\":\"Nevada\",
\"street\":\"Lake Street\",
\"city\":\"Wacissa\"
}
}
]
}"
class Person
attr_accessor :id, :name,:email, :address
end
parse_json_to_class_array(json,'data',Person)
#=>[#<Person:0x2ede818 #id=9111316, #name="Mason Lee", #email="normanodonnell#biospan.com", #address={"state"=>"American Samoa", "street"=>"Cameron Court", "city"=>"Wakulla"}>,
#<Person:0x2ede7a0 #id=500019, #name="Stella Weeks", #email="hansenwhitfield#candecor.com", #address={"state"=>"Nevada", "street"=>"Lake Street", "city"=>"Wacissa"}>]
Obviously you can expand this implementation to support single objects as well as overwrite Person#address= to perform the same operation and turn the address Hash into an Address object as well but this was not shown in your example so I did not take it this far in my answer.
A more dynamic example can be found Here
I have an employee controller which includes designation and department. Each one is separate model so i can access each api in the client side to fill each drop down. I need like view model concept in ASP.NET MVC.
For this i read work around like return 2 collections in one json object. But i am confused in the rabl file.
My controller code is below;
def employee_masters
#designations = Designation.all
#departments = Department.all
respond_with #designations
end
and my existing employee_masters.rabl is
collection :#designations
attributes :id, :name
please guide me how to change my rabl and controller code to achieve the behaviour. My expected json is below
{
"designations": [
{
"id": 1,
"name": "Program Manager"
},
{
"id": 2,
"name": "Project Manager"
},
{
"id": 3,
"name": "Tech Lead"
}
],
"departments": [
{
"id": 1,
"name": "IT"
},
{
"id": 2,
"name": "Support"
},
{
"id": 3,
"name": "Finance"
}
]
}
Modify:
I read this and got some idea. I created a class like below
class EmployeeViewModel
attr_accessor :departments, :designations
def initialize(departments, designations)
#departments = departments
#designations = designations
end
end
My controller code is
def employee_masters
#designations = Designation.all
#departments = Department.all
#employee_view_model = EmployeeViewModel.new(#departments, #designations)
respond_with #employee_view_model
end
And my rabl is
object #employee_view_model
child :departments do
extends 'api/v1/departments/show'
end
child :designations do
extends 'api/v1/designations/show'
end
Here shows error as Template::Error (undefined method `departments' for #<#:0x00000003872168>):
But when i change respond_with #employee_view_model to simply render :json => #employee_view_model it worked well and return the json. Please help to rectify rabl error
I'm trying to write data from a forum into a JSON file. The hierarchy in the JSON file is supposed to look something this:
thread_id
post_id
...some_items...
Or more specifically:
{
"0101": {
"title": "Hi everybody",
"1001": {...},
"1002": {...}
},
}
The relevant part in my function looks like this:
return {
thread_id.to_i => {
:title => title,
post_id.to_i => {...}
}
}
The result is that each post becomes the child of a new parent thread_id:
{
"0101":{
"title":"Hi everybody",
"1001":{...}
},
"0101":{
"1002":{...}
}
}
What am I doing wrong?
First of all, the JSON schema you're trying to achieve is not quite right in my opinion. See what you think of this:
{
"threads": [
{
"id": 100,
"title": "Lorem ipsum dolor sit amet",
...
"posts": [
{
"id": 1000,
"body": "Lorem ipsum dolor sit amet",
...
},
...
]
},
...
]
}
And the answer to your question depends on how your data is starting out, which we don't know, so I'll answer in terms of what I might expect the data structure to look like. (Note: don't use the constant Thread; it is already a Ruby class used for something totally unrelated.)
class ForumThread
def self.serialize(threads)
{ threads: threads.map(&:serialize) }
end
def serialize
attrs_to_serialize.inject({}) do |hash, attr|
hash[attr] = send(attr)
hash
end
end
def serialized_posts
posts.map &:serialize
end
def attrs_to_serialize
[:id, :title, ..., :serialized_posts]
end
end
class ForumPost
def serialize
attrs_to_serialize.inject({}) do |hash, attr|
hash[attr] = send(attr)
hash
end
end
def attrs_to_serialize
# same sort of thing as above
# ...
end
end
# Given the `threads` variable below holds an array or array-like
# object of ForumThread instances you could do this:
JSON.generate ForumThread.serialize(threads) # => { "threads": [...] }
I looked into different resources and still get confused on how to parse a json format to a custom object, for example
class Resident
attr_accessor :phone, :addr
def initialize(phone, addr)
#phone = phone
#addr = addr
end
end
and JSON file
{
"Resident": [
{
"phone": "12345",
"addr": "xxxxx"
}, {
"phone": "12345",
"addr": "xxxxx"
}, {
"phone": "12345",
"addr": "xxxxx"
}
]
}
what's the correct way to parse the json file into a array of 3 Resident object?
Today i was looking for something that converts json to an object, and this works like a charm:
person = JSON.parse(json_string, object_class: OpenStruct)
This way you could do person.education.school or person[0].education.school if the response is an array
I'm leaving it here because might be useful for someone
The following code is more simple:
require 'json'
data = JSON.parse(json_data)
residents = data['Resident'].map { |rd| Resident.new(rd['phone'], rd['addr']) }
If you're using ActiveModel::Serializers::JSON you can just call from_json(json) and your object will be mapped with those values.
class Person
include ActiveModel::Serializers::JSON
attr_accessor :name, :age, :awesome
def attributes=(hash)
hash.each do |key, value|
send("#{key}=", value)
end
end
def attributes
instance_values
end
end
json = {name: 'bob', age: 22, awesome: true}.to_json
person = Person.new
person.from_json(json) # => #<Person:0x007fec5e7a0088 #age=22, #awesome=true, #name="bob">
person.name # => "bob"
person.age # => 22
person.awesome # => true
require 'json'
class Resident
attr_accessor :phone, :addr
def initialize(phone, addr)
#phone = phone
#addr = addr
end
end
s = '{"Resident":[{"phone":"12345","addr":"xxxxx"},{"phone":"12345","addr":"xxxxx"},{"phone":"12345","addr":"xxxxx"}]}'
j = JSON.parse(s)
objects = j['Resident'].inject([]) { |o,d| o << Resident.new( d['phone'], d['addr'] ) }
p objects[0].phone
"12345"
We recently released a Ruby library static_struct that solves the issue. Check it out.