Rails JBuilder recursive lookup - ruby-on-rails

I am using Rails 4, and I have some simple models as follow:
class Question < ActiveRecord::Base
# columns (id, text)
has_many :answers
end
class Answer < ActiveRecord::Base
# columns (id, text, next_question_id)
belongs_to :question
end
You can see that an answer has a next_question_id column, which will be used to look up another question. I want to generate a tree-structure json like this:
{
"text": "This is question 1",
"answers": [
{
"text": "This is answer a",
"next_question": {
"text": "This is question 2",
"answers": [
{
"text": "This is answer c",
"next_question":
}
]
}
},
{
"text": "This is answer b",
"next_question": {
"text": "This is question 2",
"answers": [
{
"text": "This is answer d",
"next_question":
}
]
}
}
]
}
How can I achieve this with JBuilder? I tried the solution here, but I cannot pass the json argument to the helper function.

The standard aproach for rendering trees is using a recursive partial. To implement this you'll first need to add a method to your Answer model like this.
def next_question
Question.find(next_question_id) if next_question_id
end
(hint: alternetively you could just set a belongs_to :next_question, class_name: Question association on your Answer model)
Then you create a partial like _question.json.jbuilder that goes like this:
json.(question,:id, :text)
json.answers question.answers do |answer|
json.(answer, :id, :text)
json.partial!(:question, question: answer.next_question) if answer.next_question
end
Then in the controller you take the first question in your survey and put it in say the #first_question variable.
And the last thing: in your view you write
json.partial! :question, question: #first_question

Related

POST list of objects in rails. How can I whitelist the list of objects in strong params?

I'm trying to post a list of objects called tasks that looks like the following.
{
// Shared attributes
"parent_id": "1",
"author": "Name Name",
// Task list
"tasks": [
{"name":"task 1"},
{"name":"task 2"}...
]
}
In my controller, I have the following, which iterates through tasks, appends the shared attributes, and creates a new Task object:
task_params[:tasks].each do |task|
new_params = task
new_params[:parent_id] = task_params[:parent_id]
new_params[:author] = task_params[:author]
new_task = Task.new(new_params)
end
The problem is that :tasks isn't showing up in my task_params despite being passed in as a parameter. This seems to be because it isn't whitelisted in strong_params, but that doesn't seem to make a difference. It looks to me like I can only whitelist parameters that correspond to attr_accessors. So how can I pass in the custom parameter tasks through strong_params and into the controller method?
Edit:
My params definition:
def task_params
params.require(:task).permit(:parent_id, :author, tasks: [:name])
end
Rails output:
Parameters: {"parent_id"=>1, "author"=> "Name name", "tasks"=>[{"name"=>"task 1"}, {"name"=>"task 2"}], "task"=>{"job_id"=>1, "author"=>"Name name"}}
But when I print the task params in the controller:
{"parent_id"=>1, "author"=>"Name name"}
Look at the documentation on nested parameters with Strong Parameters. You'd do something like:
params.permit(:parent_id, :author, tasks: [:name])
Are the tasks associated to one another in the model (might be worth copying that code into the question)? If so, do you just need to use accepts_nested_attributes_for?
Docs are here: http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html#method-i-accepts_nested_attributes_for
That way, by adding accepts_nested_attributes_for :tasks (or however you're naming the association) and using fields_for in the form, you should be able to access task data in the params (it'll still need permitting).
Put simply, from the docs:
Defines an attributes writer for the specified association(s).
If you're using the stringified task data, you can probably use an attr_reader in the model to allow this to be passed in the params.
This help?
Your params, as posted in your question, are malformed (missing a comma, a close bracket, etc.). That's probably a cut and paste error, I'm guessing. So, let's assume they are supposed to look like this:
{
task: {
"parent_id"=>1,
"author": "Name name",
"tasks"=>[
{"name"=>"task 1"},
{"name"=>"task 2", "task"=>{"job_id"=>1, "author"=>"Name name"}}
]
}
}
Then if I do this in console:
params = ActionController::Parameters.new(task: {"parent_id"=>1, "author": "Name name", "tasks"=>[{"name"=>"task 1"}, {"name"=>"task 2", "task"=>{"job_id"=>1, "author"=>"Name name"}}]})
task_params = params.require(:task).permit(:parent_id, :author, tasks: [:name, task: [:job_id, :author]])
task_params[:tasks].each do |task|
puts task.merge!(parent_id: task_params[:parent_id], author: task_params[:author])
end
Then I see from the puts statements:
{"name"=>"task 1", "parent_id"=>1, "author"=>"Name name"}
{"name"=>"task 2", "task"=>{"job_id"=>1, "author"=>"Name name"}, "parent_id"=>1, "author"=>"Name name"}
Which would be your arguments for Task.new, which you could do as:
task_params[:tasks].each do |task|
new_task = Task.new(task.merge!(parent_id: task_params[:parent_id], author: task_params[:author]))
end

Rails 5 - include nested associations not working

