Destroy associated object from rails console - ruby-on-rails

I am getting nil:NilClass errors, so I diagnosed the problem on rails c, see below:
irb(main):016:0> a = Product.find(216)
=> #<Product id: 216, ....>
irb(main):017:0> a.related_products
RelatedProduct Load (1.4ms) SELECT "related_products".* FROM "related_products" WHERE "related_products"."product_id" = 216
=> [#<RelatedProduct id: 162, product_id: 216, related_id: 248, created_at: "2013-08-20 15:37:03", updated_at: "2013-08-20 15:37:03">]
The problem here is related_id: 248 doesn't exist and confirmed it on the console:
Product.find(248)
I get:
SELECT "products".* FROM "products" WHERE "products"."id" = $1 LIMIT 1 [["id", 248]]
ActiveRecord::RecordNotFound: Couldn't find Product with id=248
My question is:
1) How do I delete: a.related_products on rails console, I tried a.related_products.destroy and destroy!
Bonus: is there a way to delete all Products that has empty related_products from rails console with one command?
Thank you in advance for your help!

You should be able to do something like
a.related_products.destroy_all
This will destroy ALL of the related products in the list.
Product.destroy_all(related_products: nil)
This will loop over all products and look to see if the related_products field is empty.
You may also want to setup dependent destroys. Something like
class MyModel < ActiveRecord::Base
has_many :related_products, dependent: :destroy
end

To delete all products where related_products are empty check destroy_all Product.destroy_all(related_products: nil)
Also check the dependent: :destroy in your model.

Related

Rails association= return value and behavior

The guide does not say what return value would be for association= methods. For example the has_one association=
For the simple case, it returns the assigned object. However this is only when assignment succeeds.
Sometimes association= would persist the change in database immediately, for example a persisted record setting the has_one association.
How does association= react to assignment failure? (Can I tell if it fails?)
Is there a bang! version in which failure raises exception?
How does association= react to assignment failure? (Can I tell if it fails?)
It can't fail. Whatever you assign, it will either work as expected:
Behind the scenes, this means extracting the primary key from this
object and setting the associated object's foreign key to the same
value.
or will save the association as a string representation of passed in object, if the object is "invalid".
Is there a bang! version in which failure raises exception?
Nope, there is not.
The association= should not be able to fail. It is a simple assignment to a attribute on your attribute. There are no validations called by this method and the connection doesn't get persisted in the database until you call save.
The return value of assignments is the value you pass to it.
http://guides.rubyonrails.org/association_basics.html#has-one-association-reference-when-are-objects-saved-questionmark
So another part of the guide does talk about the return behavior for association assignment.
If association assignment fails, it returns false.
There is no bang version of this.
Update
Behaviors around :has_many/has_one through seems to be different.
Demo repository: https://github.com/lulalalalistia/association-assignment-demo
In the demo I seeded some data in first commit, and hard code validation error in second commit. Demo is using rails 4.2
has_many through
class Boss < ActiveRecord::Base
has_many :room_ownerships, as: :owner
has_many :rooms, through: :room_ownerships
end
When I add a room, exception is raised:
irb(main):008:0> b.rooms << Room.first
Boss Load (0.2ms) SELECT "bosses".* FROM "bosses" ORDER BY "bosses"."id" ASC LIMIT 1
Room Load (0.1ms) SELECT "rooms".* FROM "rooms" ORDER BY "rooms"."id" ASC LIMIT 1
(0.1ms) begin transaction
(0.1ms) rollback transaction
ActiveRecord::RecordInvalid: Validation failed: foo
irb(main):014:0> b.rooms
=> #<ActiveRecord::Associations::CollectionProxy []>
has_one through
class Employee < ActiveRecord::Base
has_one :room_ownership, as: :owner
has_one :room, through: :room_ownership
end
When I add a room I don't get exception:
irb(main):021:0> e.room = Room.first
Room Load (0.2ms) SELECT "rooms".* FROM "rooms" ORDER BY "rooms"."id" ASC LIMIT 1
RoomOwnership Load (0.1ms) SELECT "room_ownerships".* FROM "room_ownerships" WHERE "room_ownerships"."owner_id" = ? AND "room_ownerships"."owner_type" = ? LIMIT 1 [["owner_id", 1], ["owner_type", "Employee"]]
(0.1ms) begin transaction
(0.1ms) rollback transaction
=> #<Room id: 1, created_at: "2016-10-03 02:32:33", updated_at: "2016-10-03 02:32:33">
irb(main):022:0> e.room
=> #<Room id: 1, created_at: "2016-10-03 02:32:33", updated_at: "2016-10-03 02:32:33">
This makes it difficult to see whether the assignment succeeds or not.

