My models:
class Contact < ActiveRecord::Base
has_many :addresses
has_many :emails
has_many :websites
accepts_nested_attributes_for :addresses, :emails, :websites
attr_accessible :prefix, :first_name, :middle_name, :last_name, :suffix,
:nickname, :organization, :job_title, :department, :birthday,
:emails_attributes
end
class Email < ActiveRecord::Base
belongs_to :contact
validates_presence_of :account
validates_format_of :account, :with => /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :on => :create
attr_accessible :contact_id, :account, :label
end
If I run the following query, the emails are returned as expected:
c = Contact.find(3)
Contact Load (3.2ms) SELECT `contacts`.* FROM `contacts` LIMIT 1
=> #<Contact id: 3, prefix: nil, first_name: "Micah", middle_name: nil, last_name: "Alcorn", suffix: nil, nickname: nil, organization: nil, job_title: nil, department: nil, birthday: nil, created_at: "2011-07-04 23:50:04", updated_at: "2011-07-04 23:50:04">
c.emails
Email Load (4.4ms) SELECT `emails`.* FROM `emails` WHERE `emails`.`contact_id` = 3
=> [#<Email id: 3, contact_id: 3, account: "not#real.address", label: "work", created_at: "2011-07-04 23:50:04", updated_at: "2011-07-04 23:50:04">]
However, attempting to :include the relationship does not:
c = Contact.find(3, :include => :emails)
Contact Load (0.5ms) SELECT `contacts`.* FROM `contacts` WHERE `contacts`.`id` = 3 LIMIT 1
Email Load (0.8ms) SELECT `emails`.* FROM `emails` WHERE `emails`.`contact_id` IN (3)
=> #<Contact id: 3, prefix: nil, first_name: "Micah", middle_name: nil, last_name: "Alcorn", suffix: nil, nickname: nil, organization: nil, job_title: nil, department: nil, birthday: nil, created_at: "2011-07-04 23:50:04", updated_at: "2011-07-04 23:50:04">
As you can see, the SQL is being executed, but the emails are not being returned. I intend to return all contacts with each containing email(s), so :joins won't do any good. What am I missing?
The emails are there. Did you try c.emails? You will find that the emails will be there without Rails doing an additional DB query.
The thing that :include does is called eager loading, which basically means Rails will try a best effort method of prepopulating your objects with their relations, so that when you actually ask for the relation no additional DB queries are needed.
See the section "Eager loading of associations" here:
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
You might also want to check out this RailsCast:
http://railscasts.com/episodes/181-include-vs-joins
Related
I'm writing specs for creating a new account. Here's the situation:
Ruby 2.5.0, Rails 5.2.0.rc1
class Account
has_many :accounts_users, inverse_of: :account
has_many :users, through: :accounts_users
has_one :owner, -> { AccountsUser.admins.order(:id) }, class_name: 'AccountsUser'
accepts_nested_attributes_for :owner
end
class AccountsUser
belongs_to :account, inverse_of: :accounts_user
belongs_to :user, autosave: true, inverse_of: :accounts_user
accepts_nested_attributes_for :user
end
class User
has_many :accounts, through: :accounts_users
has_many :accounts_users, autosave: true, dependent: :destroy, inverse_of: :user
accepts_nested_attributes_for :accounts_users, reject_if: :all_blank
end
# AccountsController#account_create_params
params.require(:account).permit(
:plan_id,
:name,
:subdomain,
{
owner_attributes: {
user_attributes: [
:email,
:first_name,
:last_name
]
}
}
)
# rspec account_create_params
let(:valid_account_params) do
{
plan_id: plan.id,
name: 'Name',
subdomain: 'subdomain',
owner_attributes: {
user_attributes: [
email: 'jane#doe.com',
first_name: 'Jane',
last_name: 'Doe'
]
}
}
end
#account = Account.new(account_create_params)
#> NoMethodError: undefined method `with_indifferent_access' for #<Array:0x000056472e0799c0>
from /usr/local/bundle/gems/activerecord-5.2.0.rc1/lib/active_record/nested_attributes.rb:412:in `assign_nested_attributes_for_one_to_one_association'
It has to be complaining about the user_attributes array, and seeing as it's coming from a method called assign_nested_attributes_for_one_to_one_association, Rails seems to be erroneously thinking that the relationship between AccountsUsers and Users is one to one and not one to many.
I've triple checked the associations. What do you guys think? Rails 5.2 bug perhaps?
There were several issues here. I found that permitting parameters and providing them needed different syntax:
def account_create_params
params.require(:account).permit(
:plan_id,
:name,
:subdomain,
{
owner_attributes: {
user_attributes: [
:email,
:first_name,
:last_name,
:password
]
}
}
)
end
vs
let(:valid_account_params) do
{
plan_id: plan.id,
name: 'Name',
subdomain: 'subdomain',
owner_attributes: {
user_attributes: {
email: email_valid,
first_name: 'Jane',
last_name: 'Doe',
password: 'password'
}
}
}
end
A bit confusing there but it works. The inverse_of on AccountsUser was wrong. I needed plural:
belongs_to :account, inverse_of: :accounts_users, optional: true
belongs_to :user, autosave: true, inverse_of: :accounts_users
And I also needed optional: true on the account belongs to, despite accepts_nested_attributes_for setting up the relationship properly. There was a chicken vs egg situation where the AccountsUser was invalid because the Account didn't exist, and the Account couldn't be saved because the AccountsUser was invalid. This may be a Rails 5 'feature'.
I have the three following tables in Ruby on Rails 4:
The "Decision" table:
class Decision < ActiveRecord::Base
validates :title, presence: true, length: { maximum: 50 }, uniqueness: { case_sensitive: false }
validates :colour, presence: true, length: { maximum: 20 }, uniqueness: { case_sensitive: false }
has_many :decision_datafields, dependent: :destroy
has_many :datafields, through: :decision_datafields
def datafields
Datafield.where(id: self.decision_datafields.select("datafield_id"))
end
end
The "DecisionDatafield" table (linking table):
class DecisionDatafield < ActiveRecord::Base
validates :min_score, presence: true
validates_inclusion_of :min_score, :in => 1..10
belongs_to :decision
belongs_to :datafield
end
The "Datafield" table:
class Datafield < ActiveRecord::Base
validates :title, presence: true, length: { maximum: 100 }, uniqueness: { case_sensitive: false }
has_many :decision_datafields, dependent: :destroy
has_many :decisions, through: :decision_datafields
has_many :score_options, dependent: :destroy
def decisions
Decision.where(id: self.decision_datafields.select("decision_id"))
end
end
There is also a score options table, but it isn't necessary for the problem.
Anyway, what I'd like to be able to do is do a query like this:
Decision.first.datafields
... and have ActiveRecord retrieve a list of the first Decision's Datafields, along with the corresponding min_score value from the DecisionDatafields linking table.
Right now, the above query will return something like this:
#<ActiveRecord::Relation [#<Datafield id: 1, title: "DF1", created_at: "2015-03-28 09:59:26", updated_at: "2015-03-28 09:59:26">, #<Datafield id: 2, title: "DF2", created_at: "2015-03-28 09:59:26", updated_at: "2015-03-28 09:59:26">]>
... which is nice, but I want it to look like this:
#<ActiveRecord::Relation [#<Datafield id: 1, title: "DF1", created_at: "2015-03-28 09:59:26", updated_at: "2015-03-28 09:59:26", min_score: 5>, #<Datafield id: 2, title: "DF2", created_at: "2015-03-28 09:59:26", updated_at: "2015-03-28 09:59:26, min_score: 7">]>
The difference is that the min_score from the DecisionDatafield linking table has been joined to the records returned by the query.
Thanks!
This might work (haven't tested it):
class Decision < ActiveRecord::Base
...
def data_fields
Datafield.includes(:decision_datafield).select("datafields.*, decision_datafields.min_score").where(id: self.decision_datafields.select("datafield_id"))
end
end
Call it with Decision.first.data_fields
It seems that the following works:
Datafield.where(id: self.decision_datafields.select("datafield_id")).includes(:decision_datafields).joins("left join decision_datafields on datafields.id = decision_datafields.datafield_id").select("datafields.*, decision_datafields.min_score as min_score")
It would appear, however, that when using the Rails console, the min_score does not get added to the record.
However, calling Decision.first.datafields.first.min_score does return a value (it's just not shown when viewing the record via Decision.first.datafields.first.
In my seeds file I have the following:
puts "Creating Deans User"
user = User.create!(:email => "test#test.com", :password => "test1234", :password_confirmation => "test1234", :name => "Dean Chester", :admin => true)
puts "User created"
But when I check this in the console I see the following:
[#<User id: 1, email: "test#test.com", encrypted_password: "", reset_password_token: nil, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 0, current_sign_in_at: nil, last_sign_in_at: nil, current_sign_in_ip: nil, last_sign_in_ip: nil, created_at: "2012-08-06 10:02:22", updated_at: "2012-08-06 10:02:22", admin: true, name: "Dean Chester">]
And the encrypted password field is blank so what is going wrong?
Do you have:
attr_accessor :password, :password_confirmation, :current_password
in your model? If so remove it and it will fix your issue.
You might be missing attr_accessible :password, :password_confirmation in your `User.rb.
$ rails -v
Rails 3.1.1
$ ruby -v
ruby 1.9.2p312 (2011-08-11 revision 32926) [i686-linux]
If you want to reproduce the problem, just follow me:
First, create these three model(just copy):
#school.rb
class School
include Mongoid::Document
include Mongoid::Timestamps
has_many :students
end
#student.rb
class Student
include Mongoid::Document
include Mongoid::Timestamps
has_many :books
belongs_to :school
accepts_nested_attributes_for :books
end
#book.rb
class Book
include Mongoid::Document
include Mongoid::Timestamps
field :name
belongs_to :student
validate :check
def check
# The calling for the 'school' method caused the issue
self.student.school
end
end
Second, run your console and paste:
ruby-1.9.2-head :001 > School.destroy_all;Student.destroy_all; Book.destroy_all; School.create
ruby-1.9.2-head :001 > Student.create school_id: School.first.id, 'books_attributes' => {'1' => {'name' => 'I am a book'}}
Then, let's see what happend:
ruby-1.9.2-head :002 > Book.count
MONGODB xxx_development['$cmd'].find({"count"=>"books", "query"=>{}, "fields"=>nil})
=> 2
And even more, if you set the 'student has_many books' relation to 'autosave: true':
class Student
......
has_many :books, autosave: true
......
end
Let's see what will happend:
ruby-1.9.2-head :001 > School.destroy_all;Student.destroy_all; Book.destroy_all; School.create
ruby-1.9.2-head :001 > Student.create school_id: School.first.id, 'books_attributes' => {'1' => {'name' => 'I am a book'}}
ruby-1.9.2-head :002 > Student.count
MONGODB xxx_development['$cmd'].find({"count"=>"students", "query"=>{}, "fields"=>nil})
=> 2
ruby-1.9.2-head :004 > Student.all.to_a
MONGODB xxx_development['students'].find({})
=> [#<Student _id: 4f62a8341d41c81bc6000002, _type: nil, created_at: 2012-03-16 02:40:52 UTC, updated_at: 2012-03-16 02:40:52 UTC, school_id: BSON::ObjectId('4f62a8341d41c81bc6000001')>, #<Student _id: 4f62a8341d41c81bc6000003, _type: nil, created_at: 2012-03-16 02:40:52 UTC, updated_at: 2012-03-16 02:40:52 UTC, school_id: nil>]
ruby-1.9.2-head :005 > Book.count
MONGODB xxx_development['$cmd'].find({"count"=>"books", "query"=>{}, "fields"=>nil})
=> 2
ruby-1.9.2-head :006 > Book.all.to_a
MONGODB xxx_development['books'].find({})
=> [#<Book _id: 4f62a8341d41c81bc6000003, _type: nil, created_at: 2012-03-16 02:40:52 UTC, updated_at: 2012-03-16 02:40:52 UTC, name: "I am a book", student_id: BSON::ObjectId('4f62a8341d41c81bc6000002')>, #<Book _id: 4f62a8341d41c81bc6000002, _type: nil, created_at: 2012-03-16 02:40:52 UTC, updated_at: 2012-03-16 02:40:52 UTC, name: nil, student_id: nil>]
This bug really run me crazy.
Why there are additional models when calling 'school' in a book validate method?
Or there is something I did wrong?
The code is fine here, you're not doing anything incorrect - but Mongoid has no issue with this same code on master or 2.4.x. See my suggestions here to find the culprit:
https://github.com/mongoid/mongoid/issues/1826
I have a Client and ProposalRequest model that look like this:
class Client < ActiveRecord::Base
has_many :proposal_requests
accepts_nested_attributes_for :proposal_requests, :allow_destroy => true
end
class ProposalRequest < ActiveRecord::Base
belongs_to :client
end
In my my routes file, I included the nested routes, as usual.
resources :clients do
resources :proposal_requests
end
And this is my form so far:
-semantic_form_for [Client.new, ProposalRequest.new] do |f|
=f.inputs
=f.buttons
But after this, I'm stuck because of this error.
No route matches {:controller=>"proposal_requests", :client_id=>#<Client id: nil, name: nil, title: nil, organization: nil, street_address: nil, city: nil, state: nil, zip: nil, phone: nil, email: nil, status: "interested", how_you_heard: nil, created_at: nil, updated_at: nil>}
Can anyone help me puzzle out this error?
The problem is that your nested route is meant to add a new ProposalRequest to an existing Client. If you want to create a Client and a ProposalRequest at the same time, you need to just use new_client_path and semantic_form_for #client do |f|.
I would recommend you do the following in your clients_controller:
def new
#client = Client.find(params[:id])
#client.proposal_requests.build
end
And in your view:
semantic_form_for #client do |f|
= f.inputs # fields for client
= f.inputs :name => 'Proposal Request', :for => :proposal_requests do |pf|
= pf.input :some_proposal_request_attribute
= f.buttons
Hope this helps. Make sure to look at all the examples at https://github.com/justinfrench/formtastic and do some trial and error to get your form how you want it.