I'm having a problem with some associations in Rails3.
I have the following associations
InformationCategory :has_many => Informations :has_many => InformationValues
I'm able to succesfully do the following:
Information.first.information_values
=> [#InformationValue..]
I'm also able to do the following:
InformationCategory.first.informations
=> [#Information...]
However, for some reason, this fails:
InformationCategory.first.informations.first.information_values
=> NoMethodError: undefined method `information_values' for #<Information:0x000001053321c8>
Why can't I use 'nested associations' in Rails? The error message clearly states that InformationCategory.first.informations.first returns an instance of Information
Am I doing something wrong?
Thanks in advance!
You don't define all the nested descendants on outermost model: each model defines what it "directly" has_many of, or the model it belongs_to. Because your question is wrong, I can only guess without seeing more specifically how your models are supposed to be related.
This might be a start:
class InformationCategory < ActiveRecord::Base
has_many :informations
end
class Information < ActiveRecord::Base
belongs_to :information_category
has_many :information_values
end
class InformationValue < ActiveRecord::Base
belongs_to :information
end
However, you might be trying to do a has_many :through, but I can't tell from your question.
Related
Forgive me if this has already been asked (as I believe it has), but I couldn't find this exact issue (and it's very likely I'm not searching properly).
I have the following models:
class Company < ActiveRecord::Base
has_many :jobs
end
class Job < ActiveRecord::Base
belongs_to :company
has_and_belongs_to_many :tags
end
class Tag < ActiveRecord::Base
has_and_belongs_to_many :jobs
end
What I'm trying to accomplish is a list of tags by company, but I'm failing to figure out how (I should note that I'm pretty new to Ruby and Rails). I come from a .NET background, and with Linq I'd use something like Company.Jobs.SelectMany(j => j.Tags).
I tried to do Company.first.jobs.tags, which fails with NoMethodError: undefined method 'tags' for #<Job::ActiveRecord_Associations_CollectionProxy:0x892dca0>, but strangely enough, if I run Company.first.jobs.instance_methods on a rails console, there is a :tags method. And this is what I get when I use the console's autocomplete:
Any suggestions?
Thanks.
What you are looking for is called 'has many through'.
class Company < ActiveRecord::Base
has_many :jobs
has_many :tags, through: :jobs
end
This is how you can go through an association (in this case jobs) to get to that association's association.
This way you can run company.tags on an instance of a company.
The problem with your existing approach is that jobs is a collection, but you need to hit the method on individual instances. This would work with no adjustment:
Company.first.jobs.collect { |job| job.tags }
I'm pretty new to Rails and setting up associations, so I suspect I'm missing something pretty obvious. I'm trying to set up an app where one model has two models that it has_many of. The second model belongs_to the first and has_many of the third. And the third can either belong to the first or the second model.
Specifically, I have a wall model that holds pictures and collages. The wall can hold either pictures or collages or neither. Collages can hold pictures.
class Wall < ActiveRecord::Base
belongs_to :user
has_many :collages
has_many :pictures
end
class Collage < ActiveRecord::Base
belongs_to :user
belongs_to :wall
has_many :pictures
end
class Picture < ActiveRecord::Base
belongs_to :user
belongs_to :wall
belongs_to :collage
end
The error I'm getting is telling me:
undefined method `picture?' for #Wall
Is there something I'm doing wrong with the associations I'm creating?
has_many association on any model gives plural form of that method
Therefore Wall class has method #pictures available by this line:
If you want #picture method to be available you should use association as belongs_to
We can debug more into the exact problem if you tell where actually you are getting this error and what is your feature to implement.
Also name for Picture class should be with capital P
#cvibha's answer should help you with the associations
However, there's another problem you may need to consider. You're calling this method:
undefined method `picture?' for #Wall
Rails associations basically create a record as per how you define the association (has_many :pictures creates #wall.pictures). However, you're calling picture?
--
If you've got a custom method called picture?, this should work (albeit without the association working - as described in the other answer). The problem you have is I don't think you've defined picture?
I would do this:
#app/models/wall.rb
Class Wall < ActiveRecord::Base
...
def picture?
#your code
end
end
Alternatively, if you're looking to validate the existence of a picture, you may wish to use in your view:
#wall.pictures.any?
#wall.pictures.first.present?
I have 3 simple models:
class User < ActiveRecord::Base
has_many :subscriptions
end
class Product < ActiveRecord::Base
has_many :subscriptions
end
class Subscription < ActiveRecord::Base
belongs_to :user
belongs_to :product
end
I can do a_subscription.product = a_product and AR knows I mean product_id and everything works fine.
But If i do:
Subscription.where :product => a_product
It throws an error at me Unknown column 'subscriptions.product' - It knows in the first case that I mean product_id but it doesn't in the latter. I am just wondering if this is how it is suppose to be or am I missing something? I can get it to work by saying
Subscription.where :product_id => a_product
by do I have to specify _id?
Yes, right now you can't pass association to the where method. But you'll be able to do it in Rails 4. Here is a commit with this feature.
I don't think there's an elegant way around that (as of now, see #nash 's answer). However, if you have an instance of a_product and it has has_many on subscriptions, why not just turn it around and say:
subscriptions = a_product.subscriptions
Setup
For this question, I'll use the following three classes:
class SolarSystem < ActiveRecord::Base
has_many :planets
scope :has_earthlike_planet, joins(:planets).merge(Planet.like_earth)
end
class Planet < ActiveRecord::Base
belongs_to :solar_system
belongs_to :planet_type
scope :like_earth, joins(:planet_type).where(:planet_types => {:life => true, :gravity => 9.8})
end
class PlanetType < ActiveRecord::Base
has_many :planets
attr_accessible :gravity, :life
end
Problem
The scope has_earthlike_planet does not work. It gives me the following error:
ActiveRecord::ConfigurationError: Association named 'planet_type' was
not found; perhaps you misspelled it?
Question
I have found out that this is because it is equivalent to the following:
joins(:planets, :planet_type)...
and SolarSystem does not have a planet_type association. I'd like to use the like_earth scope on Planet, the has_earthlike_planet on SolarSystem, and would like to avoid duplicating code and conditions. Is there a way to merge these scopes like I'm attempting to do but am missing a piece? If not, what other techniques can I use to accomplish these goals?
Apparently, at this time you can only merge simple constructs that don't involve joins. Here is a possible workaround if you modify your models to look like this:
class SolarSystem < ActiveRecord::Base
has_many :planets
has_many :planet_types, :through => :planets
scope :has_earthlike_planet, joins(:planet_types).merge(PlanetType.like_earth)
end
class Planet < ActiveRecord::Base
belongs_to :solar_system
belongs_to :planet_type
scope :like_earth, joins(:planet_type).merge(PlanetType.like_earth)
end
class PlanetType < ActiveRecord::Base
has_many :planets
attr_accessible :gravity, :life
scope :like_earth, where(:life => true, :gravity => 9.8)
end
** UPDATE **
For the record, a bug was filed about this behavior - hopefully will be fixed soon...
You are reusing the conditions from the scope Planet.like_earth, which joins planet_type. When these conditions are merged, the planet_type association is being called on SolarSystem, which doesn't exist.
A SolarSystem has many planet_types through planets, but this is still not the right association name, since it is pluralized. You can add the following to the SolarSystem class to setup the planet_type association, which is just an alias for planet_types. You can't use the Ruby alias however since AREL reflects on the association macros, and doesn't query on whether the model responds to a method by that name:
class SolarSystem < ActiveRecord::Base
has_many :planets
has_many :planet_types, :through => :planets
has_many :planet_type, :through => :planets, :class_name => 'PlanetType'
scope :has_earthlike_planet, joins(:planets).merge(Planet.like_earth)
end
SolarSystem.has_earthlike_planet.to_sql # => SELECT "solar_systems".* FROM "solar_systems" INNER JOIN "planets" ON "planets"."solar_system_id" = "solar_systems"."id" INNER JOIN "planets" "planet_types_solar_systems_join" ON "solar_systems"."id" = "planet_types_solar_systems_join"."solar_system_id" INNER JOIN "planet_types" ON "planet_types"."id" = "planet_types_solar_systems_join"."planet_type_id" WHERE "planet_types"."life" = 't' AND "planet_types"."gravity" = 9.8
An easy solution that I found is that you can change your joins in your Planet class to
joins(Planet.joins(:planet_type).join_sql)
This will create an SQL string for the joins which will always include the correct table names and therefore should always be working no matter if you call the scope directly or use it in a merge. It's not that nice looking and may be a bit of a hack, but it's only a little more code and there's no need to change your associations.
How do I destroy the association itself and leave the objects being associated alone, while keeping this RESTful?
Specifically, I have these models:
class Event < ActiveRecord::Base
has_many :model_surveys, :as => :surveyable, :dependent => :destroy, :include => :survey
has_many :surveys, :through => :model_surveys
end
class ModelSurvey < ActiveRecord::Base
belongs_to :survey
belongs_to :surveyable, :polymorphic => true
end
class Survey < ActiveRecord::Base
has_many :model_surveys
end
That's saying that the Event is :surveyable (ModelSurvey belongs_to Event). My question is, without having to create a ModelSurveysController, how do I destroy the ModelSurvey, while leaving the Event and Survey alone?
Something with map.resources :events, :has_many => :model_surveys? I'm not quite sure what to do in this situation. What needs to happen with the routes, and what needs to happen in the controller? I'm hoping the url could look something like this:
/events/:title/model_surveys/:id
Thanks for your help,
Lance
In Rails 2.3 you have accepts_nested_attributes_for which would let you pass an array of ModelSurveys to the event in question. If you allow destroy through the nested attributes declaration, you'll be able to pass event[model_surveys][1][_destroy]=1 and the association will be removed. Check out the api docs.
Resources domain != model domain
The domain of the controller is not the same as that of the models. It's perfectly fine to update multiple models by changing the state of a resource.
In your case that means doing a PUT or POST to either the Event or the Survey which contains a list of ids for the other. The model for one will update the association.
PUT or POST
Some people (but not Roy Fielding) believe that you should use a PUT to update the resource and provide all of the state again, others feel that a POST with the partial state (ala PATCH) is sufficient.