accessing params passed into update_attribute/create in before_save - ruby-on-rails

have model
Class ModelA < ActiveRecord::Base
has_many :apples
before_save :include_prime_apple_in_apples
has_one :prime_apple
def include_prime_apple_in_apples
self.apple_ids << prime_apple_1.id
end
end
l=ModelA.new(:apple_ids => [ "ap_1_id", "ap_2_id"],:prime_apple => prime_apple_1)
l.save
l.apple_ids.should include(prime_apple_1.id) # this doesnt seem to work
How change the params passed for associations?

There's something wrong there but to answer your question:
AFAIK you can only assign to "other_ids". Also you can push directly to the has_many relation:
self.apple_ids = self.apple_ids + [prime_apple_1.id]
or
self.apples << prime_apple_1
Do you have different foreign_key set in the Apple model?
has many will do a select * from apples where modela_id = X
has one will do a select * from apples where modela_id = X limit 1
Which means that whatever you'll set on the prime_apple accessor you'll get back the first Apple record ...

Related

Ruby on Rails: How can I query or filter a computed method from the controller?

I'm very new to RoR and I'm not sure if I am asking the right question. However here it is.
I have 3 models Policy, CommissionInvoice, Intermediary
Intermediary -
has_many :commission_invoices, foreign_key: :intm_no
CommissionInvoice -
belongs_to :policy, foreign_key: :policy_id
has_one :intermediary, foreign_key: :intm_no
Policy -
has_one :commission_invoice, foreign_key: :policy_id
has_one :intermediary, through: :commission_invoice, foreign_key: :intm_no
In my Intermediary model I made a method like this:
def credit_term
if type == "BR"
90
else
30
end
end
Then in my Policy model I made a method like this:
def due_date
effectivity + intermediary.credit_term if commission_invoice
end
effectivity is a column name.
And for my PoliciesController:
def due_today
#policies = Policy.order(sort_column + " " + sort_direction).includes([:commission_invoice => :intermediary]).page(params[:page]).per(10)
end
Now, my question is, how can I filter the queried data from the controller(or if you can suggest any other solution) using the computed method due_date to the Date today? Because all of the records from the very start are being displayed. I hope my question is clear. Thanks in advance. :)
You can filter using the Array select method: https://ruby-doc.org/core-2.2.0/Array.html#method-i-select
For example:
def due_today
#policies = Policy.order(sort_column + " " + sort_direction).includes([:commission_invoice => :intermediary]).page(params[:page]).per(10)
#policies = #policies.select {|p| p.due_date.present? && p.due_date <= Date.today}
end
I added a check for due_date.present? because if commission_invoice is falsey, due_date will return nil.

How to retrieve ActiveRecord records based on the presence of a related model

I have two models, ParentProfile and RoomParentAssignment:
class ParentProfile < ActiveRecord::Base
has_many :room_parent_assignments
and
class RoomParentAssignment < ActiveRecord::Base
belongs_to :room_parent_profile, class_name: 'ParentProfile', foreign_key: 'parent_profile_id'
I would like to retrieve all ParentProfile records where there are no room_parent_assignments. I'm looking for something like the following statement (which needless to say, is invalid):
#non_room_parents = ParentProfile.where(!room_parent_assignments.present?)
How would I do this?
The below query should do
ParentProfile.joins("left outer join room_parent_assignments on room_parent_assignments.parent_profile_id = parent_profiles.id").where(room_parent_assignments: {parent_profile_id: nil})
use the below code:
parent_ids = RoomParentAssignment.select(parent_profile_id)
#non_room_parents = ParentProfile.where.not(:id => parent_ids)
You have two options here:
OPTION 1
#non_room_parents = ParentProfile.where.not(id: RoomParentAssignment.all.map(&:parent_profile_id))
OPTION 2
#non_room_parents = ParentProfile.where("id NOT IN (?)", RoomParentAssignment.all.map(&:parent_profile_id))
Both of them are equal to get no parent rooms.

Rails Equivalent of SQL

Given the following models:
class PeriodBilling < ActiveRecord::Base
belongs_to :appcode
belongs_to :period
belongs_to :sla
belongs_to :unit_type
belongs_to :dpc_employee
belongs_to :general_ledger
end
class GeneralLedger < ActiveRecord::Base
has_many :appcodes
has_many :sla_task_details
belongs_to :expense_category
has_many :period_billings
has_many :expected_billings
end
How would I find the following SQL equivalent in Rails, to display as a matrix with periods down the left side and general_ledgers across the top, with the sum(pb.current_amt) in the appropriate fields?
select pb.pe_number, gl.general_ledger_number, sum(pb.current_amt)
from period_billings pb, general_ledgers gl
where pb.sla_id = 21
and pb.general_ledger_id = gl.id
group by pb.pe_number, gl.general_ledger_number
In Active Record terms, this is kinda what I'm trying to find:
#sla = Sla.find(params[:id])
#period_billings = PeriodBilling.where("sla_id = ?", #sla.id).group_by(&:general_ledger_id)
#billing_sum = #period_billings.inject(0){|sum,billing| sum+billing.current_amt}
So, I want to find all period_billings for the selected sla, grouped by general_ledger_id to get the sum of the period_billing.current_amt for those records. Then, I want to put the 12 periods down the side to show the summed amount in its appropriate general_ledger column across the top.
Thanks in advance!!! :)
Just a wild little stab at what I suggest you do. Some things are better meant for direct sql, and when that arises, like you're requesting, it's best to just use sql.
ActiveRecord::Base.connection.execute <<-EOF
select pb.pe_number, gl.general_ledger_number, sum(pb.current_amt)
from period_billings pb, general_ledgers gl
where pb.sla_id = 21
and pb.general_ledger_id = gl.id
group by pb.pe_number, gl.general_ledger_number
EOF

