Rails 4 polymorphic has_many ignores table_name - ruby-on-rails-4

Short version: I'm building a new Rails 4 application that uses (read-only) some tables from a database used by a legacy Rails 2 application, which is still in use. The old application models/tables were very confusingly named, however (especially in the context of the new application), so I want to use different names for the models/tables using self.table_name. This all works perfectly until I tried to add in a polymorphic relationship. Rails ignores my defined table_name and does a query on the type using the new model name, which of course is different so it doesn't work. Is there any way to change this?
Long version: There are three models in this equation, and here they are:
class Exporter < MysqlBase
has_many :lic_exporter_addresses, :as => :place
self.table_name = 'excons'
self.primary_key = 'id'
end
class LicBusiness < MysqlBase
has_one :physical_address, -> { where(category: 'Physical') }, :class_name => 'LicExporterAddress', :as => :place
has_one :mailing_address, -> { where(category: 'Mailing') }, :class_name => 'LicExporterAddress', :as => :place
has_many :lic_exporter_addresses, :as => :place
self.table_name = 'businesses'
self.primary_key = 'id'
end
class LicExporterAddress < MysqlBase
belongs_to :place, polymorphic: true
self.table_name = 'addresses'
self.primary_key = 'id'
end
We have a ton of different kinds of businesses, so the Business model is the most problematic. I really don't want to have that in the new app because it would be very confusing as to what a "business" actually is. With the current code if I go into the rails console and try to get lic_exporter_addresses for a LicBusiness or Exporter, it does:
SELECT `addresses`.* FROM `addresses` WHERE `addresses`.`place_id` = '00044c693f6848f9b0978f873cf9999a' AND `addresses`.`place_type` = 'LicBusiness'
when what I need is place_type = 'Business'.
Is there any way to tell Rails what place_type to look for? I did see this question and the second answer looked promising, except that I'm already sort of doing that with Physical and Mailing addresses so I can't figure out how that'd work with both options at the same time... Thanks for any info or ideas.

In Rails 4.2, it looks like the exact string used for the query is defined as owner.class.base_class.name, where owner is the model declaring the association. So I don't think it's directly supported. But there are a few ways I can think of to hack around this. I think the most promising might be, in LicBusiness:
has_many :lic_exporter_addresses, ->{where place_type: "Business"}, foreign_key: "place_id"
That is, don't define the association as polymorphic, but define the type scope yourself. This will NOT correctly define place_type in the lic_exporter_addresses table if you ever use lic_exporter_address.place = some_lic_business_instance. However you said this table was read-only, so this may in fact not be an issue for you. If it is, there may be ways to override the behavior to get what you need.
Two other ideas both make me very nervous and I think they are probably quite dangerous for unintended side-effects. They are to override LicBusiness.base_class (this might actually be ok if you do not now and never will have STI set up on LicBusiness, but I'm still nervous), or to override LicBusiness.name (I'm pretty sure this would have unintended side-effects).

Related

NoMethodError - new since upgrading to Rails 4

