I have 2 models:
# models/car.rb
class Car < ActiveRecord::Base
belongs_to :model
end
and
# models/manufacturer.rb
class Manufacturer < ActiveRecord::Base
has_many :cars
end
When I'm executing command in rails console Car.find(1).manufacturer it shows me that one more sql query was executed SELECT manufacturers.* FROM manufacturers WHERE manufacturers.id = 54 LIMIT 1,
so I am interested is it usual (for production, first of all) behavior, when a lot of sql queries being executed just to get some object property? what about performance?
UPDATE, ANSWER:
I got an answer from another source: I was told it's "necessary evil" as a payment for abstraction
This is not a "necessary evil" and your intuition that the second query is needless is correct. What you need to do is use :include/includes to tell Rails to do a JOIN to get the associated objects in the same SELECT. So you could do this:
Car.find 1, :include => :manufacturer
# or, in Rails 3 parlance:
Car.includes(:manufacturer).find 1
Rails calls this "eager loading" and you can read more about it in the documentation (scroll down to or Ctrl+F for "Eager loading of associations").
If you always want to eager-load the associated objects you can declare default_scope in your model:
class Car
belongs_to :manufacturer
default_scope :include => :manufacturer
# or Rails 3:
default_scope includes(:manufacturer)
end
However you shouldn't do this unless you really need the associated Manufacturer every time you show a Car record.
Related
Problem?
I have a Rails5 application. I have two models. Team and Players.
The association is has_many & belongs_to in between them.
class Team < ApplicationRecord
has_many :players
end
class Player < ApplicationRecord
belongs_to :team
end
Now, I want to perform destroy players before updating the team model. The condition is like below
def update
#team.players.destroy_all if a
........
if b
.... some code.....
elsif c
#team.players.destroy_all
end
if #team.update_attributes(team_params)
redirect_to teams_path
else
... some code..
render :edit
end
end
Note
In team_params, I have players_attributes, so each time if a new entry is there I need to remove all old entries and then #team.update_attributes will insert the entries in players.
Expectations
If #team.update_attributes(team_parmas) fails, then #team.players should be rolled back.
Things I tried
I tried adding Transactions in the first line of update method but it doesn't work.
You can use a gem like paper trail to keep a history, but the nature of rails is to not be able to roll back transactions after committed. One way to mock this would be to keep the attributes in a transient collection of hashes and re-save them if you need the records once more. You could have logic like
team_players = #team.players.map(&:attributes)
Then if you need to 'roll back'
team_players.each do |team_player|
TeamPlayer.create(team_player)
end
This will only work with basic attributes. If you have other model relations you will have to handle them with attributes also.
I have a model with 2 associations and no validations, which can be created but if I want to destroy it, I get error " ActiveRecord::RecordNotDestroyed"
So I tried directly in rails create new record for this Model and then run destroy, but it rollbacks without any error message. If I do destroy! I get the error. I enabled mysql query logging but it looks it doesnt even get to delete part so I assume rails is somehow preventing from destroying this model.
As a test I created dummy model without any association, with just one string value. Same issue in rails console. I have to note that .delete method works, but .destroy not
Then I started to analyze in my rails app (I took it over from somebody so I am still not 100% familiar) and in rails console tried to destroy some records for different models and realized some I am able to destroy, some not.
Is there any way how to find out why I am not able to destroy those records? It is really strange for my Testmodel without any validation or association. Is there anywhere to look for more info? I tried this:
testrecord.destroy; testrecord.errors but in #messages I have empty {}
Here is my code for model where I found an issue (EinvoiceContact) and also my Testmodel
class EinvoiceContact < ActiveRecord::Base
belongs_to :customer
has_many :building
end
class Customer < ActiveRecord::Base
has_many :einvoice_contacts, dependent: :destroy
accepts_nested_attributes_for :einvoice_contacts, allow_destroy: true, reject_if: :empty_einvoice_contact
end
class Testmodel < ActiveRecord::Base
end
and here is output from rails and mysql log
> irb(main):045:0> Testmodel.find(1).destroy\r Testmodel Load (0.4ms)
> SELECT `testmodels`.* FROM `testmodels` WHERE `testmodels`.`id` = 1
> LIMIT 1 (0.1ms) BEGIN (0.1ms) ROLLBACK
> => false
>
>
> 2019-12-18T21:05:42.455378Z 33 Query SELECT `testmodels`.* FROM
> `testmodels` WHERE `testmodels`.`id` = 1 LIMIT 1
> 2019-12-18T21:05:42.456559Z 33 Query BEGIN
> 2019-12-18T21:05:42.458261Z 33 Query ROLLBACK
Any help where to look for more details, or where this can be set is appreciated. This app uses CanCan but I have set
can :manage, :all
Rails is 4.2.8 with ruby 2.1.4 (I know it is old app, I just took it over and maintain), mysql
thanks
It seems that Testmodel has some associations.
Try this in the console and check the output.
Testmodel.reflect_on_all_associations
Does Testmodel.find(1) return anything?
Try Testmodel.find(1).destroy! (with exclamation point) to see if it tells you anything more.
Problem solved, there was this code added in lib folder which prevents to destroy anything unless I set in model can_destroy to true
thanks all for help
class ActiveRecord::Base
def can_destroy?
false
end
before_destroy do
can_destroy?
end
I have these models:
SpaceShip < ActiveRecord::Base
has_one :martian
has_many :people
Person < ActiveRecord::Base
has_many :skills
Martian < ActiveRecord::Base
belongs_to :skill
Skill < ActiveRecord::Base
#rest omitted
I need to have a scope to find a spaceship where martian.skill.name = 'drive' or person.skill.name = 'drive'
It seems easy at first but it's not:
Spaceship < ActiveRecord::Base
scope :by_skill_name, -> (skill_name){
joins([[people: :skills], [martian: :skill]])
.where('skills.name=?', skill_name)
}
This scope won't work because only takes in count the association martian.skill.
I tried making the joins part in SQL and using table alias. It works but chaining with other scopes I get SQL errors.
I also tried with joins(...).merge(joins(...))but I'm still getting only the last relation.
Active Record doesn't have 'union' statement, I have seen some ways to implement it but I don't know if it's going to be too complicated and hacky.
Any idea?
Thanks.
EDIT:
By now I solved it using squeel where{(martian.skill.eq skill_name)|(people.skills.eq skill_name)}but I'd like to know if it's possible in plain AR.
The short version:
I have a Rails 3.2.1.4 unit test that is trying to create a new many-to-many relationship in an external database. The relationship is created, but I cannot get ActiveRecord to retrieve it.
The long version:
I have a Rails 3.2.1.4 project that is interacting with an external MySQL database that has two tables that have a many-to-many relationship via the conventional join table.
I have a test that creates a new relationship on existing data but can't retrieve the relationship. Here's the failing test:
class ExternalTest < ActiveSupport::TestCase
def setup
#foo_id = 1
#foo = ExternalAsset.find(#foo_id)
end
test "subjects attach correctly"
#foo.external_subjects << ExternalSubject.find(2)
assert_equal 1, ExternalAsset.find(#foo_id).external_subjects.count
end
end
To add to the frustration, adding and querying back the many-to-many relationship with almost this exact code works just fine in the console.
I have googled the living heck out of this and come up empty. My best guess is that this is some sort of freak interaction of ActiveRecord, an external database connection, and the test framework.
Here's the models I'm dealing with:
class ExternalAsset < ActiveRecord::Base
establish_connection "external_#{Rails.env}"
self.table_name = :assets
has_and_belongs_to_many :external_subjects, :join_table => :assets_subjects,
:association_foreign_key => :subject_id, :foreign_key => :asset_id
end
class ExternalSubject < ActiveRecord::Base
establish_connection "external_#{Rails.env}"
self.table_name = :subjects
has_and_belongs_to_many :external_assets, :join_table => :assets_subjects,
:association_foreign_key => :asset_id, :foreign_key => :subject_id
end
I think it's because you use static IDs in your test, try to use fixtures for instance.
Some remarks: You don't clearly state your issue, and your question is really too long, you should sum up your issue in a few lines in one code example.
I have a Model called Person and Person has multiple posts. When I want to query post count for each person it takes a long time to process since it needs to iterate over each person and query each posts to get the aggregation.
class Person < ActiveRecord::Base
has_many :posts
end
Output (JSON):
Person1
PostsType1Count: 15
PostsType2Count: 45
Person2
PostsType3Count: 33
.
.
.
I want to calculate all the post count for each Person in a optimum way. What would be the best solution?
Here's one way to do this, if you have a small and pre-defined set of Types
class Person < ActiveRecord::Base
has_many :type_1_posts, :class_name => 'Post', :conditions => 'post_type = 1'
has_many :type_2_posts, :class_name => 'Post', :conditions => 'post_type = 2'
has_many :type_3_posts, :class_name => 'Post', :conditions => 'post_type = 3'
end
Then you can write code that looks like this to get all the data:
#all_people = Person.includes(:type_1_posts, :type_2_posts, :type_3_posts).all
The eager loading of the posts allows the count of each type of post to be available, as well as all the posts of each type.
If you need extra performance for this code, because you perform this query a lot, then you can look into using the Rails counter cache mechanism to keep track of the counts of each type on the Person object.
The beauty of Rails here is that your main display code doesn't need to change during this process of making the code faster for reading (adding a counter cache makes adding/deleting posts slower, so you may not want it in all cases).
Write initial code
Use eager loading to make it faster
Use counter cache to make it even faster
Try this May it will work for you
#In Controller
#persons = Person.all
#In View
#persons.each do |person|
person.posts.count # It will gives all posts count
person.posts.collect{|x| true if x.type==1 }.compact.count #If you want to get the post counts based on type
end
Suppose if you want to get any mehods just check in console or debug is person.methods.sort it will give all methods.
try in rails console person.posts.methods also it will give types also then check counts based on type. because i dont know which fields in posts model. so check it.