How do I add an addPolylines feature to gmaps4rails? - ruby-on-rails

I'm working with the gmaps4rails gem (version 1.5.6), and I want to add the ability to add more Polylines to a map rather than replacing the Polylines that are already there. On github, this code is viewable here.
This feature already exists for Markers:
Gmaps.map.replaceMarkers(your_markers_json_array); Gmaps.map.addMarkers(your_markers_json_array);.
The Markers feature seems to be laid out in two places:
1) In gmaps4rails.base.js as:
Gmaps4Rails.prototype.addMarkers = function(new_markers) {
this.markers = this.markers.concat(new_markers);
this.create_markers();
return this.adjustMapToBounds();
};
2) In gmaps4rails.base.js.coffee as:
#add new markers to on an existing map
addMarkers : (new_markers, adjustBounds = true) ->
#update the list of markers to take into account
#markers = #markers.concat(new_markers)
#put markers on the map
#create_markers()
#adjustMapToBounds() if adjustBounds
I figured I could work with the replacePolylines code in to make my own addPolylines call:
1) In gmaps4rails.base.js near the replacePolylines code as:
Gmaps4Rails.prototype.addPolylines = function(new_polylines) {
this.polylines = this.polylines.concat(new_polylines);
this.create_polylines();
return this.adjustMapToBounds();
};
2) In gmaps4rails.base.js.coffee near the replacePolylines code as:
#add new polylines to on an existing map
addPolylines : (new_polylines) ->
#update the list of polylines to take into account
#polylines = #polylines.concat(new_polylines)
#put polylines on the map
#create_polylines()
#.... and adjust map boundaries
#adjustMapToBounds()
I've made these changes to the gem that's already added to my Rails project, and I've restarted my Rails server. I call it the same way I call replacePolylines, with Gmaps.map.addPolylines(your_polylines_json_array);. It produces an error in the console: Uncaught TypeError: Object #<Gmaps4RailsGoogle> has no method 'addPolylines'.
There doesn't seem to be anywhere else in the gmaps4rails project where I have to do anything for my addPolylines call, but I'm obviously not doing something right. Can anyone explain what I need to do to get this to work, based on this information?

As a workaround, until I can figure out how to build the functionality into gmaps4rails itself, I've taken these measures to asynchronously load Polylines onto a map...
In my UsersController, I've set up the show action to build a blank #polylines_json set:
def show
#user = User.find(params[:id])
#polylines_json = {}
end
The idea here is to have nothing for gmaps4rails to display on its initial map load, so that the initial page display is as quick as possible. In the view, I'm loading the map with this line:
<%= gmaps( :polylines => { :data => #polylines_json }, :map_options => { :type => 'HYBRID', :zoom => 12, :auto_adjust => false, :center_latitude => "-77", :center_longitude => "21" } ) %>
Back in the UsersController, I've got a custom action maps that is set up to handle json requests that contain from and to parameters (so that I can find items within specific ranges). It's in this action that I'm building the actual Polyline json data based on the from/to parameters:
def items
items = items.find(:all, :conditions => ['id >= ? AND id <= ?', params[:from], params[:to]])
polyline = []
i=0
items.each do |item|
polyline[i] = []
polyline[i] += [{:lng=>item.longitude.to_f,:lat=>item.latitude.to_f}]
i += 1
end
#polylines_json = polyline.to_json
respond_to do |format|
format.json { render json: #polylines_json }
end
end
Finally, back in the view to bring it all together, I'm asynchronously building up the Polyline collection ten at a time until I retrieve them all from the database:
<% content_for :scripts do %>
<script type="text/javascript" charset="utf-8">
numberOfItems = <%= #user.items.count %>;
var polylines= [];
function LoadMap(from, to) {
if (to < numberOfItems){
nextFrom = to + 1;
nextTo = to + 10;
}
if(nextTo <= numberOfItems){
$.ajax({
dataType: 'json',
url: "http://<%= request.host %>/users/<%= #user.id %>/maps.json?from="+from+"&to="+to,
success: function(response) {
polylines = polylines.concat(response);
Gmaps.map.replacePolylines(polylines);
LoadMap(nextFrom, nextTo);
}
});
}
}
LoadMap(1,10);
</script>
<% end %>

Related

How to use geoNear for List of Articles, when Addresses are in separate table?

I need a RoR mongoDB query to list articles within a given radius, sorted by created_at.
The challenge is that addresses are saved in separate table and referenced by key/id out of articles. Don't know how to make query with geoNear for this scenario.
Also pagination needed and performant query desirable.
Currently approaching like:
Get addresses within defined radius
Get articles associated to address results out of 1.
sort_by address (geoNear default)
Pagination is making usage of last_address_id. Also here have an issue, as last page is in loop.
#seaches_controller.rb
def index
#addresses =
Address.get_addresses_with_radius(article_search_params).to_a
#address_hash = #addresses.group_by{|a| a['_id'].to_s}
#articles = Article.includes(:gift, :category)
.where( transaction_status:
{
'$nin' => ["concluded"]
},
address_id:
{
:$in => #addresses.map{|a| a['_id'].to_s}
}
).to_a
.sort_by{|m|
#addresses.map{|a|
a['_id']}.index(m['address_id']) }
end
#address.rb
def self.get_addresses_with_radius(params, additional_query={})
#raw query for aggreegate with geoNear
last_maximum_distance = params[:last_maximum_distance] || 0 # in
meeters
radius = params[:radius] || 5000000 #In Meters
query_params = additional_query
if params[:last_address_id]
query_params[:_id] ||= {}
query_params[:_id] = query_params[:_id].merge({ '$ne' =>
(BSON::ObjectId(params[:last_address_id])) } )
end
addresses_in_radius =
Address.collection.aggregate([
{
'$geoNear':
{
near:
{
type: "Point",
coordinates: [ params[:lat].to_f, params[:lon].to_f ]
},
distanceField: "distance_from", #GeoNear Will atomatically distance as distance_from_field
minDistance: last_maximum_distance.to_f,
maxDistance: radius,
query: query_params,
#query:{ 'location.0': {'$ne' =>
params[:last_lat].to_f},'location.1': {'$ne' => params[:last_lon].to_f}},
spherical: true
}
},
{"$limit": params[:per_page].to_i}
])
addresses_in_radius
end
Currently I'm getting the list of articles sorted by addresses/distance, as per default geoNear behavior => should be by created_at.
Pagination is somehow based on addresses => should ideally be based on articles.
Pagination is buggy, as last page is loading in loop => loop-bug to go away.
Not sure if best is to first search for articles and then addresses, or first addresses and then get the articles; relevant note: all within defined radius.

