The gem manual indicates Venue.near([40.71, -100.23], 20, :units => :km)
However this application's model objects have two geocoded co-ordinates variables in addition to the individual longitudes and latitudes. In order to test the various search options (postGIS, numerical indexes of lon and lat, or calling geocoder services) is there any way to access a specific model variable in the finding of objects near a given location?
The relevant schema data is:
t.decimal "origin_lon", precision: 15, scale: 10
t.decimal "origin_lat", precision: 15, scale: 10
t.spatial "origin_lonlat", limit: {:srid=>3857, :type=>"point"}
add_index "strappos", ["origin_lat"], :name => "index_strappos_on_origin_lat"
add_index "strappos", ["origin_lon"], :name => "index_strappos_on_origin_lon"
add_index "strappos", ["origin_lonlat"], :name => "index_strappos_on_origin_lonlat", :spatial => true
GeoCoder defaults to using latitude and longitude as their variable names. Since yours are different, you'll need to add an extra option in order to query origin_lon and origin_lat.
In your model, where you initialize geocoder for the model, you have the option of assigning a different name for the lat/lon attribute:
# YourModel.rb
geocoded_by :address,
latitude: :origin_lat,
longitude: :origin_lon
I placed it on multiple lines for clarity. As you can see, I'm telling geocoder that my lat/lon attribute names are different than the defaults.
Now, querying the model using near will look at those new attribute names as opposed to the defaults.
Related
Getting this error with sphinx 2
sphinxql: syntax error, unexpected IDENT, expecting CONST_INT or CONST_FLOAT or '-' near 'WI AND published = 1 AND sphinx_deleted = 0 LIMIT 0, 10; SHOW META'
index.html.erb
error is being thrown in the template at the line of a partial collection: #posts_by_state, but two other instances of the same partial are working great. The State sort is what is throwing it off.
posts_controller.rb
#posts_by_state = Post.search(params[:search], with: { state: current_user.state, published: true }, :page => params[:page], :per_page => 10)
post_index.rb
ThinkingSphinx::Index.define :post, :with => :active_record do
indexes :title, as: :post_title
indexes :desc, as: :description
indexes tags(:name), as: :tag_name
#indexes happening_on, sortable: true
#has author_id, published_at
has published_at
has last_touched
has state
has published
set_property:field_weights => {
:post_title => 5,
:description => 1,
:tag_name => 10
}
end
String attributes in Sphinx can only be used for sorting - not filtering, not grouping - and so your options to work around this are as follows:
Pull it out into an associated model (State or PostState, perhaps?), and then filter by the foreign key integer instead.
Store that value as a field instead, and use :conditions instead of :with.
Hack around it with CRC32 values.
I highly recommend the first of these options (I'd argue it's cleaner, accurate), but it's up to you.
I want to use FactoryGirl.attributes_for in controller testing, as in:
it "raise error creating a new PremiseGroup for this user" do
expect {
post :create, {:premise_group => FactoryGirl.attributes_for(:premise_group)}
}.to raise_error(CanCan::AccessDenied)
end
... but this doesn't work because #attributes_for omits the :user_id attribute. Here is the difference between #create and #attributes_for:
>> FactoryGirl.create(:premise_group)
=> #<PremiseGroup id: 3, name: "PremiseGroup_4", user_id: 6, is_visible: false, is_open: false)
>> FactoryGirl.attributes_for(:premise_group)
=> {:name=>"PremiseGroup_5", :is_visible=>false, :is_open=>false}
Note that the :user_id is absent from #attributes_for. Is this the expected behavior?
FWIW, my factories file includes definitions for :premise_group and for :user:
FactoryGirl.define do
...
factory :premise_group do
sequence(:name) {|n| "PremiseGroup_#{n}"}
user
is_visible false
is_open false
end
factory :user do
...
end
end
Short Answer:
By design, FactoryGirl's attribues_for intentionally omits things that would trigger a database transaction so tests will run fast. But you can can write a build_attributes method (below) to model all the attributes, if you're willing to take the time hit.
Original answer
Digging deep into the FactoryGirl documentation, e.g. this wiki page, you will find mentions that attributes_for ignores associations -- see update below. As a workaround, I've wrapped a helper method around FactoryGirl.build(...).attributes that strips id, created_at, and updated_at:
def build_attributes(*args)
FactoryGirl.build(*args).attributes.delete_if do |k, v|
["id", "created_at", "updated_at"].member?(k)
end
end
So now:
>> build_attributes(:premise_group)
=> {"name"=>"PremiseGroup_21", "user_id"=>29, "is_visible"=>false, "is_open"=>false}
... which is exactly what's expected.
update
Having absorbed the comments from the creators of FactoryGirl, I understand why attributes_for ignores associations: referencing an association generates a call to the db which can greatly slow down tests in some cases. But if you need associations, the build_attributes approach shown above should work.
I think this is a slight improvement over fearless_fool's answer, although it depends on your desired result.
Easiest to explain with an example. Say you have lat and long attributes in your model. On your form, you don't have lat and long fields, but rather lat degree, lat minute, lat second, etc. These later can converted to the decimal lat long form.
Say your factory is like so:
factory :something
lat_d 12
lat_m 32
..
long_d 23
long_m 23.2
end
fearless's build_attributes would return { lat: nil, long: nil}. While the build_attributes below will return { lat_d: 12, lat_m: 32..., lat: nil...}
def build_attributes
ba = FactoryGirl.build(*args).attributes.delete_if do |k, v|
["id", "created_at", "updated_at"].member?(k)
end
af = FactoryGirl.attributes_for(*args)
ba.symbolize_keys.merge(af)
end
To further elaborate on the given build_attributes solution, I modified it to only add the accessible associations:
def build_attributes(*args)
obj = FactoryGirl.build(*args)
associations = obj.class.reflect_on_all_associations(:belongs_to).map { |a| "#{a.name}_id" }
accessible = obj.class.accessible_attributes
accessible_associations = obj.attributes.delete_if do |k, v|
!associations.member?(k) or !accessible.include?(k)
end
FactoryGirl.attributes_for(*args).merge(accessible_associations.symbolize_keys)
end
Here is another way:
FactoryGirl.build(:car).attributes.except('id', 'created_at', 'updated_at').symbolize_keys
Limitations:
It does not generate attributes for HMT and HABTM associations (as these associations are stored in a join table, not an actual attribute).
Association strategy in the factory must be create, as in association :user, strategy: :create. This strategy can make your factory very slow if you don't use it wisely.
The accepted answer seems outdated as it did not work for me, after digging through the web & especially this Github issue, I present you:
A clean version for the most basic functionality for Rails 5+
This creates :belongs_to associations and adds their id (and type if :polymorphic) to the attributes. It also includes the code through FactoryBot::Syntax::Methods instead of an own module limited to controllers.
spec/support/factory_bot_macros.rb
module FactoryBot::Syntax::Methods
def nested_attributes_for(*args)
attributes = attributes_for(*args)
klass = args.first.to_s.camelize.constantize
klass.reflect_on_all_associations(:belongs_to).each do |r|
association = FactoryBot.create(r.class_name.underscore)
attributes["#{r.name}_id"] = association.id
attributes["#{r.name}_type"] = association.class.name if r.options[:polymorphic]
end
attributes
end
end
this is an adapted version of jamesst20 on the github issue - kudos to him 👏
I have a model that's backed by Mongodb and I'm trying to get Gmaps4Rails to be able to properly use a location indexed array field that's in my mongo document.
I'm failing to figure out how I should map this given that the latitude and longitude aren't stored as independent values in order to take advantage of the geoindexing on mongo:
class Site
include MongoMapper::Document
include Gmaps4rails::ActsAsGmappable
acts_as_gmappable :lat => ???,
:lon => ???,
:process_geocoding => false
key :name, String
key :location, Array
ensure_index [[:location, '2d']]
end
for now I'm just doing this:
class Site
include MongoMapper::Document
include Gmaps4rails::ActsAsGmappable
acts_as_gmappable :process_geocoding => false
key :name, String
key :location, Array
ensure_index [[:location, '2d']]
def lat
return latitude
end
def lon
return longitude
end
def latitude
return location[1]
end
def longitude
return location[0]
end
end
Another noob question that seems like it should be simple:
Thanks to the help received here, I can easily get a sum of selected transactions:
#trip_hash = transactions.sum(:amount_cents, :group => :trip_id)
The issue, however, is that the :amount_cents column represents a raw Money object that needs to be transformed before summing in order to accommodate currency exchange. The Money "composed of" Procs look like this:
composed_of :amount,
:class_name => "Money",
:mapping => [%w(amount_cents cents), %w(currency currency_as_string)],
:constructor => Proc.new { |cents, currency| Money.new(cents || 0, currency || Money.default_currency) },
:converter => Proc.new { |value| value.respond_to?(:to_money) ? value.to_money : raise(ArgumentError, "Can't convert #{value.class} to Money") }
I can easily call:
transactions.map(&:amount).inject(:+)
to get a transformed grand total, but I can't figure out how to do it in the context of the groupings.
Many thanks, again, for the help!
Took a lot of canoodling and reading, but finally figured out the following:
trip_hash = bankroll.transactions.group_by(&:trip_id).map {|tr,t| Hash[tr, t.map(&:amount).inject(:+)]}
=>[{0=>#<Money cents:137693 currency:USD>}, {7=>#<Money cents:-39509 currency:USD>}, {10=>#<Money cents:50009 currency:USD>}]
Map within the map did it! Hashifying makes it view friendly, and it retains the Money object for formatting purposes....
I'm trying to create a simple scope that sphinx will index (Ruby on Rails). The normal scope returns what it should, the sphinx scope returns no results.
define_index do
# fields
indexes :name
indexes author
indexes description
indexes list_of_tags
indexes approved
# attributes
has created_at, updated_at, downloads
# delta indexing
set_property :delta => true
# weighting fields
set_property :field_weights => {
:name => 10,
list_of_tags => 6,
author => 5,
description => 4,
}
end
normal scope:
scope :approved, where(:approved => true)
sphinx scope:
sphinx_scope(:approval_scope) {
{:conditions => {:approved => "true"}}
}
Approved is a boolean field, however, since I'm indexing it as a field, I believe its value is treated as a String. Regardless, letting the value of the sphinx scope be "true" or true makes no difference - Theme.approval_score still returns 0 results unlike Theme.approval. I hope I'm missing something simple..
make the approved with has
define_index do
# fields
...
has approved
...
end
then
sphinx_scope(:approval_scope) {
{:with => {:approved => true}}
}