Rails : get information recursively with has_one - ruby-on-rails

I have 2 very simple models:
class Teacher < ActiveRecord::Base
has_one :fee, :foreign_key => 'teacher_id'
end
...
class Fee < ActiveRecord::Base
belongs_to :teacher
end
When loading teacher information with the "find" method, only teacher table information are present. (data exists in the fee table)
#teacher = Teacher.find(id)
With the debug method :
--- !ruby/object:Teacher
attributes:
id: 7
…
presentation:
presentation_flag: true
created_at: 2013-09-16 00:38:14.000000000 Z
updated_at: 2013-09-16 00:38:14.000000000 Z
status:
last_login:
first_name:
…
But nothing about fee table. Any ideas ? Thank you

This is by design. ActiveRecord is 'lazy' by default, it only fetches the fee data when you access #teacher.fee for the first time.
In case you want to load both at the same time, you should use 'eager loading' by adding includes like this:
#teacher = Teacher.includes(:fee).find(id)
Here are a couple of references for 'eager loading':
http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations
http://railscasts.com/episodes/22-eager-loading-revised

Related

Loading Relations

Goal: I would like to include all of a customers medical conditions as an array in the result of a customer.
for:
cust = Customer.includes(:conditions).find(1)
expected result:
#<Customer id: 1, first_name: "John", last_name: "Doe", conditions [...]>
actual result:
#<Customer id: 1, first_name: "John", last_name: "Doe">
code:
I have 2 classes and a 3rd join class (ConditionsCustomer).
class Customer < ApplicationRecord
has_many :conditions_customers
has_many :conditions, through: :conditions_customers
end
#join table. Contains 2 foreign_keys (customer_id, condition_id)
class ConditionsCustomer < ApplicationRecord
belongs_to :customer
belongs_to :condition
end
class Condition < ApplicationRecord
has_many :conditions_customers
has_many :customers, through: :conditions_customers
end
What's interesting is that I see 3 select queries getting fired (customer, join table and medical conditions table) so I know the includes is somewhat working but unfortunately customer returns without the medical conditions.
I've also tried using a join but I get an array of same customer over and over again.
Is there an easy way to do this with ActiveRecord? I would prefer not having to merge the record manually.
Not really possible via active record, as json offers some cool possibilities :
render json: customers,
include: {
conditions: {
only: [:attr1, :attr2], # filter returned fields
methods: [:meth1, :meth2] # if you need model methods
},
another_joined_model: {
except: [:password] # to exclude specific fields
}
}

Querying Parent Model with respect to current user's Child Model

Accordingly to devise documentation, related records to current_user is always available, despite what I've read previously. For instance: current_user.comments current_user.profile_images
What bugs me really is this:
Post.rb
class Post < ApplicationRecord
belongs_to :user
has_many :postsettings, inverse_of: :post
accepts_nested_attributes_for :postsettings
User.rb
class User < ApplicationRecord
has_many :posts
has_many :postsettings
Postsettings.rb
class Postsetting < ApplicationRecord
belongs_to :post, required: false
belongs_to :user, required: false
In Posts controller I have this:
#post_delete = current_user.postsettings.includes(:postsettings).where.not
(postsettings: {user_id: current_user.id, delete_post: false})
Which works, but is not producing the desired outcome because I need to query ALL Posts where current_user.POSTSETTINGS.delete_post is true or false.
So I've been at this for a couple of days now, and I managed to come up with this:
#post_delete = Post.includes(current_user.postsettings).where(
postsettings: {user_id: current_user.id, delete_post: false})
This produces an error message which I have not seen before.
ActiveRecord::ConfigurationError in Posts#index
#<Postindex id: 284, read: nil,
created_at: "2017-04-15 11:38:02",
updated_at: "2017-04-15 11:38:02", post_id: 96, user_id: 1,
delete_post: false>
Which indicates as far as I can see that the query finds all that it needs to find. But it won't actually function. Please help me.... I'm dying over here.
Post.joins(:postsettings)
.where(postsettings: { user: current_user } )
.where(postsettings: { delete_post: [true, false] })
.joins creates a INNER JOIN which means that only rows from posts with matches in postsettings will be returned.
The answer given by max is good, for convenience though, you can set your user model as follows. Add this line to your user model.
has_many :posts_with_settings, through: :postsettings, source: :post
Now you should be able to call current_user.posts_with_settings to give you all the posts with postsettings set for current_user. From there you can filter as you wish, e.g,
current_user.posts_with_settings.where(postsettings: {delete_post: true})
For more info on :through option see here.

Rails Create Related Models together