Strange has_many Association Behavior in Rails 4

I've got two tables, User and Allergy. These are connected via another table, UserAllergy. The models are as would be expected:
class User
has_many :user_allergies
has_many :allergies, through: :user_allergies
end
class UserAllergy
belongs_to :user
belongs_to :allergy
end
class Allergy
has_many :user_allergies
has_many :users, through :user_allergies
end
What I'm confused about is creating allergies from a multiple-valued collection_select in my User form.
I have the following field:
<%= f.collection_select :allergy_ids,
Allergy.all,
:id,
:name,
{},
{ class: 'form-control', multiple: true }
%>
This correctly inserts a key into my params like so if I selected the Allergies with ids 1 and 2:
{ user: { id: "1", allergy_ids: ["", "1", "2"] } }
When I create the user instantiated with #user = User.new( my_params ), the weird behavior occurs. Instead of inserting the provided allergy_ids into the join table, Rails does a query to get all current user_allergies for the user, then deletes all of the current user_allergies:
Started PATCH "/employees/regular_user" for 127.0.0.1 at 2015-06-18 22:08:30 -0400
Processing by UsersController#update as HTML
Parameters: {"utf8"=>"✓", "user"=>{ "allergy_ids"=>["", "1", "2", "3"]}, "button"=>"", "id"=>"regular_user"}
CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 2]]
(0.1ms) begin transaction
Allergy Load (0.1ms) SELECT "allergies".* FROM "allergies" INNER JOIN "user_allergies" ON "allergies"."id" = "user_allergies"."allergy_id" WHERE "user_allergies"."user_id" = ? [["user_id", 1]]
SQL (0.1ms) DELETE FROM "user_allergies" WHERE "user_allergies"."user_id" = ? AND "user_allergies"."allergy_id" = 1 [["user_id", 1]]
(27.4ms) commit transaction
Redirected to http://localhost:3000/employees/regular_user
Completed 302 Found in 32ms (ActiveRecord: 27.8ms)
Anyone knows what gives, or what I need to do to create allergies implicitly? I've tried accepts_nested_attributes_for and changing around the form to use fields_for.
So, I went and looked at code of mine that does a similar function. Here's what my create method looks like. This is creating a Student with assignment to Student Groups in a school setting (I didn't use "class" since Ruby wouldn't like that).
def create
#student = Student.new(student_params)
if #student.save
#student.student_groups = StudentGroup.where(id: params[:student][:student_group_ids])
flash[:success] = "Student was successfully created."
redirect_to #student
else
render 'new', notice: "Your student could not be created."
end
end
I completely ignore the Student Group IDs when creating the student_params, since I'm not using them for mass assignment.
Yes, one extra line of code. I'd be really interested to hear if there's a way to accomplish this via mass assignment.
You're missing one part of the puzzle which is the relation from Allergy to User.
class Allergy
has_many :user_allergies
has_many :users, through: :user_allergies
end
Just give the following code a try-
params.require(:user).permit(___, ____, {allergy_ids: []}, ____, ____)

How does .first work on a CollectionProxy in Rails?

I have a couple models set up like this:
class Contract < ActiveRecord::Base
has_many :invoices, dependent: :destroy
end
class Invoice < ActiveRecord::Base
belongs_to :contract
end
I have a feature test set up like this...
feature "Some cool functionality", js: true do
let(:contract) { create(:contract) }
let(:invoice) { create(:invoice, contract: contract) }
#etc...
end
While debugging the test I noticed this...
(byebug) p contract
#<Contract id: 1, created_at: "2014-02-25 01:52:52", updated_at: "2014-02-25 01:52:52">
(byebug) p invoice
#<Invoice id: 1, contract_id: 1, created_at: "2014-02-25 01:52:52", updated_at: "2014-02-25 01:52:52">
Here's the confusing part:
(byebug) p contract.invoices.first
nil
I thought that would return my invoice defined in my feature test.
However, I think I can verify that contract has one invoice...
(byebug) p contract.invoices.count
(1.0ms) SELECT COUNT(*) FROM "invoices" WHERE "invoices"."contract_id" = $1 [["contract_id", 1]]
1
What's going on here?
Try calling contract.reload
When you call let the value of the variable/method is cached after the first time it is invoked. So when you call contract.invoices.first you are invoking invoices on the cached contract object currently in memory.
To ensure you're using the most up-to-date data, try using Rails' reload! console method:
# from command line
reload!

