From inside edit action in the contacts controller have ...
#programs << #contact.program
Which produces the following error:
NoMethodError - undefined method `<<' for Program::ActiveRecord_Relation
Contacts Model:
belongs_to :program
Program Model:
has_many :contacts
validates :name, presence: true, uniqueness: true
#programs.class
Program::ActiveRecord_Relation
#contact.program.class
Program(id: integer, name: string, active: boolean, created_at: datetime, updated_at: datetime)
Question: Why does this operation fail? Why can't the record get added to the record collection. What is preventing the collection(ActiveRecord_Relation) from adding the record?
You're contradicting yourself here:
Program has_many contacts vs Programs << Contact.program
If you're trying to add a Contact to a particular program, you would be looking at adding the contact:
program.contacts << contact
And if you're trying to set the program for the contact:
contact.program = program
What does not make sense, however, is to try to add something to “programs”, which isn't a relationship. Nothing in this system as you've defined it has_many :programs, so #programs.<< cannot possibly act on a relationship.
You're receiving this error because the ActiveRecord::Relation class is only a collection of results returned by an ActiveRecord query. You probably got it by running Program.where or a similar query. It is not an ActiveRecord::Association and therefore you cannot add more records to it.
You must instead use the association returned by the parent object.
Here's an example of what you're doing, vs what you should be doing:
class User < ApplicationRecord
has_many :programs
end
class Program < ApplicationRecord
belongs_to :user
end
new_program = Program.new
# What you're attempting.
programs_where = Program.where(user_id: User.first) # Class is Program::ActiveRecord_Relation
programs_where << new_program # Throws Error b/c << is not available on ActiveRecord::Relation objects.
# What you should be attempting.
user = User.first
programs_assoc = user.programs # Returns Programs::ActiveRecord_Associations_CollectionProxy
programs_assoc << new_program # Returns Correctly
Note: It's not clear how #programs is defined. Is this answer does not work for you then please provide the complete controller code, as well as the other model you're using.
Related
I have two models: Users and PaymentMethods, the association between this models is:
class User < ApplicationRecord
has_many :payment_methods, dependent: :destroy
end
class PaymentMethod < ApplicationRecord
belongs_to :user, optional: true
end
I want to loop in each user and see in an attribute of PaymentMethod, named 'period_end_date'. so I do this:
#users = User.all
#users.each do |u|
u.payment_methods.last.period_end_date
end
I'm getting this error => NoMethodError: undefined method `payment_methods' for User::ActiveRecord_Relation
The error is shown because I have 2 test users, in the first user there is still no data in the attribute 'period_end_date' and association exist, but is empty, in the second user there is data in the attributes, if I say, u.payment_methods.last.period_end_date I get => Wed, 13 Jun 2018 (only in the second user)
I want to filter in my loop only the users who has data in PaymentMethod attributes for get rid of => NoMethodError: undefined method `payment_methods' for User::ActiveRecord_Relation
How I do this?
thanks
I want to filter in my loop only the users who has data in PaymentMethod attributes for get rid of => NoMethodError: undefined method `payment_methods' for User::ActiveRecord_Relation
The actual problem seems to be you have users without payment methods (see my comment on your question).
You have some options, depending on how you're going to use the results.
1) You can filter out users without payment methods when you query them from the database like this:
#users = User.joins :payment_methods
2) If #users must include users that without payment methods, you can skip them when looping like this:
#users.map do |user|
next unless user.payment_methods.any?
user.payment_methods.last.period_end_date
end
3) You can guard by checking for payment_methods before calling .last.
User.all.map do |user|
user.payment_methods.last.period_end_date if user.payment_methods.any?
end
4) You can add a period_end_date method to the user
class User < ApplicationRecord
def period_end_date
payment_methods.limit(1).pluck :period_end_date
end
end
5) push #4 into the association by extending it with a helper method
class User < ApplicationRecord
has_many :payment_methods, class_name: 'PaymentMethod' do
def last_period_end_date
last.period_end_date if any?
end
end
end
which you can call like this
User.all.map do |user|
user.payment_methods.last_period_end_date
end
If you're really only concerned about PaymentMethods without a period_end_date then try this:
6) You can still filter users when you query them from the database
#users = User.joins(:payment_methods).where.not(payment_methods: { period_end_date: nil })
7) This can be simplified a bit by pushing the where.not conditions into a scope of the PaymentMethod class:
class PaymentMethod < ApplicationRecord
scope :period_ends, -> { where.not period_end_date: nil }
end
and merging it
#users = User.joins(:payment_methods).merge PaymentMethod.period_ends
Notes
payment_methods.last doesn't specify an order, you should set one (either as part of this chain, when you specify the association, or with a default scope) otherwise the order is up to your database and may be indeterminate.
chain .includes(:payment_methods) to eager load the payment methods and avoid n+1 queries
it sounds like a nil period_end_date could be invalid data. Consider adding a validation / database constraint to prevent this from happening
Is it possible to, within the record found through an association, retain access to the related model instance which found it?
Example:
class Person < ApplicationRecord
has_many :assignments
attr_accessor :info_of_the_moment
end
p = Person.first
p.info_of_the_moment = "I don't want this in the db"
assignment = p.assignments.first
assignment.somehow_get_p.info_of_the_moment # or some such magic!
And/or is there a way to "hang on to" the parameters of a scope and have access to them from within the found model instance? Like:
class Person < ApplicationRecord
has_many :assignments
attr_accessor :info_of_the_moment
scope :fun_assignments, -> (info) { where(fun: true) }
end
class Assignment < ApplicationRecord
belongs_to :person
def get_original_info
# When I was found, info was passed into the scope. What was it?
end
end
You can add your own extension methods to an association and those methods can get at the association's owner through proxy_association:
has_many :things do
def m
# Look at proxy_association.owner in here
end
end
So you could say things like:
class Person < ApplicationRecord
has_many :assignments do
def with_info
info = proxy_association.owner.info_of_the_moment
# Then we wave our hands and some magic happens to encode
# `info` into a properly escaped SQL literal that we can
# toss in a `select` call. If you're working with PostgreSQL
# then JSON would be a reasonable first choice if the info
# was, say, a hash.
#
# The `::jsonb` in the `select` call is there to tell everyone
# that the `info_of_the_moment` column is JSON and should be
# decoded as such by ActiveRecord.
encoded_info = ApplicationRecord.connection.quote(info.to_json)
select("assignments.*, #{encoded_info}::jsonb as info_of_the_moment")
end
end
#...
end
p = Person.first
p.info_of_the_moment = { 'some hash' => 'that does', 'not go in' => 'the database' }
assignment = p.assignments.with_info.first
assignment.info_of_the_moment # And out comes the hash but with stringified keys regardless of the original format.
# These will also include the `info_of_the_moment`
p.assignments.where(...).with_info
p.assignments.with_info.where(...)
Things of note:
All the columns in select show up as methods even when they're not part of the table in question.
You can add "extension" methods to an association by including a block with those methods when calling the association's method.
An SQL SELECT can include values that aren't columns, literals work just fine.
What format you use to tunnel your extra information through the association depends on the underlying database.
If the encoded extra information is large then this can get expensive.
This is admittedly a bit kludgey and brittle so I'd agree with you that rethinking your whole approach is a better idea.
I have two different objects which can belong to one parent object. These child objects can both also belong to each other (many to many). What's the best way to ensure that child objects which belong to each other also belong to the same parent object.
As an example of what I'm trying to do I have a Kingdom which has both many People and Land. The People model would have a custom validate which checks each related Land and error.adds if one has a mismatched kingdom_id. The Land model would have a similar validate.
This seems to work, but when updating it allows the record to save the 'THIS IS AN ERROR' error is in people.errors, however the Land which raised the error has been added to the People collection.
kingdom = Kingdom.create
people = People.create(:kingdom => kingdom)
land = Land.create(:kingdom_id => 999)
people.lands << land
people.save
puts people.errors.inspect # #messages={:base=>["THIS IS AN ERROR"]
puts people.lands.inspect # [#<Land id: 1...
Ideally I'd want the error to cancel the record update. Is there another way I should be going about this, or am I going in the wrong direction entirely?
# Models:
class Kingdom < ActiveRecord::Base
has_many :people
has_many :lands
end
class People < ActiveRecord::Base
belongs_to :kingdom
has_and_belongs_to_many :lands
validates :kingdom_id, :presence => true
validates :kingdom, :associated => true
validate :same_kingdom?
private
def same_kingdom?
if self.lands.any?
errors.add(:base, 'THIS IS AN ERROR') unless kingdom_match
end
end
def kingdom_match
self.lands.each do |l|
if l.kingdom_id != self.kingdom_id
return false
end
end
end
end
class Land < ActiveRecord::Base
belongs_to :kingdom
has_and_belongs_to_many :people
end
Firstly, the validation won't prevent the record from being added to the model's unpersisted collection. It will prevent the revised collection from being persisted to the database. So the model will be in an invalid state, and flagged as such with the appropriate errors. To see this, you can simply reload the people object.
You also have an error in your logic - the kingdom_match method will never return true even if no invalid kingdom_id's are found. You should add a line to fix this:
def kingdom_match
self.lands.each do |l|
return false if l.kingdom_id != self.kingdom_id
end
true
end
And you can make this validation a bit more concise and skip the kingdom_match method entirely:
def same_kingdom?
if self.lands.any?{|l| l.kingdom_id != self.kingdom_id }
errors.add(:base, 'THIS IS AN ERROR')
end
end
I have a model with a belongs to relationship.
class Product < ActiveRecord::Base
attr_accessible :name, :price, :request_id, :url
# Relationships
belongs_to :request
end
class Request < ActiveRecord::Base
attr_accessible :category, :keyword
# Relationships
has_many :products
end
This is the code in my controller function
product = Product.where({ :asin => asin }).first
# See if the product exists
begin
#This throws a method not found error for where
product = Product.where({ :name => name }).first
rescue
Product.new
# This throws a method not found error for request_id
product.request_id = request.id
product.save
end
I'm trying to create a new product object like so
product = Product.first(:conditions => { :name => name })
When I call that I get an error saying undefined method 'first' for Product:Class
I tried doing Product.new and I can't access any attributes. I get this for every one undefined method 'request_id=' for #<Product:0x007ffce89aa7f8>
I've been able to save request objects. What am I doing wrong with products?
EDIT:
So as it turns out there was an old Product data type that was being imported that wasn't an ActiveRecord class. It was using that instead of my Product::ActiveRecord. I deleted that import and it's good to go. Sorry to have wasted everybody's time.
Not sure what the proper protocol is here for what to do with this question.
Is your Product class an ActiveRecord::Base class? You can find out by running:
Product.ancestors.include?(ActiveRecord::Base)
If this returns false, it's getting the class loaded from somewhere else.
First check to see that your Product class is set up correctly by typing in:
rails c
# after console has loaded
Product
If this looks correct then we will try to instantiate a product by calling:
# Create a new product
product = Product.new(name: "first product", price: 100, url: "http://www.example.com")
# Persist this object to the database
product.save
If you are missing any attributes run another migration to add them to the Product table.
If none of those suggestions work, check to make sure that there isn't an existing class with the same name in your project. This would cause all kinds of errors and would explain certain methods not being found.
I implemented an example of Reverse Polymorphism in Rails with the selected answer from this question:
Reverse Polymorphic Associations
With this we are able to do the following:
t = Article.new
t.article_elements # []
p = Picture.new
t.article_elements.create(:element => p)
t.article_elements # [<ArticleElement id: 1, article_id: 1, element_id: 1, element_type: "Picture", created_at: "2011-09-26 18:26:45", updated_at: "2011-09-26 18:26:45">]
t.pictures # [#<Picture id: 1, created_at: "2011-09-26 18:26:45", updated_at: "2011-09-26 18:26:45">]
I'm wondering if it's possible to modify this such that if I do
t.article_elements that I can also see the attributes for the picture to. So for example, if I had an picture_name attribute for the variable p, how can I access that from t.article_elements ? So basically, I am trying to access the parent's attributes from the child object.
Note that t.article_elements is a collection. I will use article_element to refer to one member of the collection.
Per your example,
article_element.element.picture_name
will work.
However, you run into a problem with undefined methods by mismatched attributes. For example, a video would not have a picture_name attribute. If all element types shared a common attribute, such as name, it would be fine.
One way to avoid this problem is to check whether the element responds to a given attribute method. For example:
# models/article_element.rb
def element_try(method)
self.element.respond_to?(method) ? self.element.send(method) : ""
end
If our element is a video and we call:
article_element.element_try(:picture_name) # => ""
we will get a blank string instead of NoMethodError.
This solution is a bit hacky, so use at your own risk. Personally, I'd use common attributes instead.
I had similar situation.
class User < ActiveRecord::Base
has_one :image, as: :imageable
end
class Person < ActiveRecord::Base
has_one :image, as: :imageable
end
class Image < ActiveRecord::Base
belongs_to :imageable, :polymorphic => true
end
When you wanna access parent of image, you can do Image.last.imageable and that will give you either User or Person object. It works same with has_many relations.