has_many multiple associations with different names - ruby-on-rails-4

Ruby 2.1.0/Rails 4.0.2
I have a Bus model and a Cluster model. They look as follows:
class Bus < ActiveRecord::Base
has_many :left_centroids, class_name: "Centroid"
has_many :right_centroids, class_name: "Centroid"
end
class Centroid < ActiveRecord::Base
belongs_to :bus
end
Bus also has a method that is basically KMeans Algorithm, so I run it to replace the old left_centroids and then to replace the right_centroids. Right and Left differ in the value of a given field from the Centroid Model.
I have tried saving those via simple setting: #bus.left_centroids = centroids_for_algorithms and also through update_attributes. But whenever I save one, say left, right is overwritten with the values of left, and the other way around, which is pointless in the context of my application.
Am I missing something?
UPDATE: After I run the K Means Algorithm (from the ai4r gem, link in comments), I collect the centroids
def algorithm(direction)
clusters = k_means_algorithm_part
centroids_to_add_to_bus = Centroid.limit 0
clusters.centroids.each do |centroid|
cent = Centroid.create(
:latitude => centroid[0].to_d,
:longitude => centroid[1].to_d,
:catch_time => centroid[2],
:direction => direction,
:bus_id => bus_id
)
centroids_to_add_to_bus.append cent
end
bus = Bus.find(bus_id)
if direction
bus.right_centroids = centroids_to_add_to_bus
else
bus.left_centroids = centroids_to_add_to_bus
end
end

Can you add another column in the centroids table called type. Now you can use this to apply conditions on your associations. Like this
class Bus < ActiveRecord::Base
has_many :left_centroids, class_name: "Centroid", -> { where type: 'left_centroid' }
has_many :right_centroids, class_name: "Centroid", -> { where type: 'right_centroid' }
end
Not sure of the syntax, but I think this should help.

Related

Ruby on Rails Unique Ordered Query

What I am trying to do is:
Find the most recent CompetencyLog (completed_at) for each Competency on a Course for a Member
class Member < ActiveRecord::Base
has_many :competency_logs
has_many :awards
end
class CompetencyLog < ActiveRecord::Base
belongs_to :member
belongs_to :competency
has_one :course, through: :competency
end
def Course < ActiveRecord::Base
has_many :competencies
has_many :awards
end
class Competency < ActiveRecord::Base
belongs_to :course
end
I have managed to get the ordered list
course = Course.find(params[:course_id])
current_member.competency_logs.where('competency_id IN (?)', course.competency_ids).ordered
From here I have tried a few different things with limited to no success. Any recommendations would be greatly appreciated. Looking to do as much of this in the database as possible for speed since this is frequently called and depended on ever changing timestamps on the CompetencyLog
The results I want are basically
member.competency_logs.where('competency_id IN (?)', course.competency_ids).uniq.pluck(:competency_id)
But instead of the competency_id I want the competency_log models
So I've added some additional relationships and come up with the following so far, currently investigating postgresql DISTINCT ON as an alternative
competency_logs = competencies.collect { |c| c.competency_logs.ordered.first }
competency_logs.collect { |c| c.current? }.all?
The following will work:
max_completed = current_member.competency_logs.where('competency_id IN (?)', course.competency_ids).maximum(:completed_at)
current_member.competency_logs.first(:conditions => {:completed_at => max_completed})
This might solve your problem. It does however operate under the assumption that you don't have duplicated completed_at timestamps per member.
some_member.competency_logs.
where(
competency_id: some_course.competency_ids,
completed_at: some_member.competency_logs.
select("MAX(completed_at) as completed_at").
where(competency_id: some_course.competency_ids).
group(:completed_at)
)

Rails Sort Results based on an association to a child model's method

