Send 2 json from server to angularjs controller by 1 request - ruby-on-rails

In a rails project, I have 1 controller and 2 model. I want send 1 request from angularjs to rails server and for response, get 2 json array, 1. first model. 2. seccond model.
Now I use below code, and get just 1 of 2 array:
Rails Contorller: tables_controller.rb:
class Api::V1::TablesController < Api::V1::BaseController
def index
#table = Table.all
#mostagheltype = Mostagheltype.all
//I can just send 1 of model.
respond_with(Table.all)
end
end
Angularjs Controller: table.js:
$scope.tables = Tables.index();
tableService.js:
'use strict';
var app = angular.module('tableService', ['ngResource']);
app.factory('Tables', function($resource) {
return $resource('/api/tables.json', {}, {
index: { method: 'GET', isArray: true}
});
});
I can push 2 table in 1 array in rails controller and then recieve this from angular controller, like below:
array = []
Table.all.each do |table|
array << { name: table.name, check: 1 }
end
Mostagheltype.all.each do |m|
array << { name: mostagheltype.name, check: 2}
end
//I can seprate 2 array by `check` value in angularjs part.
but I want a solution that I send each array separate. How can I do this? Any idea?

Short answer no. You can't have two responses to one request. You can respond with an object that contains both arrays.
My is a little rusty. So this may be more psuedo code than ruby;
obj = {}
obj.array1 =[]
obj.array2 =[]
Then populate each array and return the object.

Related

Mongoid Aggregate result into an instance of a rails model

Introduction
Correcting a legacy code, there is an index of object LandingPage where most columns are supposed to be sortable, but aren't. This was mostly corrected, but few columns keep posing me trouble.
Theses columns are the one needing an aggregation, because based on a count of other documents. To simplify the explanation of the problem, I will speak only about one of them which is called Visit, as the rest of the code will just be duplication.
The code fetch sorted and paginate data, then modify each object using LandingPage methods before sending the json back. It was already like this and I can't modify it.
Because of that, I need to do an aggregation (to sort LandingPage by Visit counts), then get the object as LandingPage instance to let the legacy code work on them.
The problem is the incapacity to transform Mongoid::Document to a LandingPage instance
Here is the error I got:
Mongoid::Errors::UnknownAttribute:
Message:
unknown_attribute : message
Summary:
unknown_attribute : summary
Resolution:
unknown_attribute : resolution
Here is my code:
def controller_function
landing_pages = fetch_landing_page
landing_page_hash[:data] = landing_pages.map do |landing_page|
landing_page.do_something
# Do other things
end
render json: landing_page_hash
end
def fetch_landing_page
criteria = LandingPage.where(archived: false)
columns_name = params[:columns_name]
column_direction = params[:column_direction]
case order_column_name
when 'visit'
order_by_visits(criteria, column_direction)
else
criteria.order_by(columns_name => column_direction).paginate(
per_page: params[:length],
page: (params[:start].to_i / params[:length].to_i) + 1
)
end
def order_by_visit(criteria, order_direction)
def order_by_visits(landing_pages, column_direction)
LandingPage.collection.aggregate([
{ '$match': landing_pages.selector },
{ '$lookup': {
from: 'visits',
localField: '_id',
foreignField: 'landing_page_id',
as: 'visits'
}},
{ '$addFields': { 'visits_count': { '$size': '$visits' }}},
{ '$sort': { 'visits_count': column_direction == 'asc' ? 1 : -1 }},
{ '$unset': ['visits', 'visits_count'] },
{ '$skip': params[:start].to_i },
{ '$limit': params[:length].to_i }
]).map { |attrs| LandingPage.new(attrs) { |o| o.new_record = false } }
end
end
What I have tried
Copy and past the hash in console to LandingPage.new(attributes), and the instance was created and valid.
Change the attributes key from string to symbole, and it still didn't work.
Using is_a?(hash) on any element of the returned array returns true.
Put it to json and then back to a hash. Still got a Mongoid::Document.
How can I make the return of the Aggregate be a valid instance of LandingPage ?
Aggregation pipeline is implemented by the Ruby MongoDB driver, not by Mongoid, and as such does not return Mongoid model instances.
An example of how one might obtain Mongoid model instances is given in documentation.