I have two simple models:
note.rb
with
:title -> string, :content -> string
has_and_belongs_to_many :tags, join_table: :tags_notes
accepts_nested_attributes_for :tags
tag.rb
with
:name -> string
has_and_belongs_to_many :notes, join_table: :tags_notes
Both models are connected through has_and_belongs_to_many relationship.
The association table is called tags_notes as indicated above.
Well, the problem I have here is, in my RESTful controller, to get notes, I have this:
GET /api/notes
This only returns Note objects:
[
{
"id": 1,
"title": "12231",
"content": "121213"
},
{
"id": 2,
"title": "test",
"content": "testtest"
}
]
However, each note has tags, and I would like to dump those in the response as well, like this:
[
{
"id": 1,
"title": "12231",
"content": "121213",
tags: [
{
"name": "test",
"id": 1
},
{
...
}
]
},
...
]
In my controller, I've tried
Note.includes(:tags).
Current controller code:
def index
notes = Note.includes(:tags)
render json: notes, status: :ok
end
They only seem to return notes, without tags. Same is the case with Note.eager_load(:tags) What am I doing wrong? Cannot find enough documentation that will help me fix this issue.
If someone can help me with this I will be grateful.
Thanks a bunch.
Shortly after posting my question I found the answer myself. The include has to go in render.
So the controller code
def index
notes = Note.all
render json: notes, :include => :tags, status: :ok
end
Seems to do the trick!

Rails API Design: best way to include other attributes with json_api relationships

I have a Rails 5 app in which I use the gem active_model_serializers(https://github.com/rails-api/active_model_serializers). In my app I have a simplified data model that looks something like this:
# LocalizedString.rb
has_many :translations
# Translation.rb
belongs_to :localized_string
I'm trying to follow the best practices from JSON API, I have configured active_model_serializers like this:
ActiveModelSerializers.config.adapter = :json_api
When a user of the API requests translations (http://[root]/api/apps/117/translations) I currently get the following result:
{
"data": [
{
"id": "152",
"type": "translations",
"attributes": {
"value": "Test",
},
"relationships": {
"language": {
"data": {
"id": "1",
"type": "languages"
}
},
"localized-string": {
"data": {
"id": "162",
"type": "localized-strings"
}
}
}
},
[...]
From my localised-string I also want to include another attribute that is critical for the consumer of the API, and I don't want to have to make another API call to get the value of the attribute. I wonder what is the best / recommended way to do this that also follows json_api if possible.
Something like this could work:
"localized-string": {
"data": {
"id": "162",
"key": "my key value", # the attribute I need.
"type": "localized-strings"
}
}
But I'm not sure how to achieve that using active_model_serializers or if it is another recommended way of doing what I want with [json_api][1].
For completion, my relevant serialiser files looks lik this:
class TranslationSerializer < ActiveModel::Serializer
attributes :id, :value, :created_at, :updated_at
has_one :language
has_one :localized_string, serializer: LocalizedStringParentSerializer
end
class LocalizedStringParentSerializer < ActiveModel::Serializer
# I want to include the key attribute in my relationship object, but this doesn't work.
attributes :id, :key
end
So, any ideas on what I need to do to achieve what I want?
Per spec, relationships are represented by resource object identifiers. To include more than just the id and type, you'll want to use the include param. In AMS, I think that would be 'include: [:localizations], fields: { localizations: [:key]}' (not at computer now, but is approx right)

Multikey indexing in rails mongoid

I want to store data in this format.
{
"_id": ObjectId(...)
"title": "Grocery Quality"
"comments": [
{ author_id: ObjectId(...)
date: Date(...)
text: "Please expand the cheddar selection." },
{ author_id: ObjectId(...)
date: Date(...)
text: "Please expand the mustard selection." },
{ author_id: ObjectId(...)
date: Date(...)
text: "Please expand the olive selection." }
]
}
I'm confused as to how to achieve this format for my data.
I am using mongoid; does Mongoid support Multikey Indexing?
How can use mongoid to achieve my desired format and behaviour?
I'm not sure if I got your doubt correctly but as I can't comment I'm answering right away. If it isnt this what you asked, please explain a little bit more =)
You have your model with those fields you wrote before, I will call it Post model. For the comments on it, I would suggest you create another model callend Comment and embed it on the Post model:
class Post
field: title
embeds_many :comments
end
class Comment
field :date
field :text
has_one :author
embedded_in :post
end
And to index the comments on the Post model you could do:
index({ :"comments.updated_at" => 1 })

Rails 3: Wrapping as_json response with additional lines

I was very happy to learn of as_json to make my code DRY. And I've added the following to a model:
class ProductType < ActiveRecord::Base
has_many :component_types
def as_json(parameter)
{:name => self.name,
:description => self.description,
:children => self.componentTypes}
end
end
This is great. The only thing is that for my client side application, I need to wrap the response I get into this format, (where "items" contains what is created by as_json):
{
"identifier": "name",
"label": "name",
"items":
[
{
"name": "myName1",
"description": "myDesc1",
"children":[]
},
{
"name": "myName2",
"description": "myDesc2",
"children":[]
}
]
}
There are a lot of limitations to overriding as_json, and your issue is one of them. I'd suggest having a look at the RABL gem as I think it will help you reach your goal.

Resources