I am connecting to one of my company's SQL Server databases, and trying to set up ActiveRecord so I can treat them just the same as Rails objects.
I have these two models:
class Change < ActiveRecord::Base
belongs_to :affected_contact, class_name: "Contact"
end
class Contact
# Contact's primary key is a binary UUID; I can't change this
end
I am trying to get the affected contact of one particular change. Normally, this would be a simple case, but:
Change.first.affected_contact
Change Load (52.6ms) EXEC sp_executesql N'SELECT TOP (1) [chg].* FROM [chg] ORDER BY [chg].[id] ASC'
Contact Load (28.0ms) EXEC sp_executesql N'SELECT TOP (1) [ca_contact].* FROM [ca_contact] WHERE [ca_contact].[contact_uuid] = #0', N'#0 binary', #0 = 0xfcf9a8ac6381aa4386c9b10ee382e10b [["contact_uuid", "<16 bytes of binary data>"]]
=> nil
... that's not what I want! And yet, if I eager-load the join first, it works:
Change.eager_load(:affected_contact).first.affected_contact
SQL (34.4ms) EXEC sp_executesql N'SELECT TOP (1) holy_crap_theres_a_lot_of_columns FROM [chg] LEFT OUTER JOIN [ca_contact] ON [ca_contact].[contact_uuid] = [chg].[affected_contact] ORDER BY [chg].[id] ASC'
=> #<Contact contact_uuid: "\xFC\xF9\xA8\xACc\x81\xAAC\x86\xC9\xB1\x0E\xE3\x82\xE1\v", ... >
In fact, if I force the matching to happen in the JOIN clause in any way, it will work, but belongs_to seems to use the WHERE clause instead, and nil is the best response I can get (a lot of the time, there are conversion errors between the string and its binary type).
Is there a way to ensure eager-loading through the JOIN clause happens by default on the belongs_to association?
I found that #find_by_contact_uuid (contact_uuid being the primary key) worked, where #find didn't, for some reason. That led to this being implemented.
I have ended up essentially rewriting the association methods that Active Record supplies:
module AssociationMethods
def self.included(base)
base.reflect_on_all_associations(:belong_to).each do |a|
define_method a.name do
# #find_by_<uuid_pk> seems to work where #find doesn't
a.klass.send "find_by_#{a.association_primary_key}", self[a.foreign_key]
end
end
base.reflect_on_all_associations(:has_many).each do |a|
define_method a.name do
a.klass.where(a.foreign_key => self.send(a.association_primary_key))
end
end
end
end
class Contact
has_many :changes, foreign_key: :affected_contact_id
include AssociationMethods # include *after* all associations are defined
end
class Change
belongs_to :affected_contact, class_name: 'Contact'
include AssociationMethods
end
It doesn't cover everything that Active Record supplies when setting up the associations, but it seems to do the trick.
Using includes should resolve your problem. This is because includes will preload or eager_load depending on your other conditions.
read more here
Related
In my project I have 3 models Assignment, Question and MultipleChoice with the following associations
assignment.rb
has_many :questions, dependent: :destroy
question.rb
belongs_to :assignment, class_name: 'Assignment', foreign_key: :assignment_id
has_many :multiple_choices, dependent: :destroy
multiple_choice.rb
belongs_to :question
Now I want to make a query like below
#assignment = Assignment.find(params[:id])
#questions = #assignment.questions.includes(:multiple_choices)
This is not working as expected.
So, I want all questions that belongs to the assignment for the params[:id] and the associated multiple choices that belongs to a question. My above query do not give any error but it only show questions not multiple choices associated with question. How can I do this? I am learning api development for rails. So I want to send this value as json and probably I will need serialization. How can I do this? I am working on rails 4.
Edit
well the output for
#questions = #assignment.questions.includes(:multiple_choices) and
#questions = #assignment.questions.eager_load(:multiple_choices) and
#questions = #assignment.questions are all same.
I dont understand why the output do not include any value from multiple choices table
Output of the command
#assignment.questions.eager_load(:multiple_choices).to_sql id
=> "SELECT \"questions\".\"id\" AS t0_r0, \"questions\".\"content\" AS t0_r1, \"questions\".\"q_type\" AS t0_r2, \"quest
ions\".\"created_at\" AS t0_r3, \"questions\".\"updated_at\" AS t0_r4, \"questions\".\"assignment_id\" AS t0_r5, \"multi
ple_choices\".\"id\" AS t1_r0, \"multiple_choices\".\"content\" AS t1_r1, \"multiple_choices\".\"created_at\" AS t1_r2,
\"multiple_choices\".\"updated_at\" AS t1_r3, \"multiple_choices\".\"question_id\" AS t1_r4 FROM \"questions\" LEFT OUTE
R JOIN \"multiple_choices\" ON \"multiple_choices\".\"question_id\" = \"questions\".\"id\" WHERE \"questions\".\"assignm
ent_id\" = $1"
The behaviour for includes function changed in Rails 4. You can find more details here:
http://blog.arkency.com/2013/12/rails4-preloading/
I also suppose, that if you will use eager_load instead of includes, you will get the result you need.
#assignment is an object from which you are getting the questions.
But with #questions = #assignment.questions.includes(:multiple_choices) how can you get the multiple_choices without calling this on an object?
Try this->
#assignment = Assignment.includes(:questions).find(params[:id])
#assignment.questions.includes(:multiple_choices).collect {|question| question.multiple_choices }
This also includes eager loading.
Hope you'll get what you expect.
I have associations as
Delivery Model
class Delivery < ActiveRecord::Base
belongs_to :schedule, inverse_of: :deliveries
accepts_nested_attributes_for :schedule
end
Schedule Model
class Schedule < ActiveRecord::Base
has_many :deliveries, inverse_of: :schedule
include PushUpdates
def offer
deliveries.last.try(:offer)
end
end
PushUpdates: app/model/concerns/push_updates.rb
module PushUpdates
extend ActiveSupport::Concern
included do
after_create {update_client_store :create unless Rails.env.test? }
after_update {update_client_store :update unless Rails.env.test? }
end
def update_client_store(operation)
...
self.offer
end
end
Now, while saving delivery, am also accepting the one set of schedule details.
and when it saves delivery and schedule, there is after_create callback called for schedule model, and when it tries to find out the deliveries.last it gives nil value.
Since am accepting it as a part of nested attributes, then delivery object should be available for it but still it gives nil value.
Am I missing anything here? Thanks.
It looks to me that Rails is trying to make a database call before the associated records have been committed.
There is an additional callback that you could look at after_commit (in place of after_create that is fired once the records have been committed to whatever database you are using.
That should mean that your deliveries.last should come back non-empty. You could also force a database lookup on the model with self.reload, although that could cause unexpected behaviour.
i have Trip model which has destinations defined like that:
class Trip < ActiveRecord::Base
...
has_and_belongs_to_many :destinations, join_table: :trips_destinations
...
end
What I want to do is to expose the trip information included the associated destinations. I defined this response entity for the destinations:
module Services
module Trips
class DestinationResponseEntity < Grape::Entity
expose :id
expose :name
end
end
end
And the trip destination entity is this:
module Services
module Trips
class TripResponseEntity < Grape::Entity
expose :id
expose :title
expose :duration
expose :total_price
expose :description
expose :destinations, using: Trips::DestinationResponseEntity
expose :photo
end
end
end
I'm presenting the result in that way:
present trip, :with => Trips::TripResponseEntity
But the response of the service returns always an empty destination array.
[{"id":3,"title":"Islandhopping in Thailand","duration":14,"total_price":3450,"description":"Relax under swaying palm trees then jump into crystal-clear waters",**"destinations":[]**,"photo":"http://s3.amazonaws.com/ntradadevelopment/images/trips/3/original/thailand.jpeg"]
In the console I can see all the destinations associated with the trip properly.
Any clue of what could be causing the issue is really appreciated.
What you're doing with Grape::Entity looks correct to me.
I encountered a similar issue that turned out to be a problem with my has_and_belongs_to_many relationship not being defined correctly so you might need to check that again.
In particular, you seem to be overriding the default naming convention of the join table. Could it be tripping up Grape?
I use rails 4.1 with Postgres and I have a model like this one:
class User < ActiveRecord::Base
has_many :events
has_many :event_memberships
has_many :shared_events, through: :event_memberships, source: event
def all_events
#... code goes here
end
end
I'd like to do an union between events and shared_events associations but I can't make it work.
The only thing I can make it work is this:
def all_events
events + shared_events
end
But it will execute 2 SQL queries instead of one and I won't be able to order or limit the results.
I have also tried something like this:
def all_events
User.find_by_sql("(#{events.to_sql}) UNION (#{shared_events.to_sql})")
end
Or this method https://coderwall.com/p/9hohaa
But both methods throw this error:
PG::UndefinedParameter: ERROR: there is no parameter $1
That seems to be known bug in rails 4+ and it won't be resolved https://github.com/rails/rails/issues/13686
So my only option here seems to write plain old SQL.
Anyone with a better idea ?
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?