Ruby on Rails 3: Combine results from multiple has_many or has_many_through associations

I have the following models. Users have UserActions, and one possible UserAction can be a ContactAction (UserAction is a polymorphism). There are other actions like LoginAction etc. So
class User < AR::Base
has_many :contact_requests, :class_name => "ContactAction"
has_many :user_actions
has_many_polymorphs :user_actionables, :from => [:contact_actions, ...], :through => :user_actions
end
class UserAction < AR::Base
belongs_to :user
belongs_to :user_actionable, :polymorphic => true
end
class ContactAction < AR::Base
belongs_to :user
named_scope :pending, ...
named_scope :active, ...
end
The idea is that a ContactAction joins two users (with other consequences within the app) and always has a receiving and a sending end. At the same time, a ContactAction can have different states, e.g. expired, pending, etc.
I can say #user.contact_actions.pending or #user.contact_requests.expired to list all pending / expired requests a user has sent or received. This works fine.
What I would now like is a way to join both types of ContactAction. I.e. #user.contact_actions_or_requests. I tried the following:
class User
def contact_actions_or_requests
self.contact_actions + self.contact_requests
end
# or
has_many :contact_actions_or_requests, :finder_sql => ..., :counter_sql => ...
end
but all of these have the problem that it is not possible to use additional finders or named_scopes on top of the association, e.g. #user.contact_actions_or_requests.find(...) or #user.contact_actions_or_requests.expired.
Basically, I need a way to express a 1:n association which has two different paths. One is User -> ContactAction.user_id, the other is User -> UserAction.user_id -> UserAction.user_actionable_id -> ContactAction.id. And then join the results (ContactActions) in one single list for further processing with named_scopes and/or finders.
Since I need this association in literally dozens of places, it would be a major hassle to write (and maintain!) custom SQL for every case.
I would prefer to solve this in Rails, but I am also open to other suggestions (e.g. a PostgreSQL 8.3 procedure or something simliar). The important thing is that in the end, I can use Rails's convenience functions like with any other association, and more importantly, also nest them.
Any ideas would be very much appreciated.
Thank you!
To provide a sort-of answer to my own question:
I will probably solve this using a database view and add appropriate associations as needed. For the above, I can
use the SQL in finder_sql to create the view,
name it "contact_actions_or_requests",
modify the SELECT clause to add a user_id column,
add a app/models/ContactActionsOrRequests.rb,
and then add "has_many :contact_actions_or_requests" to user.rb.
I don't know how I'll handle updating records yet - this seems not to be possible with a view - but maybe this is a first start.
The method you are looking for is merge. If you have two ActiveRecord::Relations, r1 and r2, you can call r1.merge(r2) to get a new ActiveRecord::Relation object that combines the two.
If this will work for you depends largely on how your scopes are set up and if you can change them to produce a meaningful result. Let's look at a few examples:
Suppose you have a Page model. It has the normal created_at and updated_at attributes, so we could have scopes like:
:updated -> { where('created_at != updated_at') }
:not_updated -> { where('created_at = updated_at') }
If you pull this out of the database you'll get:
r1 = Page.updated # SELECT `pages`.* FROM `pages` WHERE (created_at != updated_at)
r2 = Page.not_updated # SELECT `pages`.* FROM `pages` WHERE (created_at = updated_at)
r1.merge(r2) # SELECT `pages`.* FROM `pages` WHERE (created_at != updated_at) AND (created_at = updated_at)
=> []
So it did combine the two relations, but not in a meaningful way. Another one:
r1 = Page.where( :name => "Test1" ) # SELECT `pages`.* FROM `pages` WHERE `pages`.`name` = 'Test1'
r2 = Page.where( :name => "Test2" ) # SELECT `pages`.* FROM `pages` WHERE `pages`.`name` = 'Test2'
r1.merge(r2) # SELECT `pages`.* FROM `pages` WHERE `pages`.`name` = 'Test2'
So, it might work for you, but maybe not, depending on your situation.
Another, and recommended, way of doing this is to create a new scope on you model:
class ContactAction < AR::Base
belongs_to :user
scope :pending, ...
scope :active, ...
scope :actions_and_requests, pending.active # Combine existing logic
scope :actions_and_requests, -> { ... } # Or, write a new scope with custom logic
end
That combines the different traits you want to collect in one query ...

