Joining two Models in Rails 4 - ruby-on-rails-4

I have a Location model and an Answer model and I'm trying to join the two, so that when an Answer is created, it is associated with the Location.
I have added in the appropriate has_one and belongs_to associations, added a migration to add Locations id to the Answer table.
Trouble is when an Answer gets created, the 'location_id' is nil.
Any help would be greatly appreciated. Here's my files:
Location Model
class Location < ActiveRecord::Base
require 'Geoip-rails'
has_one :answer
def self.get_location
geoip = GeoIP.new("lib/GeoLiteCity.dat")
l = Location.new
l.city = geoip.country('78.151.144.93').city_name
l.save
end
end
Answer Model
class Answer < ActiveRecord::Base
belongs_to :location
belongs_to :question
belongs_to :choice
has_one :question, :through => :choice
end
Add location to Answers migration
class AddLocationToAnswers < ActiveRecord::Migration
def change
add_column :answers, :location_id, :integer
add_index :answers, :location_id
end
end
Questions controller - Answer action
def answer
#choice = Choice.find(params[:id])
#location = Location.get_location
#answer = Answer.create(:question_id => #choice.question_id, :choice_id => #choice.id)
redirect_to questions_url
end
Update:
I have now changed around the association from Mischa's comments.
Now in the console I can do this and join the Models:
1.9.3-p392 :013 > a = Answer.last
Answer Load (0.2ms) SELECT "answers".* FROM "answers" ORDER BY "answers"."id" DESC LIMIT 1
=> #<Answer id: 13, question_id: 1, choice_id: 2, created_at: "2013-07-05 09:50:28", updated_at: "2013-07-05 09:50:28", location_id: nil>
1.9.3-p392 :014 > a.location
=> nil
1.9.3-p392 :015 > a.location = Location.last
Location Load (0.2ms) SELECT "locations".* FROM "locations" ORDER BY "locations"."id" DESC LIMIT 1
=> #<Location id: 25, lat: nil, lon: nil, city: "London", created_at: "2013-07-05 09:50:28", updated_at: "2013-07-05 09:50:28">
1.9.3-p392 :016 > a
=> #<Answer id: 13, question_id: 1, choice_id: 2, created_at: "2013-07-05 09:50:28", updated_at: "2013-07-05 09:50:28", location_id: 25>
1.9.3-p392 :017 >
I've tried achieving this in 'answer' action in the controller as follows:
#answer = Answer.create(:question_id => #choice.question_id, :choice_id => #choice.id, :location_id => #location.id)
But :location_id => #location.id give me an error:
undefined method `id' for true:TrueClass
I don't know how to combine the two models in the controller
Solved
For anyone interested. My get_location method wasn't doing what it should. I had to call the object l after it had been saved. 5 hours of head banging and all that was needed was 'l'.
def self.get_location
geoip = GeoIP.new("lib/GeoLiteCity.dat")
l = Location.new
l.city = geoip.country('78.151.144.93').city_name
l.save!
l
end

Related

Polymorphic association with condition

We have models as below...
class Post
has_many: :comments, as: :commentable
end
class Media
has_many: :comments, as: :commentable
end
class Comment
belongs_to: :commentable, polymorphic: true
belongs_to: :post, foreign_key::post_id, foreign_type: 'Post'
end
Exampale
Post1 has Comment3 => {id: 3, text: "Some comment 3", commentable_id: 1, commentable_type: 'Post'}
Media2 has Comment4 => {id: 4, text: "Some comment 4", commentable_id: 2, commentable_type: 'Media'}
When I want to access Comment3.post it gives result as expected.
But When some how want access Comment4.post it brings an object from post table which has id = 2, but expected nil, coz Comment4 does not belongs to any post.
We can get from below method inside Comment model, but want as an association.
def post
self.commentable if self.commentable_type == 'Post'
end
Could not get what I expected.., Please help here...
I like your approach
def post
self.commentable if self.commentable_type == 'Post'
end
But if you really need it in the association way, then you can try with
belongs_to :post, foreign_key: :commentable_id, foreign_type: :commentable_type, polymorphic: true
OR
belongs_to :post, -> (record){ record.commentable_type == 'Post' ? joins(:comments).where(comments: {commentable_type: 'Post'}) : where("false") }, foreign_key: :commentable_id
Hope, it helps.

rails 4 "chained" has_one: :through relations work with persisted but not new objects

I have a rather complex model setup:
class LineItem < ActiveRecord::Base # STI parent class
end
class VendorLineItem < LineItem
belongs_to: :unit_rate, class_name: 'Billing::UnitRate'
has_one :contract_resource, through: :unit_rate
has_one :resource, through: :contract_resource
end
Note that the above two classes are not namespaced, while the associated models below all live in a Billing namespace.
class Billing::UnitRate < ActiveRecord::Base
belongs_to :contract_resource, class_name: 'Billing::ContractResource'
has_one :contract, through: :contract_resource
has_one :resource, through: :contract_resource
has_many :vendor_line_items, dependent: :destroy
end
class Billing::ContractResource < ActiveRecord::Base
belongs_to :contract
belongs_to :resource, class_name: 'Billing::Resource'
has_many :unit_rates, dependent: :destroy
end
class Billing::Resource < ActiveRecord::Base
has_many :contract_resources
has_many :contracts, through: :contract_resources
has_many :unit_rates, through: :contract_resources
end
My problem is with the VendorLineItem class, where I need to get at the associated resource.
If I am working with persisted data, all is well. In a rails console:
2.2.3 :001 > x = VendorLineItem.last
VendorLineItem Load (9.8ms) SELECT "line_items".* FROM "line_items" WHERE "line_items"."type" IN ('VendorLineItem') ORDER BY "line_items"."id" DESC LIMIT 1
=> #<VendorLineItem id: 42, fire_id: 938774, type: "VendorLineItem", unit_rate_id: 716, units_utilized: #<BigDecimal:6184d30,'0.1E1',9(18)>, fire_department_id: nil, dollar_amount: nil, created_at: "2016-11-18 16:22:23", updated_at: "2016-11-18 16:22:23", total: #<BigDecimal:6184060,'0.25E2',9(18)>>
2.2.3 :002 > x.resource
Billing::Resource Load (8.9ms) SELECT "billing"."resources".* FROM "billing"."resources" INNER JOIN "contracts_resources" ON "billing"."resources"."id" = "contracts_resources"."resource_id" INNER JOIN "billing"."unit_rates" ON "contracts_resources"."id" = "billing"."unit_rates"."contract_resource_id" WHERE "billing"."unit_rates"."id" = $1 LIMIT 1 [["id", 716]]
=> #<Billing::Resource id: 39, name: nil, description: nil, code: nil, sort_order: nil, is_active: true, constant_name: "E0", created_at: "2015-10-14 15:59:00", updated_at: "2015-10-14 15:59:00", vendor_id: 1, resource_type_id: 37>
If, OTOH, I am working with a new instance, the resource comes back as nil (rails doesn't even try; no sql is emitted).
2.2.3 :003 > vli = VendorLineItem.new unit_rate_id: 711
=> #<VendorLineItem id: nil, fire_id: nil, type: "VendorLineItem", unit_rate_id: 711, units_utilized: nil, fire_department_id: nil, dollar_amount: nil, created_at: nil, updated_at: nil, total: nil>
2.2.3 :004 > vli.resource
=> nil
But I can access the resource through an explicit chain of the associations:
2.2.3 :005 > vli.unit_rate.contract_resource.resource
Billing::UnitRate Load (8.9ms) SELECT "billing"."unit_rates".* FROM "billing"."unit_rates" WHERE "billing"."unit_rates"."id" = $1 LIMIT 1 [["id", 711]]
Billing::ContractResource Load (8.8ms) SELECT "contracts_resources".* FROM "contracts_resources" WHERE "contracts_resources"."id" = $1 LIMIT 1 [["id", 261]]
Billing::Resource Load (8.7ms) SELECT "billing"."resources".* FROM "billing"."resources" WHERE "billing"."resources"."id" = $1 LIMIT 1 [["id", 34]]
=> #<Billing::Resource id: 34, name: nil, description: nil, code: nil, sort_order: nil, is_active: true, constant_name: "D5", created_at: "2015-10-14 15:59:00", updated_at: "2015-10-14 15:59:00", vendor_id: 1, resource_type_id: 19>
I can also find the resource through the has_one :contract_resource, through: :unit_rate association without a problem:
2.2.3 :007 > vli.contract_resource.resource
Billing::ContractResource Load (8.7ms) SELECT "contracts_resources".* FROM "contracts_resources" INNER JOIN "billing"."unit_rates" ON "contracts_resources"."id" = "billing"."unit_rates"."contract_resource_id" WHERE "billing"."unit_rates"."id" = $1 LIMIT 1 [["id", 711]]
Billing::Resource Load (8.7ms) SELECT "billing"."resources".* FROM "billing"."resources" WHERE "billing"."resources"."id" = $1 LIMIT 1 [["id", 34]]
=> #<Billing::Resource id: 34, name: nil, description: nil, code: nil, sort_order: nil, is_active: true, constant_name: "D5", created_at: "2015-10-14 15:59:00", updated_at: "2015-10-14 15:59:00", vendor_id: 1, resource_type_id: 19>
Why is the has_one: resource, through: :contract_resource association not working here?
Turns out this is a long-standing bug in rails:
https://github.com/rails/rails/issues/20827 [Closed]
https://github.com/rails/rails/issues/33155 [Reopened here]

Rails :has_many :through sort order issue in model

I am having some weird behavior in a Rails model and I'm not quite sure why. Thanks to anyone who can point me as to what I'm missing or not understanding.
The Problem
I have a model Car with a has_many :through relationship on Part through CarsPart. I expect accessing a car's parts to be in alphabetical order. I created the model specs and the controller specs. In the controller, the parts are ordered in alphabetical order as called out in the has_many relationship. In the controller spec, they are in order. In the model spec, they are not. I don't think it's tied to the testing framework because i've debugged the running server and see the same behavior.
The Code
Car
class Car < ActiveRecord::Base
has_many :cars_parts
has_many :parts, -> { order('name asc') }, through: :cars_parts, :dependent => :destroy
accepts_nested_attributes_for :cars_parts, :allow_destroy => true
validates :make, :model, presence: true
validate :validate_cars_parts
def validate_cars_parts
errors.add(:parts, "wrong number") if self.cars_parts.size < 1
self.cars_parts.each do |car_part|
part = Part.find(car_part.part_id)
errors.add(:parts, "doesn't exist") if part == nil
end
end
end
Part
class Part < ActiveRecord::Base
belongs_to :cars_part
has_many :cars, through: :cars_parts
validates :name, presence: true
end
CarsPart
class CarsPart < ActiveRecord::Base
belongs_to :car
belongs_to :part
end
Cars Controller Spec Passes
require 'rails_helper'
RSpec.describe CarsController, type: :controller do
render_views
context 'property features' do
describe "GET #show/id returns ordered parts" do
before :each do
#p4 = FactoryGirl.create(:part, :name => 'BPart2')
#p3 = FactoryGirl.create(:part, :name => 'APart2')
#p6 = FactoryGirl.create(:part, :name => 'BPart3')
#p5 = FactoryGirl.create(:part, :name => 'APart3')
#p2 = FactoryGirl.create(:part, :name => 'BPart1')
#p1 = FactoryGirl.create(:part, :name => 'APart1')
end
it 'returns parts in alphabetical order' do
car = FactoryGirl.create(:car, make: 'Nissan', model: 'Murano', parts: [#p4, #p3, #p6, #p5, #p2, #p1])
get :show, id: car.id, format: :json
expect(response).to have_http_status(:success)
expect(response).to render_template(:show)
response_json = JSON.parse(response.body)
response_json['parts'].each do |part|
puts part['id']
end
expect(response_json['parts'].size).to eq(6)
expect(response_json['parts'][0]['id']).to eq(#p1.id)
expect(response_json['parts'][1]['id']).to eq(#p3.id)
expect(response_json['parts'][2]['id']).to eq(#p5.id)
expect(response_json['parts'][3]['id']).to eq(#p2.id)
expect(response_json['parts'][4]['id']).to eq(#p4.id)
expect(response_json['parts'][5]['id']).to eq(#p6.id)
end
end
end
end
Car Model Spec Fails
require 'rails_helper'
RSpec.describe Car, type: :model do
context 'car parts' do
before :each do
#p4 = FactoryGirl.create(:part, :name => 'BPart2')
#p3 = FactoryGirl.create(:part, :name => 'APart2')
#p6 = FactoryGirl.create(:part, :name => 'BPart3')
#p5 = FactoryGirl.create(:part, :name => 'APart3')
#p2 = FactoryGirl.create(:part, :name => 'BPart1')
#p1 = FactoryGirl.create(:part, :name => 'APart1')
end
it 'returns parts in alphabetical order' do
car = FactoryGirl.create(:car, make: 'Nissan', model: 'Murano', parts: [#p4, #p3, #p6, #p5, #p2, #p1])
expect(car).to be_valid
expect(car.errors.messages.size).to eq(0)
expect(car.parts.size).to eq(6)
car.parts.each do |part|
puts part.id
end
expect(car.parts[0]['name']).to eq('APart1')
expect(car.parts[1]['name']).to eq('APart2')
expect(car.parts[2]['name']).to eq('APart3')
expect(car.parts[3]['name']).to eq('BPart1')
expect(car.parts[4]['name']).to eq('BPart2')
expect(car.parts[5]['name']).to eq('BPart3')
end
end
end
Summary
Please help me understand why when accessing them through the model they are not ordered, but when the controller looks them up and renders them, they are.
Thank you.
EDIT
The only way I have managed to get this to work is to create a 'ordered_parts' method that does the ordering and remove the order from the has_many. I had to change all my controllers to call 'ordered_parts' instead of the 'parts' method. Surely there is a better way. If anyone knows, please help.
def ordered_parts
self.parts.order('name asc')
end

Before_save = Change default value for many to many

Can someone help me figure out what code I should put in my change_default method?
I would like to be able to do something like this...
user = User.find(1)
user.companies.first.default!
or
user.companies.find_by_company_id(2).default!
My code:
class Role < ActiveRecord::Base
before_save :change_default
belongs_to :user
belongs_to :company
def change_default
#update default field for to false for current scope
#Update current record default field to true
#this is what I currently have, but it's not setting my defaults to false
if self.default == true
roles = Roles.where( :user_id => self.user_id)
roles.update_all(:default => false)
end
end
def default!
self.default=true
self.save
end
end
class User < ActiveRecord::Base
has_many :roles
has_many :companies, :through => :roles
end
class Company < ActiveRecord::Base
has_many :roles
has_many :users, :through => :roles
end
Code run:
pry(main)> a.roles.last.default!
(0.1ms) begin transaction
SCOPING
SQL (0.4ms) UPDATE "roles" SET "default" = 'f' WHERE "roles"."user_id" = 1
(2.0ms) commit transaction
=> true
[5] pry(main)> a.roles
=> [#<Role id: 1, company_id: 1, name: nil, view_document: nil, edit_document: nil, upload_document: nil, delete_document: nil, review_document: nil, company_info: nil, company_clients: nil, company_users: nil, company_admin: nil, created_at: "2014-08-04 20:10:23", updated_at: "2014-08-04 22:29:40", user_id: 1, default: true>,
#<Role id: 2, company_id: 2, name: nil, view_document: nil, edit_document: nil, upload_document: nil, delete_document: nil, review_document: nil, company_info: nil, company_clients: nil, company_users: nil, company_admin: nil, created_at: "2014-08-04 20:11:10", updated_at: "2014-08-04 20:11:10", user_id: 1, default: nil>,
#<Role id: 3, company_id: 3, name: nil, view_document: nil, edit_document: nil, upload_document: nil, delete_document: nil, review_document: nil, company_info: nil, company_clients: nil, company_users: nil, company_admin: nil, created_at: "2014-08-04 20:11:14", updated_at: "2014-08-04 22:29:16", user_id: 1, default: true>]
As you can see the update ran, but the default values are not set to false. If you have any recommendations or things I can check, please let me know.
Needed to exclude the current record that is being modified. Added the where.not clause
def change_default
if self.default_changed? && self.default == true
roles = Role.where( :user_id => self.user_id)
roles = roles.where.not(:id => self.id)
roles.update_all(:default => false)
end
end
Looks like in the example you never call save on the roles. Therefore, your before_save :change_default is not being executed. Perhaps adding role.save in def default! would at least have the before_save be executed because I'm not sure that your current console test is actually running the change_default method.
If I am wrong and it actually is running, then I'm not sure why your block would not set the default value to false.

How to whitelist dynamic hstore keys in a nested model

I have these relationships:
class Applicant < ActiveRecord::Base
has_many :answers
accepts_nested_attributes_for :answers
end
class Answer < ActiveRecord::Base
belongs_to :applicant
end
The answer model has an hstore attribute called properties. The properties hash will have dynamic keys as they are created in the app by the user.
I can't seem to successfully whitelist these dynamic keys within the applicants controller.
This is my current (unsuccessful) attempt.
def applicant_params
params.require(:applicant).permit(:answers_attributes: [:question_id, :id]).tap do |whitelisted|
whitelisted[:answers_attributes][:properties] = params[:applicant][:answers_attributes][:properties]
end
end
Thanks for any help.
UPD. Try to use following approach (tested in separate file):
#params = ActionController::Parameters.new(
applicant: {answers_attributes: {
"0" => {question_id: 10, id: 110, properties: {a: "b", c: "d"}},
"1" => {question_id: 20, id: 120, properties: {m: "n", o: "p"}}
}})
def applicant_params
#properties should be [:a, :c, :m, :o]
properties = []
#params[:applicant][:answers_attributes].values.each do |answer|
properties |= answer[:properties].keys
end
#params.require(:applicant).permit(answers_attributes:
[:question_id, :id, properties: properties])
end
BTL. There is pretty good article on working with hstores. And some general things on using hstore in Rails 4.