How to calculate specific rating count hash in ruby on rails?

So, I have an after_save hook on review model which calls calculate_specific_rating function of product model. The function goes like this:
def calculate_specific_rating
ratings = reviews.reload.all.pluck(:rating)
specific_rating = Hash.new(0)
ratings.each { |rating| specific_rating[rating] += 1 }
self.specific_rating = specific_rating
save
end
Right now, it returns
specific_rating => {
"2"=> 3, "4"=> 1
}
I want it to return like:
specific_rating => {
"1"=> 0, "2"=>3, "3"=>0, "4"=>1, "5"=>0
}
Also, is it okay to initialize a new hash everytime a review is saved? I want some alternative. Thanks
You can create a range from 1 until the maximum value in ratings plus 1 and start iterating through it, yielding an array where the first element is the current one, and the second element is the total of times the current element is present in ratings. After everything the result is converted to a hash:
self.specific_rating = (1..ratings.max + 1).to_h { |e| [e.to_s, ratings.count(e)] }
save
You could also do something like this -
def calculate_specific_rating
ratings = [1,2,3,4,5]
existing_ratings = reviews.group_by(&:rating).map{|k,v| [k, v.count]}.to_h
Hash[(ratings - existing_ratings.keys).map {|x| [x, 0]}].merge(existing_ratings)
end
which gives
{3=>0, 4=>0, 5=>0, 2=>3, 1=>1}

Rails app: I need to build a json object from params inside a loop

I need to build a json object inside a loop using params.
My params look like this...
params[:answers]
returns => {"1"=>"answer1", "2"=>"answer2"}
The keys in this json object are the id's of the survey question.
So I planed to loop through the keys to build the json object like this...
def build_answersheet_json(params[:answers], params[:survey_id])
params[:answers].keys.each do |question_id|
current_question = question_id
current_answer = params[:answers][question_id]
end
end
Since im using "t.json" in my migration to save json to postgres, I wanted to use the extracted question_id and answer to build a json object that looks something like this...
{
survey_id: '1',
answers: {
question: [{
question_id: 1,
answer: 'answer1'
}, {
question_id: 2,
answer: 'answer2'
}]
}
}
Ive been trying to do this using a method that looks somthing like this...
build_answersheet_json(params[:answers], params[:survey_id])
Ive tried JSON.parse() and Ive tried to just logically work through it but I cant seem to figure this out.
Any help is appreciated.
Maybe you can try something like that:
/* fake params (to test) */
params = {
survey_id: '1',
answers: {
"1"=>"answer1",
"2"=>"answer2",
"3"=>"answer3",
"4"=>"answer4"
}
}
def build_answersheet_json(answers, survey_id)
{
survey_id: survey_id,
answers: answers.map { |k,v| { question_id: k.to_i, answer: v } }
}
end
survey = build_answersheet_json(params[:answers], params[:survey_id])
puts survey.class
#Hash
puts survey.to_json
# formated JSON string:
# {
# "survey_id":"1",
# "answers":[
# {"question_id":1,"answer":"answer1"},
# {"question_id":2,"answer":"answer2"},
# {"question_id":3,"answer":"answer3"},
# {"question_id":4,"answer":"answer4"}
# ]
# }
In order to save to a t.json postgress column type, just pass the Hash survey object, like that:
YourModel.create(survey: survey)
Source: http://edgeguides.rubyonrails.org/active_record_postgresql.html
Try
{
survey: ¯\_༼◉ل͟◉༽_/¯,
}
Json may not be parsed if json have construction like this:
survey = {
}
Json may not contain = and assignment
Check real variables values with puts varname.inspect near at code lines where you meet unexpected behaviour.

Rails Netzke v0.10.1 - Refreshing grid panel within a tabpanel