is there a way to eager load polymorphic association's associations?

artists have many activities (basically a cache of interactions between users):
class Activity < ActiveRecord::Base
belongs_to :receiver, :class_name => 'Artist', :foreign_key => :receiver_id #owns the stuff done "TO" him
belongs_to :link, :polymorphic => true
belongs_to :creator, :class_name => 'Artist', :foreign_key => :creator_id #person who initiated the activity
end
For example:
Activity.create(:receiver_id => author_id, :creator_id => artist_id, :link_id => id, :link_type => 'ArtRating')
I want to create an activity stream page for each artist, consisting of a list of different types of events, ArtRatings (likes, dislikes), Favoriting, Following etc.
The controller looks like this:
class ActivityStreamController < ApplicationController
def index
#activities = #artist.activities.includes([:link,:creator,:receiver]).order("id DESC").limit(30)
end
end
The db call correctly eagerly loads the polymorphic link objects:
SELECT "activities".* FROM "activities" WHERE (("activities"."receiver_id" = 6 OR "activities"."creator_id" = 6)) ORDER BY id DESC LIMIT 30
ArtRating Load (0.5ms) SELECT "art_ratings".* FROM "art_ratings" WHERE "art_ratings"."id" IN (137, 136, 133, 130, 126, 125, 114, 104, 103, 95, 85, 80, 73, 64)
SELECT "follows".* FROM "follows" WHERE "follows"."id" IN (14, 10)
SELECT "favorites".* FROM "favorites" WHERE "favorites"."id" IN (25, 16, 14)
But when I display each ArtRating, I also need to reference the post title, which belongs to a post. In the view, if I do:
activity.link.post
It does a separate DB call for each art_rating's post. Is there a way to eagerly load the post as well?
UPDATE TO THE QUESTION:
If there is no way to achieve eager loading of posts using 'includes' syntax, is there a way to manually do the eager loading query myself and inject it into the #activities object?
I see in the DB log:
SELECT "art_ratings".* FROM "art_ratings" WHERE "art_ratings"."id" IN (137, 136, 133, 130, 126, 125, 114, 104, 103, 95, 85, 80, 73, 64)
Is there a way I can access this list of ids from the #activities object? If so, I could do 2 additional queries, 1 to get the art_ratings.post_id(s) in that list, and another to SELECT all posts IN those list of post_ids. Then somehow inject the 'post' results back into #activities so that it's available as activity.link.post when I iterate through the collection. Possible?
TL;DR my solution makes artist.created_activities.includes(:link) eager load everything you want
Here's my first attempt at it: https://github.com/JohnAmican/music
A few notes:
I'm relying on default_scope, so this isn't optimal.
It looks like you're using STI. My solution doesn't. That means you can't simply call activities on an artist; you have to reference created_activities or received_activities. There might be a way around this. I'll update if I find anything.
I changed some names around because it was confusing to me otherwise.
If you go into console and do created_activities.includes(:link), the appropriate stuff gets eager-loaded:
irb(main):018:0> artist.created_activities.includes(:link)
Activity Load (0.2ms) SELECT "activities".* FROM "activities" WHERE "activities"."creator_id" = ? [["creator_id", 1]]
Rating Load (0.3ms) SELECT "ratings".* FROM "ratings" WHERE "ratings"."id" IN (1)
RatingExplanation Load (0.3ms) SELECT "rating_explanations".* FROM "rating_explanations" WHERE "rating_explanations"."rating_id" IN (1)
Following Load (0.3ms) SELECT "followings".* FROM "followings" WHERE "followings"."id" IN (1)
Favorite Load (0.2ms) SELECT "favorites".* FROM "favorites" WHERE "favorites"."id" IN (1)
=> #<ActiveRecord::Relation [#<Activity id: 1, receiver_id: 2, creator_id: 1, link_id: 1, link_type: "Rating", created_at: "2013-10-31 02:36:27", updated_at: "2013-10-31 02:36:27">, #<Activity id: 2, receiver_id: 2, creator_id: 1, link_id: 1, link_type: "Following", created_at: "2013-10-31 02:36:41", updated_at: "2013-10-31 02:36:41">, #<Activity id: 3, receiver_id: 2, creator_id: 1, link_id: 1, link_type: "Favorite", created_at: "2013-10-31 02:37:04", updated_at: "2013-10-31 02:37:04">]>
At the very least, this proves that Rails has the ability to do this. Circumventing default_scope seems like an issue with telling Rails what you want to do rather than a technical limitation.
UPDATE:
Turns out that when you pass a scope block to an association and call that association, self within that block refers to the relation. So, you can reflect on it and act appropriately:
class Activity < ActiveRecord::Base
belongs_to :creator, class_name: 'Artist', foreign_key: :creator_id, inverse_of: :created_activities
belongs_to :receiver, class_name: 'Artist', foreign_key: :receiver_id, inverse_of: :received_activities
belongs_to :link, -> { self.reflections[:activity].active_record == Rating ? includes(:rating_explanation) : scoped }, polymorphic: true
end
I updated my code to reflect (haha) this.
This can be cleaned up. For example, maybe you don't always want to eager load rating_explanations when accessing activity links. There are a number of ways to solve that. I could post one if you'd like.
But, I think the most important thing that this shows is that within the association's scope block, you have access to the ActiveRecord::Relation being built. This will allow you to do things conditionally to it.
Try this:
#artist.activities.includes([{:link => :post},:creator,:receiver])...
See the Rails docs for more.
If I get this right, you not only want to load a polymorphic association's association, but a polymorphic association's polymorphic association.
This basically means joining one defined table with another defined table through a bunch of undefined tables that come from some fields in the database.
Since both the activity and the post are joined through a polymorphic object they both have a something_id and something_type column.
Now I think active record doesn't let you do this out of the box but basically you want something like:
class Activity
has_one :post, :primary_key => [:link_id, :link_type], :foreign_key => [:postable_id, :postable_type]
end
(assuming your polymorphic association on Post is belongs_to :postable)
That would then give you a query sort-of direct association between Post and Activity that is a very weird take on habtm with a 'polymorphic join table' (I just made that term up). Because both share the same polymorphicly associated object, they can be connected. Sort of like a friends-of-my-friends thing.
CAVEAT: Like I said, as far as I know, AR doesn't let you do this out of the box (though it would be awesome) but there are some gems that give you composite foreign and/or primary keys. Maybe you can find one to help you solve your problem in this way.
Given your update to the question (that this isn't feasible using AR's include), you could try the following to get the associated Posts with a single extra query:
fetch the Activities, then build an Array of their linked post_ids.
#activities = #artist.activities.includes([:link,:creator,:receiver])
activity_post_ids = #activities.map{|a| a.link.post_id}.compact
then load the posts in one query and store them in a Hash, indexed by their id
activity_posts = Hash[Post.where(id: activity_post_ids).map{|p| [p.id, p]}]
#=> {1 => #<Post id:1>, 3 => #<Post id:3>, ... }
finally loop over #activities and set the post attribute of each associated link
#activities.each{|a| a.link.post = activity_posts[a.link.post_id]}
This simple ActiveRecord should work:
#artist.activities.includes([:link => :post,:creator,:receiver]).order("id DESC").limit(30)
If not, if you are getting an error like "Association named 'post' was not found; perhaps you misspelled it?", then you have a model problem, because at least one of your link associations doesn't have the post association.
Polymorphic associations are intended to be used with a common interface. If you ask for the post for any of the polymorphic association, then ALL of your associations should implemented also that association (the post one).
Check that all of your models used in the polymorphic association implement the post association.