Rails ActiveRecord - Best way to perform an include?

I have three models:
class Book < ActiveRecord::Base
has_many :collections
has_many :users, :through => :collections
end
class User < ActiveRecord::Base
has_many :collections
has_many :books, :through => :collections
end
class Collection < ActiveRecord::Base
belongs_to :book
belongs_to :user
end
I'm trying to display a list of the books and have a link to either add or remove from the user's collection. I can't quite figure out the best syntax to do this.
For example, if I do the following:
Controller
class BooksController < ApplicationController
def index
#books = Book.all
end
end
View
...
<% if book.users.include?(current_user) %>
...
or obviously the inverse...
...
<% if current_user.books.include?(book) %>
...
Then queries are sent for each book to check on that include? which is wasteful. I was thinking of adding the users or collections to the :include on the Book.all, but I'm not sure this is the best way. Effectively all I need is the book object and just a boolean column of whether or not the current user has the book in their collection, but I'm not sure how to forumlate the query in order to do that.
Thanks in advance for your help.
-Damien
I have created a gem(select_extra_columns) for returning join/calculated/aggregate columns in a ActiveRecord finders. Using this gem, you will be able to get the book details and the flag indicating if the current user has the book in one query.
In your User model register the select_extra_columns feature.
class Book < ActiveRecord::Base
select_extra_columns
has_many :collections
has_many :users, :through => :collections
end
Now in your controller add this line:
#books = Book.all(
:select => "books.*, IF(collections.id, 1, 0) AS belongs_to_user",
:extra_columns => {:belongs_to_user => :boolean},
:joins => "LEFT OUTER JOIN collections
ON book.id = collections.book_id AND
collections.user_id = #{current_user.id}"
)
Now in your view you can do the following.
book.belongs_to_user?
You're going to to want 2 SQL queries, and O(1) based lookups (probably irrelevant, but it's the principle) to check if they have the book.
The initial calls.
#books = Book.all
#user = User.find(params[:id], :include => :collections)
Next, you're going to want to write the books the user has into a hash for constant time lookup (if people won't ever have many books, just doing an array.include? is fine).
#user_has_books = Hash.new
#user.collections.each{|c|#user_has_books[c.book_id] = true}
And on the display end:
#books.each do |book|
has_book = #user_has_books.has_key?(book.id)
end
I'd err away from caching the book_ids on the user object, simply because going this route can have some funny and unexpected consequences if you ever start serializing your user objects for whatever reason (i.e. memcached or a queue).
Edit: Loading intermediary collection instead of double loading books.
Essentially you need to make one call to get the book information and the Boolean flag indicating if the current user has the book. ActiveRecord finders doesn't allow you to return the join results from another table. We work around this problem by doing a trick.
In your Book model add this method.
def self.extended_book
self.columns # load the column definition
#extended_user ||= self.clone.tap do |klass|
klass.columns << (klass.columns_hash["belongs_to_user"] =
ActiveRecord::ConnectionAdapters::Column.new(
"belongs_to_user", false, "boolean"))
end # add a dummy column to the cloned class
end
In your controller use the following code:
#books = Book.extended_book.all(
:select => "books.*, IF(collections.id, 1, 0) AS belongs_to_user",
:joins => "LEFT OUTER JOIN collections
ON book.id = collections.book_id AND
collections.user_id = #{current_user.id}"
)
Now in your view you can do the following.
book.belongs_to_user?
Explanation:
In the extended_book method you are creating a copy of Book class and adding a dummy column belongs_to_user to the hash. During the query extra join column is not rejected as it exists in the columns_hash. You should use the extended_book only for querying.
If you use it for CRUD operations DB will throw error.
I would first create an instance method in the User model that 'caches' the all the Book ID's in his collection:
def book_ids
#book_ids ||= self.books.all(:select => "id").map(&:id)
end
This will only execute the SQL query once per controller request. Then create another instance method on the User model that takes a book_id as a parameter and checks to see if its included in his book collection.
def has_book?(book_id)
book_ids.include?(book_id)
end
Then while you iterate through the books:
<% if current_user.has_book?(book.id) %>
Only 2 SQL queries for that controller request :)
Use exists? on the association as it is direct SQL call. The association array is NOT loaded to perform these checks.
books.users.exists?(current_user)
This is the SQL executed by Rails.
SELECT `users`.id FROM `users`
INNER JOIN `collections` ON `users`.id = `collections`.user_id
WHERE (`users`.`id` = 2) AND ((`collections`.book_id = 1)) LIMIT 1
In the above SQL current_user id = 2 and book id is 1
current_user.books.exists?(book)
This is the SQL executed by Rails.
SELECT `books`.id FROM `books`
INNER JOIN `collections` ON `books`.id = `collections`.book_id
WHERE (`books`.`id` = 3) AND ((`collections`.user_id = 4)) LIMIT 1
In the above SQL current_user id = 4 and book id is 3
For more details, refer to the documentation of the exists? method in a :has_many association.
Edit: I have included additional information to validate my answer.

Resources