I'm at my wit's end. I upgraded to Rails 4.2.10, and everything is terrible.
Here is the relevant part of /models/product.rb
class Product < ActiveRecord::Base
delegate_attributes :price, :is_master, :to => :master
And here is /models/variant.rb:
class Variant < ActiveRecord::Base
belongs_to :product
The variants table has fields for "price" and "is_master". Products table does not.
It used to be the case that one could access Product.price and it would get/set the price for the master variant (there's really only one variant per product, the way things are currently set up).
Now it complains that:
NoMethodError: undefined method `price=' for #<Product:0x0000000d63b980>
It's true. There's no method called price=. But why wasn't this an issue before, and what on earth should I put in that method if I create it?
Here's the code to generate a product in db/seeds.rb:
product = Product.create!({
name: "Product_#{i}",
description: Faker::Lorem.sentence,
store_id: u.store.id,
master_attributes: {
listing_folder_id: uuids[i],
version_folder_id: uuids[i]
}
})
product.price = 10
product.save!
end
delegate_attributes isn't a Rails method and looks like it comes from a gem (or gems) that aren't actively maintained?
If there's a new version of whatever gem you're using that might help, because the short answer is that part of the "delegating" of an attribute would involve getting and setting the attribute, so it would generate #price= for you.
If you want to define it yourself, this should do it (within your Product class):
def price=(*args)
master.price=(*args)
end
or if you want to be more explicit:
def price=(amount)
master.price = amount
end

Rails 4 Has_Many With Finder_SQL Deprecated

I am upgrading a Rails app to 4.0. I am receiving the following deprecation warning. I have Google'd on this, but have not found anything that tells how to change this.
DEPRECATION WARNING: The :finder_sql association option is deprecated. Please find an alternative (such as using scopes)...
Here is the scope that is causing the warning:
has_many :elective_instructors,
:class_name => "Instructor",
:finder_sql => proc { "SELECT DISTINCT people.* FROM people
INNER JOIN class_sections ON class_sections.instructor_id = people.id
INNER JOIN courses ON courses.id = class_sections.course_id
INNER JOIN taken_classes ON class_sections.id = taken_classes.class_section_id
WHERE
courses.core = FALSE
AND
taken_classes.student_id = #{id}
AND
people.type = 'Instructor'
AND
people.ignore = FALSE" }
Any ideas would be greatly appreciated. Thanks!
As of 4.1, :finder_sql is not just deprecated - it has been completely REMOVED from Rails.
Here is one way to do something similar through the use of scopes. Let's say we have a User class and Job class (so a user can have many jobs). And let's say that we want to find all distinct jobs that this user holds (contrived example, but it illustrates the point) and let's say we want to use custom SQL for this. We can use find_by_sql as follows
class Job < ActiveRecord::Base
scope :distinct_user_jobs, -> (user_id){ find_by_sql(["SELECT DISTINCT jobs.* FROM jobs WHERE jobs.user_id=?", user_id]) }
end
And then pass in the user_id
user = User.first
Job.distinct_user_jobs(user.id)

Eager Loading Multiple Associations and serialization

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.

Problems exposing has_and_belongs_to_many relation using Grape::Entity

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?

Rails 4 not passing permitted parameters

Apologies for posting what seems to be an oft-asked question but I have toiled for two days without getting closer.
I am running Rails 4.0.1 (with Devise, CanCan, among others) and have user and role models with a HABTM users_roles table (as created by the since-removed rolify).
class Role < ActiveRecord::Base
has_and_belongs_to_many :users, :join_table => :users_roles
belongs_to :resource, :polymorphic => true
attr_reader :user_tokens
def user_tokens=(ids)
self.user_ids = ids.split(",")
end
end
My submitted form data is:
"role"=>{"name"=>"disabled", "resource_id"=>"", "resource_type"=>"Phone", "user_tokens"=>["", "3"]}
(Not sure where the empty string comes from; it's not in the option list, but hey, welcome to Rails).
My problem is with getting the user_tokens data passed into the appropriate variable in the controller.
There are lots of postings suggesting the correct format (e.g., how to permit an array with strong parameters)
If I specify the permitted parameters thus:
params.require(:role).permit(:name, :resource_id, :resource_type, :user_tokens => [] )
I get a '500 Internal server error' with no other details.
If I specify the permit as
params.require(:role).permit(:name, :resource_id, :resource_type, user_tokens: [] )
I don't get any errors (No unpermitted parameters in the dev log) but the user_tokens array is not passed through in the #role.
If I run this scenario through a console I get:
params = ActionController::Parameters.new(:role => {:resource_id => "", :resource_type => "Phone", :user_tokens => ["", "3"]})
params.require(:role).permit(:name, :resource_id, :resource_type, user_tokens: [] )
=> {"resource_id"=>"", "resource_type"=>"Phone", "user_tokens"=>["", "3"]}
params.require(:role).permit(:user_tokens).permitted?
Unpermitted parameters: resource_id, resource_type, user_tokens
=> true
I'm stuck as to where I should try looking next.
Even though I was sure I had already tried it, changing the model to be
def user_tokens=(ids)
self.user_ids = ids
end
fixed it. I've always struggled to understand this bit of notation except when I've spent 4 days trying why my many:many models don't work.
I guess it'd be easier if I was doing this full time rather than just for particular sysadmin projects.