I have two Models Order(id, user_id,...) and OrderEvent(id, order_id, ...)the data of these are stored in two Objects #order and #order_event.
Now i want to save Order and update the order_id in OrderEvent and
then save the content in the database.
In CakePHP if we had the content structured in a certain way we could save all of these together at once. Is there a way to save the content in a single call so that if there is any validation error both records are not created and fails
Order
--- !ruby/object:Order
attributes:
id:
host_id: 1
user_id: 1
order_no: PH1504-F3D11353
type_of_order: events
order_date: !ruby/object:DateTime 2015-04-17 10:49:52.066168000 Z
sub_total: 7050.0
tax_rate:
rate_cost:
deliver_charges:
discount_code:
discount_rate:
discount_cost:
total_cost: 7050.0
order_dishes_count:
order_events_count:
status: 0
created_at:
updated_at:
OrderEvent
--- !ruby/object:OrderEvent
attributes:
id:
order_id:
event_id: 2
no_of_ppl: 3
event_date: 2015-01-22 00:00:00.000000000 Z
cost: 2350.0
total_cost: 7050.0
status: 0
created_at:
updated_at:
Order
class Order < ActiveRecord::Base
belongs_to :user
belongs_to :host
has_many :order_events
has_many :messages
end
OrderEvent
class OrderEvent < ActiveRecord::Base
belongs_to :event
belongs_to :order
end
If you have set proper associations, following will work for you:
#order = Order.create(...)
#order_event = #order.order_events.create(...) # pass all attrs except id & order_id
EDIT:
If I have the object ready and just wanna save can I use '.save' instead of .create – Harsha M V
In that case, you can directly update #order_event as:
#order_event.update(:order => #order)
Best way is to use Transactions of rails. Reference : Transactions
What you are looking for might be :
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
If you only want to update the OrderEvent just do
class OrderEvent
belongs_to :order
end
And in your controller
#order_event.update_attributes(order: #order)
Edit : update_attributes saves to the database.
See http://apidock.com/rails/ActiveRecord/Persistence/update for more info.
If you have both models ready but none has an id yet then I think you should go for nested attributes :
class Order
accepts_nested_attributes_for :order_events
has_many :order_events
end
class OrderEvent
belongs_to :order
end
I controller, In order to save the Order, with its order_events :
def update
#order.update_attributes(order_events_attributes: [#order_event])
end
Should work like a charm.
Inverse_of and accepts_nested_attributes_for allow you to create two associated objects at the same time.
Your models should be something like:
class Order < ActiveRecord::Base
has_many :order_events, inverse_of: :order
accepts_nested_attributes_for :order_events
end
class OrderEvent < ActiveRecord::Base
belongs_to :order, inverse_of: :order_event
end
In the orders controller:
def new
#order = Order.new
#order.order_events.build
end
def create
#order = Order.new(order_params)
...
end
Allow order_events attributes in the params:
def order_params
params.require(:order).permit(order_event_attributes: [])
end
In the form:
<%= form_for #order do |f| %>
<%= f.fields_for :order_events do |event| %>
<!-- event fields go here -->
<% end %>
Here is an article with more details what invese_of does: http://viget.com/extend/exploring-the-inverse-of-option-on-rails-model-associations

ActiveRecord join with select Rails 4

I have the following AR relations,
class Restaurant < ActiveRecord::Base
has_many :deals
end
class Deal < ActiveRecord::Base
belongs_to :restaurant
acts_as_taggable_on :tags
end
What I want to do is to find the deals with given tags, and return the related restaurants.
I have the following scope in my Restaurant model
scope :food_type, ->(food_types){
select("*")
joins(:deals).merge(Deal.tagged_with([food_types], any: true, wild: true ))
}
But the issue is, when I call Restaurant.food_type(<tags>) it returns ActiveRecord relation with Restaurant objects with Deal data
Ex: #<Restaurant id: 383, name: "sample deal name", note: "sample deal note", created_at: "2014-03-18 22:36:27", updated_at: "2014-03-18 22:36:27"
I even used select in the scope , without any luck. How can I return Restaurant attributes from the above scope?

Creating associated models on create in Rails?

I have to be dead tired because I really can't figure out such simple task as this.
Having:
class Account < ActiveRecord::Base
has_one :subscription, :dependent => :destroy
after_save :append_subscription
private
def append_subscription
# TODO
end
end
# Subscription(id: integer, account_id: integer, level: integer (: 1), starts_at: date, ends_at:date, created_at: datetime, updated_at: datetime)
class Subscription < ActiveRecord::Base
belongs_to :account
end
I'm trying resolve the TODO part, or am I going about it the wrong way? Here's the test.
describe Account do
include AccountSpecHelper
it "should have a subscription at least at level one on creation" do
account = Account.create
account.subscription.level.should be(1)
end
end
Why after_save and not before_create and let ActiveRecord worry about creating associated model and assigning account_id correctly?
I haven't checked, but this should work:
class Account
before_create {|account| account.build_subscription(params)} # or move it to method
end

Resources