I've got an Event model. and this Event model has_one Address model. A User model also has_one Address model. I want to get a list of all events in the future, but sorted based on distance between the events address and the users address. This 'distance' is a method on my address model. how can i write the rails query with active record? I've tried many variations of the following but nothing seems to work.
Event.where('end_time > ?', Time.now).includes(Address).order(address.distance_from(User.find(3).primary_address))
i always get this error: NameError: undefined local variable or method `address' for main:Object
definitions of Event and User:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
has_many :addresses
belongs_to :primary_address, :class_name => "Address"
class Event < ActiveRecord::Base
extend TimeFromPieces
attr_accessor :start_date_string
belongs_to :address
delegate :timezone, to: :address
here is my address model:
class Address < ActiveRecord::Base
belongs_to :user
has_many :events
validates_presence_of :name, :address1, :city, :state, :zip
geocoded_by :address_as_string do |obj, results|
Rails.logger.debug { "results: #{results}" }
obj.lat = results.first.coordinates[0]
obj.lng = results.first.coordinates[1]
Rails.logger.debug { "Neighborhoods: #{results.first.address_components_of_type(:neighborhood)}" }
neighborhoods = results.first.address_components_of_type(:neighborhood)
if neighborhoods.count > 1
obj.neighborhood = neighborhoods.first["long_name"]
end
end
after_validation :geocode, :if => :address1_changed?
after_validation :time_zone_for_address, :if => :address1_changed?
before_save :set_primary_if_only_address_for_user
before_save :normalize_fields
scope :near, ->(lat, lng, distance_in_meters = 2000) {
where(%{
ST_Dwithin(
ST_GeographyFromText(
'SRID=4326;POINT(' || addresses.lng || ' ' || addresses.lat || ')'
),
ST_GeographyFromText('SRID=4326;POINT(%f %f)'),
%d
)
} % [lng, lat, distance_in_meters])
}
def self.distance(address1, address2)
query = %{ SELECT
ST_Distance(
ST_GeographyFromText('SRID=4326;POINT(%f %f)'),
ST_GeographyFromText('SRID=4326;POINT(%f %f)')
)
} % [address1.lng, address1.lat, address2.lng, address2.lat]
meters = ActiveRecord::Base.connection.execute(query).values.first.first.to_i
meters/1609.34
end
def distance_from(address)
Address.distance(self, address)
end
end
Instead of using a ruby geocoder, you could use a gem like geokit-rails, which provides geocoding abilities to active record, it gives you nice methods that allow you to query by distance directly to the database, so you don't need to fetch all objects to calculate their distance
You could say that address is a mappable object, and point the lat and lng fields ( if they aren't using the default names used by the gem )
class Address
acts_as_mappable
end
Then you could say that an event is also mappable through the address object attached to it
class Event
acts_as_mappable through: :address
end
The query would look something like this
Event.where('end_time < ?', Time.now).by_distance(origin: address)

Why do these models and methods crash my server/stack level too deep?

I'm new to Ruby-on-Rails and have been trying to slowly understand how everything works but I've run into a brick wall at this point.
I have the models
class User < ActiveRecord::Base
validates :username, {:uniqueness => true, :presence => true}
validates_presence_of :password_digest, :on => :create
has_one :player, dependent: :destroy
has_secure_password
has_many :planets, through: :player
end
class Player < ActiveRecord::Base
has_many :planets, dependent: :destroy
belongs_to :user
validates_presence_of :race, on: :create
end
class Planet < ActiveRecord::Base
belongs_to :player
end
In my UserController I create a User with login and password and that works just fine. Then I redirect to my PlayerController where I have a simple radio button to select race (just one right now) and then create the player and add it to the current user. That also works just fine. Now the problem is when I try to add a Planet into the player. I do this in the same controller method (not sure if this is proper but it needs to populate with a default planet to a new player).
def generate_attributes
{
class: 'HomeWorld',
land: 500,
ore: 100,
agriculture: 0,
industry: 0,
housing: 10
}
end
def create
#current_user.player = Player.new(player_params)
#current_user.player.planets.create generate_attributes
redirect_to action: :home
end
On #current_user.player.planets.create generate_attributes my server completely crashes. Previously when I removed both belongs_to :user from player and belongs_to :player from planet I would get a Stack Level Too Deep exception.
There's obviously something I'm doing horrendously wrong but I can't figure out what it is. I attempted to debug but once I get to a certain point within the rails source code my debugger would disconnect and I'd have to force the server to stop.
Any insight would be greatly appreciated.
Based on your generate_attributes method, it looks like you might have a field called class on your Planet model. This is almost definitely causing your problems, and you need to use a different name than class.
The reason is that in Ruby, just about everything has a class, e.g.:
>> "foo".class
# => String
>> User.first.class
# => User
However, for your Planet model, class is being set to a string. Rails uses a lot of introspection into class names to deal with associations; because you're linking this Planet to a Player with a belongs_to, I'm assuming Rails is checking the class of the Planet object to make sure it's correct, seeing "HomeWorld" instead of Planet like it expects, and raising an exception as a result.
(And even taking associations out of the picture, you'll still invariably run into problems using class as a field name).

Using CanCan with Polymorphic Associations

I am struggling to use CanCan with my polymorphic associations. It is breaking down when I try and load the polymorphic objects in my controller.
class Trip < ActiveRecord::Base
has_many :images, as: :viewable
end
class Image < ActiveRecord::Base
belongs_to :viewable, polymorphic: true
end
Now I have a controller that deals specifically with images assigned to a trip.
class TripImagesController < ApplicationController
load_and_authorize_resource :trip
load_and_authorize_resource :image, through: :trip
def index
end
end
However, when I hit this index action I get the following error:
Couldn't find Image with id= [WHERE "images"."viewable_id" = $1 AND "images"."viewable_type" = $2]
I can see the query being executed in the Rails logs:
SELECT "images".* FROM "images" WHERE "images"."viewable_id" = $1 AND "images"."viewable_type" = $2 AND "images"."id" = $3 LIMIT 1 [["viewable_id", 1], ["viewable_type", "Trip"], ["id", ""]]
So the select statement looks good, except that it's trying to find only a single image (even though I'm in an index action). It's specifying an image id (which obviously does not exist in the route params) and it's also limiting the result to a single row.
This is using Rails 4.0.2 and CanCan 1.6.10. What am I missing here?

ActiveRecord query through multiple joins

I have a schema like this.
managers
has_many :emails
has_many :stores
emails
belongs_to :manager
stores
belongs_to :manager
belongs_to :region
regions
has_many :stores
has_many :readings
readings
belongs_to :regions
I want to get readings for a manager. In SQL I would do something like this.
SELECT * FROM managers
JOIN stores ON stores.manager_id = managers.id
JOIN regions ON stores.region_id = regions.id
JOIN readings ON readings.region_number = regions.number
WHERE
manager.name = 'John Smith'
AND
regions.number = '1234567'
LIMIT 100
I can't figure out how to do this in activerecord. I have been trying to make sense of http://guides.rubyonrails.org/active_record_querying.html and http://guides.rubyonrails.org/association_basics.html but it's not sinking in. I think I just need to see it from a different view point.
I was thinking I would be accessing the data like this but I think I just don't understand how it works.
managers.name
managers.stores.name
managers.stores.regions.readings.limit(10)
I have been having to so something like this which is a whole lot uglier.
managers.first.stores.first.regions.first.readings.limit(10)
Consider the following models (and use of has_many through) :
class Reading < ActiveRecord::Base
belongs_to :region,
inverse_of: :readings
end
class Region < ActiveRecord::Base
has_many :readings,
inverse_of: :region
has_many :stores,
inverse_of: :region
end
class Store < ActiveRecord::Base
belongs_to :region,
inverse_of: :stores
belongs_to :manager,
inverse_of: :stores
end
class Manager < ActiveRecord::Base
has_many :stores,
inverse_of: :region
has_many :emails,
inverse_of: :manager
has_many :regions,
through: :stores
has_many :readings,
through: :regions
end
class Email < ActiveRecord::Base
belongs_to :manager,
inverse_of: :emails
end
Now your question is a little ambiguous because you say you want to obtain readings for a manager but your SQL doesn't select readings at all and also prescribes a region.
Assuming you want all Reading's matching a given Manager and Region:
#readings = Reading.joins(region: { stores: :manager }).where(
manager: { name: 'John Smith' },
region: { id: 1234567 })
Assuming you also want to eager load regions, stores and managers to avoid 1+N queries:
#readings = Reading.includes(region: { stores: :manager }).where(
manager: { name: 'John Smith' },
region: { id: 1234567 })
Assuming you have a managers name and want both their details and readings:
#manager = Manager.where(name: 'John Smith').first!
#readings = manager.readings
All of the above query examples return ActiveRecord::Relation's which can be further chained with where conditions, or joins, limit, group, having and order etc
You should also consider the differences of joins, includes, preload, eager_load and references methods. There is a brief on them here I would alos encourage you to read docs, guides and blogs about Arel as it supports joins and aliasing too.
After using ActiveRecord in anger for a while now I have come to the conclusion that Sequel/SequelModel is a much better DB/ORM than ActiveRecord. No disrespect to the developers but I've found Sequel is a better tool. Arel has thin documentation for years now and ActiveRecord/Arel have failings in a number of areas such as join conditions, control of join types and eager loading, unions, intersections, recursive queries, trees/adjacency lists, and many other SQL features that Sequel covers.
Since you appear to be just starting out with AR you may wish to instead start out with Sequel than struggle with weaknesses and the frustrations of ActiveRecord querying including the disjointed use of AR and Arel, Relations vs Associations and query composition oddities, it goes on and on. There is nothing more frustrating than knowing the SQL you want but ActiveRecord/Arel conspire to stop you so you're forced to use the touted escape route and 'just use SQL string fragment' and you get back a result that can't be chained, but the rest of your code expects a Relation! (eg paged results)
I wanted to comment on the comment from user1757006 Oct 4 '13 at 14:39, but I don't have enough points..
Anyway, this addresses deeper nesting scenarios. I have added planet and country models to show how the syntax works when needing to chain additional models.
#readings = Reading.includes(planet: [ country: [ region: [ {stores:
:manager}]]]).where(
manager: { name: 'John Smith' },
region: {id: 1234567 })
Assuming managers is a model name
managers.find(:all,
:joins=>" JOIN stores ON stores.manager_id = managers.id
JOIN regions ON stores.region_id = regions.id
JOIN readings ON readings.region_number = regions.number"
:conditions=>"manager.name = 'John Smith' AND regions.number = '1234567'"
:limit=>100)
try something like this:
managers.joins(stores: {regions: :readings}).where('managers.name = ? AND regions.number = ?', 'John Smith', '1234567').limit(100)