Rails ActiveRecord: legacy table without primary key shows nil for result?

I've got a Rails app that'll be sitting on top of a legacy database with some ugly tables that I'm having to deal with. One is a feature_attributes table related to features. Problem is that this feature_attributes table doesn't have a primary key. I wouldn't think that'd be a problem, but evidently it is. I've got my model name which is different from the table name, but I'm using set_table_name to specify the right one.
class Feature < ActiveRecord::Base
has_many :feature_attributes
end
class FeatureAttribute < ActiveRecord::Base
set_table_name 'feature_attribute'
belongs_to :feature
end
Once I load a feature that I know has related feature_attributes and call feature.feature_attributes on it, I get nil. Matter of fact, even FeatureAttribute.first gives me nil. The result of FeatureAttribute.any? returns false. I'm worried that ActiveRecord isn't reading any of the data from the table because there isn't a primary key. Is that what's going on here?
The feature_attribute table has the following columns.
feature_id
attribute_name
attribute_value
created_date
modified_date
Help!
I'll also note that running the generated SQL directly against the MySQL server actually gets me the rows I want.
SELECT `feature_attribute`.* FROM `feature_attribute` WHERE `feature_attribute`.`feature_id` = 24;
EDIT: I am so sorry. Next time I'll learn to check my database.yml. Evidently I was reading from a test database that had an identical feature table, but the feature_attribute table was totally empty.
I feel like a moron. Thanks for your help, everyone; I'm up voting you all for your troubles. I did like just about everybody's answer. (Can I down vote myself? :))
Try to also set the primary key:
class FeatureAttribute < ActiveRecord::Base
set_table_name 'feature_attribute'
set_primary_key 'feature_id'
belongs_to :feature
end
UPDATE
I think your problem lies anywhere else. I just tested and ActiveRecords works fine with tables without a primary key:
For a simple table:
class CreateThings < ActiveRecord::Migration
def change
create_table :things, :id => false do |t|
t.string :name
t.timestamps
end
end
end
in console:
Loading development environment (Rails 3.1.1)
irb(main):001:0> Thing.create(:name=>'A name for the thing')
(0.1ms) BEGIN
SQL (0.3ms) INSERT INTO `things` (`created_at`, `name`, `updated_at`) VALUES ('2011-11-02 16:33:48', 'A name for the thing', '2011-11-02 16:33:48')
(40.3ms) COMMIT
=> #<Thing name: "A name for the thing", created_at: "2011-11-02 16:33:48", updated_at: "2011-11-02 16:33:48">
irb(main):002:0> Thing.first
Thing Load (0.7ms) SELECT `things`.* FROM `things` LIMIT 1
=> #<Thing name: "A name for the thing", created_at: "2011-11-02 16:33:48", updated_at: "2011-11-02 16:33:48">
irb(main):003:0>
UPDATE 2
Not very fine:
irb(main):003:0> Thing.create(:name=>'Another thing')
(0.2ms) BEGIN
SQL (0.4ms) INSERT INTO `things` (`created_at`, `name`, `updated_at`) VALUES ('2011-11-02 16:40:59', 'Another thing', '2011-11-02 16:40:59')
(35.4ms) COMMIT
=> #<Thing name: "Another thing", created_at: "2011-11-02 16:40:59", updated_at: "2011-11-02 16:40:59">
irb(main):004:0> Thing.first
Thing Load (0.5ms) SELECT `things`.* FROM `things` LIMIT 1
=> #<Thing name: "A name for the thing", created_at: "2011-11-02 16:33:48", updated_at: "2011-11-02 16:33:48">
irb(main):005:0> Thing.last
Thing Load (11.8ms) SELECT `things`.* FROM `things` ORDER BY `things`.`` DESC LIMIT 1
Mysql2::Error: Unknown column 'things.' in 'order clause': SELECT `things`.* FROM `things` ORDER BY `things`.`` DESC LIMIT 1
ActiveRecord::StatementInvalid: Mysql2::Error: Unknown column 'things.' in 'order clause': SELECT `things`.* FROM `things` ORDER BY `things`.`` DESC LIMIT 1
If a certain attribute can occur only once for a feature, you assume attribute_name is the key, in combination with the feature_id. Rails does not support composite primary keys out of the box, but there is a gem called composite_primary_keys that supports just that.
In your model you can then write
set_primary_keys :feature_id, :attribute_name
If an attribute-name could occur multiple time for a single feature, the combination of feature_id, attribute_name and attribute_value is the key, and you should write
set_primary_keys :feature_id, :attribute_name, :attribute_value
Hope this helps.
[edit] Alternative approach:
The above clearly is not enough, so you could also do the following:
class Feature
has_many :feature_attributes, :finder_sql => 'select * from feature_attributes where feature_id=\'#{id}\''
end
Hope that this does help :)
class Feature < ActiveRecord::Base
self.table_name = 'legacy_table_name'
self.primary_key = nil
end
Then query using Feature.find_by field:'value' or other ActiveRecord queries.
If it isn't possible to update the table to just add a autoincrement primary key, then you might be better served dealing with it manually:
class Feature < ActiveRecord::Base
# get rid of has_many feature_attributes and use this instead
def feature_attributes
FeatureAttribute.find_by_sql(["select * from feature_attribute where feature_id = ?", self.id])
end
end

Resources