I try to fetch random single database from rails by ajax?

I am using ajax with rails to get single random row I want to display just 10 rows with every request by ajax get one single random and display it, i try something like that, Model.all.sample or Using offset with first but my problem is duplication how can avoid it or how can i set all response to check if I sent it before or not Note: I send all elements was appended as array for backend to check if i send it before and change it but i have wrong
result
my code is :- in backend is my function
def get_10
arr = params['arr']
if arr.nil?
#rand_record = Phrase.all.sample
else
i = 0
if i < 10
#rand_record = Phrase.all.sample
while(i < arr.length) && (i<10)
flag = arr[i].include?#rand_record.name
if flag
#rand_record = Phrase.all.sample
i = 0
elsif flag == false
return #rand_record
end
i+=1
end
end
end
respond_to do |format|
format.js { }
end
end
in my js ajax is :
function myFunction(){
var arr = []
var len = $('li').length
for (let i=0 ; i< len; i++){
var attr = $('li')[i].childNodes['0'].data
arr.push(attr)
}
$.ajax({
method: 'GET',
url: '/phrase',
dataType: 'script',
data: {arr: arr}
}).done(function(data){
console.log(data);
})
}
in template is:
<div class="form-group">
<%= button_tag 'GetPhrases', type: 'button', onclick:"myFunction()", class: 'btn btn-default' , id:"get" %>
</div>
my result is
this pharase number 1
this pharase number 5
this pharase number 4
this pharase number 5
this pharase number 8
enter image description here
I want to avoid duplication I want to retrive just 10 random single row without duplication
Be careful with sample on the active record relation, instead do:
Model.where(id: Model.all.pluck(:id).sample(10))
This will just pluck all the IDs and sample 10 of them and then select records with those random 10 ids.

Is there a way I can use a rake task or method that can rewrite an HTML partial using erb?

Sorry for the confusing title, but I will elaborate here.
ok so on the users index page of my site I have a list of Top Trending songs. The list is ordered by user rankings and this list changes dynamically as each songs aggregate ranking changes relative to each other.
class SongratingsController < ApplicationController
#Top100 = Rails.cache.read('Top100')
lastSpot = #Top100.last
def reCalcTop100
#Top100 = Song.where('num_stars > ?', 0 ).order('num_stars desc, total_score desc').limit(100)
Rails.cache.fetch('Top100'){#Top100}
end
def addRatingToSong
userID = params[:uid].to_i
songId = params[:sid].to_i
rVal = params[:valR].to_i
#averageS = []
songRate = Songrating.find_by(:user_id => userID, :song_id => songId)
if songRate != nil
oldScore = songRate.rating
songRate.update_attributes(:rating => rVal)
#song = Song.find(songId)
score = #song.total_score - oldScore
newScore = score + rVal
averageScore = newScore/#song.songratings_count
#song.update_attributes(:total_score => newScore,:num_stars => averageScore)
#averageS[0] = averageScore
#averageS[1] = #song.songratings_count
else
Songrating.create!(:user_id => userID, :song_id => songId,:rating => rVal)
#song = Song.find(songId)
newScore = #song.total_score + rVal
averageScore = newScore/#song.songratings_count
#song.update_attributes(:total_score => newScore,:num_stars => averageScore)
#averageS[0] = averageScore
#averageS[1] = #song.songratings_count
end
if newScore > lastSpot.total_score && averageScore > lastSpot.num_stars
reCalcTop100
end
if request.xhr?
render :json => {
:starData => #averageS
}
end
end
end
As you can see in these two photos below I have a view partial that shows this list, but right now I have the list generated each time a user logs into the main page. But since this list is not unique to the user, I feel I am wasting time regenerating this list.
ideally I would like to generate and write a static HTML partial only when the top100 list changes, but I don't really know how to accomplish this.
thanks.
Yep just use erb
vars = OpenStruct.new({some_var: some_val})
rendered_html = ERB.new(File.read("#{Rails.root}/app/templates/embed_code.html.erb")).result(vars.instance_eval { binding })
This will put the rendered html in the rendered_html variable from there you can write it to a file or do anything you want. This should work in the context of a ruby class or rake task afaik.
The vars are passed to the template and can be used as <%= some_var %> in the template.
Now that i've answered you actual question, i think the better solution is to just use Rails.cache to cache the rendered data.
Anything that takes a long time can be cached with
result = Rails.cache.fetch "some_cache_key" do
# things you want to cache
end
this will cache the block and return it to result. if unstale cached data exisits in the future it will just return it from cache, if cache is empty or stale it will re-execute the block and return it into result.
Finally in the context fo a controller you can just use action caching which is a bit more hands off.
See: http://guides.rubyonrails.org/caching_with_rails.html for more details.

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