Adding has_and_belong_to_many entity using static method - ruby-on-rails-4

I have two models :
class Candidate < ActiveRecord::Base
has_and_belongs_to_many :skills
end
class Skill < ActiveRecord::Base
has_and_belongs_to_many :candidates
def self.add
find_or_create_by(name:"management")
end
end
The following execution seems to work fine :
>current_candidate.skills.count
=> 0
>current_candidate.skills.find_or_create_by(name:"mathematics")
=> ...
>current_candidate.skills.count
=> 1
But here is what happen when I use the static method add :
>current_candidate.skills.count
=> 0
>current_candidate.skills.add
=> ...
>current_candidate.skills.count
=> 0
The skill management is created, but not binded to my candidate entity.
I don't understand why the static method doesn't consider the current_candidate.
PS: It works well with direct binding (like has_many and belongs_to)

New answer (previous answer deleted):
Your code will not work because you have defined add as a class method of Skill. The class Skill has no relation to candidates. An instance of a Skill does have a relation to candidates.
So to fix this, I think it makes more sense to add an add_skill method on Candidate and remove the one in Skill.
class Candidate < ActiveRecord::Base
has_and_belongs_to_many :skills
def add_skill(skill_name)
skills.find_or_create_by(name: skill_name)
end
end
Usage:
current_candidate.add_skill('a_skill')
As stated in my previous answer, you might want to replace the find_or_create_by call with the bang version find_or_create_by! to catch (validation) errors.

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 polymorphic has_many ignores table_name

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).

geokit-rails getting the route passing close to 2 points

I'm having a route model
class Route < ActiveRecord::Base
has_many :etapes
acts_as_mappable :through => :steps
end
and a step one (that contains lat and lgn)
class Step ActiveRecord::Base
belongs_to :route
acts_as_mappable
end
Actually, I can get the routes passing close to one point with the in_range scope : Route.joins(:steps).in_range(0..15, :origin => [lat, lng]).group(:id)
I'm trying to get the route passing close to 2 steps, acts_as_mappable does not have the scope I need, so I'm wondering what the best way to go ?
Well, to those having similar issue, I did it like this.
Given a hash with 4 points (2 lat and lng);
ids = Route.joins(:steps).in_range(0..15, :origin => [hash[:lat1], hash[:lng1]]).group(:id).map(&:id)
routes = Route.joins(:steps).in_range(0..15, :origin => [hash[:lat2], hash[:lng2]]).group(:id)
routes.select {|route| ids.include? route.id}
I first take the routes in range with my first point, then the routes in range with my second point and I select in my first routes only the one in the second routes.

Where should I place bestseller? method - in model or somewhere else?

I've got some simple model:
class Product < ActiveRecord::Base
has_many :categories
end
Now I would like to check in some service, if product is a bestseller and do other action for it:
class ProductService
def remind
Product.all.each do |product|
puts product unless bestseller?
end
end
end
So now what is the best place to put the bestseller? method - inside model or in the service as private method?
In future it may be used in some other services or actions.
Do you think the model is right place to put this method there?
Example of bestsellers method (bestsellers are picked manualy by adding to category 'bestsellers':
def bestseller?(product)
product.categories.include?(BESTSELLER_CATEGORY_ID)
end
or
def bestseller?(product_id)
Category.find(BESTSELLER_CATEGORY_ID).products.include?(product_id)
end
I still haven't decided which one is better (both do the same)

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).