I try to make a Netzke component with one master grid and subgrids in the south region of a Panel.
When a row in the maingrid is selected then should the subgrids be filtered with records related to the record in maingrid - like described here for an old netzke version:
https://groups.google.com/forum/#!searchin/netzke/tabpanel/netzke/PFAQ-wYyNog/2RJgRLzh80oJ
I know that netzke is not further in development but I use it in a project.
ruby 2.1.2 (Mac OSX rbenv)
rails 4.0.10
netzke-core v0.10.1
netzke-basepack v0.10.1
Here my Code:
models:
class MbOrganisation < ActiveRecord::Base
has_many :mb_contacts
def customer_name
"#{orga_customer} - #{orga_name1}"
end
end
class MbContact < ActiveRecord::Base
belongs_to :mb_organisation
end
This is the central component
app/components/organisation_multitab.rb
class OrganisationMultitab < Netzke::Base
component :organisation_organisations
component :organisation_tabpanel do |c|
c.klass = MblixBaseTabpanel
c.items = [:organisation_contacts]
end
js_configure do |c|
c.layout = :border
c.border = false
c.init_component = <<-JS
function(){
// calling superclass's initComponent
this.callParent();
// setting the 'rowclick' event
var view = this.netzkeGetComponent('organisation_organisations').getView();
view.on('itemclick', function(view, record){
// The beauty of using Ext.Direct: calling 3 endpoints in a row, which results in a single call to the server!
this.selectItem({item_id: record.get('id')});
}, this);
}
JS
end
def configure(c)
super
c.items = [
{ items: [:organisation_organisations], region: :center },
{ items: [:organisation_tabpanel], region: :south, height: 200, split: true }
]
end
endpoint :select_item do |params, this|
# store selected id in the session for this component's instance
component_session[:selected_item_id] = params[:item_id]
end
end
These components are additionally used
Maingrid - organisation_organisations.rb
class OrganisationOrganisations < Netzke::Basepack::Grid
def configure(c)
super
c.model = "MbOrganisation"
c.columns = [:orga_customer, :orga_name1, :orga_name2, :orga_street, :orga_zip, :orga_city, :orga_tel, :orga_email]
c.force_fit = true
end
end
Component with Tabpanel- base_tabpanel.rb:
class BaseTabpanel < Netzke::Basepack::TabPanel
component :organisation_contacts do |c|
c.data_store = {auto_load: false}
c.scope = {:mb_organisation_id => component_session[:selected_item_id]}
c.strong_default_attrs = {:mb_organisation_id => component_session[:selected_item_id]}
end
def configure(c)
super
c.active_tab = 0
c.prevent_header = true
end
end
The grid component for the contacts:
class OrganisationContacts < Netzke::Basepack::Grid
def configure(c)
super
c.model = "MbContact"
c.columns = [{ :name => :mb_organisation__customer_name,
:header => "Organisation"
}, :cont_salutation, :cont_title, :cont_lastname, :cont_firstname, :cont_email, :cont_tel, :cont_mobile, :cont_birthday]
c.force_fit = true
end
end
The function this.selectItem(...) is correct triggered and calls the endpoint in OrganisationMultitab.
I have two problems/questions
First
- How can I automatically reload the stores of the subgrids in the tabpanel?
The described way in the linked google groups article: https://groups.google.com/forum/#!searchin/netzke/tabpanel/netzke/PFAQ-wYyNog/2RJgRLzh80oJ is outdated (It's for netzke v0.5 - I use netzke v0.10.1):
{
:south => {
:item0 => {:load_store_data => aggregatee_instance(:south__item0).get_data},
:item1 => {:load_store_data => aggregatee_instance(:south__item1).get_data}
}
}
second problem: I got an error - when I manually refresh the subgrids:
ActiveModel::ForbiddenAttributesError in NetzkeController#direct
Update
The ActiveModel::ForbiddenAttributesError is solved by myself. There was a bug in the netzke-basepack gem:
Netzke::Basepack::Grid ran in an ActiveModel::ForbiddenAttributesError (rails 4 strong parameters) when the component, like above described, has a scope configured. (config[:scope] will later be merged to the params object that is an ActionController::Parameters object. - As the scope is database related this will be denied with ActiveModel::ForbiddenAttributesError )
My solution: In the endpoint.rb the ActionController::Parameters will be converted to a Hash - then the error is gone.
I made a fork and a pull request in github for this gem.
But
the second problem is not solved.
second problem: Now the subgrids can be manually refreshed without an error but they are always empty.
I guess the scope in the child component
component :organisation_contacts do |c|
c.data_store = {auto_load: false}
c.scope = {:mb_organisation_id => component_session[:selected_item_id]}
c. strong_default_attrs = {:mb_organisation_id => component_session[:selected_item_id]}
end
has no access to the value of the
component_session[:selected_item_id]
of the Organisation MultiTab parent component?
But it is neccessary to split the components - like described here: https://groups.google.com/forum/#!searchin/netzke/tabpanel/netzke/sDrU7NZIlqg/-2wGmed7fjcJ
Hope there is somebody who can help me. :-)
Thanks
Best regards
You're getting the ActiveModel::ForbiddenAttributesError because you're not permiting the attributes from the controller. Rails now uses strong_parameters instead of attr_accessible (like in Rails 3).
So I found the solution by my self.
First issue - reloading the grids in the Tabs
The store of the Ext gridcomponent can also be accessed in the Javascript.
So I extended the Javascript configuration of the OrganisationMulitab with this part:
Ext.each(this.netzkeGetComponent('organisation_tabpanel').items.items, function(item, index) {
item.getStore().load();
});
Second issue - send the selected id to the scope in the child component
The value must be sent to the session of the child component - so this does the job:
component_instance(:organisation_tabpanel).component_session[:selected_item_id] = params[:item_id]
instead of
component_session[:selected_item_id] = params[:item_id]
(The problem with the ActiveModel::ForbiddenAttributesError was a bug in the gem - solution is in my update of the question - I made a fork of the gem https://github.com/tomg65/netzke-basepack/tree/master-fixes-changes and sent a pull request to the original https://github.com/netzke/netzke-basepack/pull/158)
So the final code looks like this and all works fine:
class OrganisationMultitab < Netzke::Base
component :organisation_organisations
component :organisation_tabpanel do |c|
c.klass = MblixBaseTabpanel
c.items = [:organisation_contacts]
end
js_configure do |c|
c.layout = :border
c.border = false
c.init_component = <<-JS
function(){
// calling superclass's initComponent
this.callParent();
// setting the 'rowclick' event
var view = this.netzkeGetComponent('organisation_organisations').getView();
view.on('itemclick', function(view, record){
// The beauty of using Ext.Direct: calling 3 endpoints in a row, which results in a single call to the server!
this.selectItem({item_id: record.get('id')});
Ext.each(this.netzkeGetComponent('organisation_tabpanel').items.items, function(item, index) {
item.getStore().load();
});
}, this);
}
JS
end
def configure(c)
super
c.items = [
{ items: [:organisation_organisations], region: :center },
{ items: [:organisation_tabpanel], region: :south, height: 200, split: true }
]
end
endpoint :select_item do |params, this|
# store selected id in the session for child component's instance
component_instance(:organisation_tabpanel).component_session[:selected_item_id] = params[:item_id]
end
end
Hope this helps others too.
Best regards
Thomas

without template, need to render not return json

I'm trying to make code from a Sinatra app work in the Rails context. The Sinatra app uses ajax requests to trigger the Sinatra routes/controller actions. For example, if you trigger the new function on a javascript model
new: function() {
var _this = this;
$.ajax({
url: "/gamestart",
type: "POST",
....
It will trigger the route/controller code in the Sinatra app
post "/new" do
end
When I tried to make this work in Rails, I'm getting a 500 internal server error. In my Rails app, the new_game button triggers an ajax request to a Rails route which triggers a controller action, and that controller action uses the Rails model to get data from the database. For some reason that doesn't seem like the right way to do it in Rails, and I'm wondering if it's the reason I'm getting the server error
GET http://localhost:3000/gamestart 500 (Internal Server Error)
If possible, can you tell me where in the chain of actions outlined below that error is arising and what I might do to fix it.
1 Click on the new game button triggers 'startNewGame' method
'click #new_game': 'startNewGame',
2 The startNewGame method calls method on Game model
startNewGame: function() {
this.model.new();
},
3 The new method in the Game model makes a GET request to the url '/gamestart'. I also tried a post request. I don't know why it would need to be a post request, but neither worked. (In the original Sinatra application, the gamestart url led immediately into the function post '/gamestart' do...)
new: function() {
var _this = this;
$.ajax({
url: "/gamestart",
type: "GET", \\\ also tried POST
success: function(response) {
var json = $.parseJSON(response);
_this.set({lost: false});
_this.set({win: false});
_this.trigger("gameStartedEvent", json);
}
})
},
4 I directed the url to a controller action in Rails router file
match 'gamestart' => 'locations#gamestart', :via => :get
Note, in the original Sinatra application, the route and the controller action were combined
5 The gamestart method of the locations_controller.rb
def gamestart
word = Word.get_random
masquerade_word = Word.masquerade(word)
session[:word] = word
session[:incorrect_guesses] = 0
session[:chars_left] = word.size
session[:revealed_word] = masquerade_word
{:word => masquerade_word}.to_json
end
6 The get_random method on the word model Word.rb, which is called from locations controller
def get_random
words = []
locations = Location.all (this pulls up the names of the locations from the db)
locations.each do |e|
words << e.name
end
words.sample
end
ERROR MESSAGE
GET http://localhost:3000/gamestart 500 (Internal Server Error) jquery.js:8215
XHR finished loading: "http://localhost:3000/gamestart". jquery.js:8215
send jquery.js:8215
jQuery.extend.ajax jquery.js:7767
window.Game.Backbone.Model.extend game.js:27
window.OptionsView.Backbone.View.extend.startNewGame optionsView.js:14
jQuery.event.dispatch jquery.js:3062
elemData.handle.eventHandle
Note, in the original Sinatra application, the route and the controller action were combined in the usual Sinatra way
post "/gamestart" do
word = Word.get_random
masquerade_word = Word.masquerade(word)
session[:word] = word
session[:incorrect_guesses] = 0
session[:chars_left] = word.size
session[:revealed_word] = masquerade_word
{:word => masquerade_word}.to_json
end
UPDATE
The 500 error seemed to be triggered by a missing template. This method in locations controller wasn't rendering anything. It didn't have a view file. I therefore changed the controller to make it respond_to :json and then use respond_with at the end of the action, but that triggered a 406 error.
def gamestart
word = Word.get_random
masquerade_word = Word.masquerade(word)
session[:word] = word
session[:incorrect_guesses] = 0
session[:chars_left] = word.size
session[:revealed_word] = masquerade_word
{:word => masquerade_word}.to_json
end
became now triggers 406 error
respond_to :json
def gamestart
word = Word.get_random
masquerade_word = Word.masquerade(word)
session[:word] = word
session[:incorrect_guesses] = 0
session[:chars_left] = word.size
session[:revealed_word] = masquerade_word
plainvariable = {:word => masquerade_word}.to_json ###changed
respond_with plainvariable ###changed
end
You say that your gamestart controller method is causing a server error due to a missing template. If we look at that controller method:
def gamestart
#...
{:word => masquerade_word}.to_json
end
we see that it returns a JSON string but it neglects to render anything. You don't call any rendering or redirection methods so Rails helpfully (ha ha) assumes that you want to render the gamestart view template; but, you have no such thing so you get an error.
You should render your JSON, not return it; something more like this:
def gamestart
#...
render :json => { :word => masquerade_word }
end

Resources