Access rails includes with has_many - ruby-on-rails

I have a model called "Match" and a model called "Bet"
class Match < ActiveRecord::Base
...
has_many :bets
end
And my Model Bet:
class Bet < ActiveRecord::Base
attr_accessible :match_id, :user_id, :bet
...
belongs_to :match
belongs_to :user
end
I'm using the following code to select some matches and user's bets together:
#matches = Match.includes(:bets).where("bets.user_id = ? or bets.user_id is NULL", #user.id)
How can I access user bets with this query?
Using this does not work:
#matches.each do |match|
match.bet.bet
...
How to access bet attribute inside match?
Thanks!!
Trying #sevenseacat answer with this code:
#user ||= User.find_by_id(params[:user_id]) if params[:user_id]
if #user
#matches = Match.includes(:home_team, :away_team, :bets).where("bets.user_id = ? or bets.user_id is NULL", #user.id) #.group_by{ |match| match.date.strftime("%d/%m/%y")}
#matches.each do |match|
match.bets.each do |bet|
bet.bet
end
end
end
I've changed it to match.bets.first (I only have 1 bet for each match_id and user_id so it works).

You would access each match's bets by doing simply match.bets inside your block.
If you wanted to iterate over those bets, use another each.

#sevenseacat is right
#match.bet.each { |bet| bet.attr } maybe good for you

Related

Caching association that was in where clause

Let me show an example:
I have 2 models:
class User < ActiveRecord::Base
has_many :posts
end
class Post < ActiveRecord::Base
belongs_to :user
scope :created_in, ->(start_date, end_date) { where(created_at: start_date..end_date) }
end
What I want is to get users that created post during a specific period:
users = User.includes(:posts).joins(:posts).merge(Post.created_in(start_date, end_date))
Is it somehow possible to cache posts that are in the where clause? So after I do
users.first.posts
it will show me exactly those posts that match the condition without producing any additional queries.
No, I don't think this is possible. Depending on the context, what you can do is to do a lookup table which you memoize / cache. Something like
User.all.each do |user|
posts = posts_by_user_id[user.id]
end
def posts_by_user_id
#_posts_by_user_id ||= posts.group_by(&:user_id)
end
def posts
Post.created_in(start_date, end_date)
end

Show results depending on association

I am building a landlord mgt application. But I am having an issue - I only want to index requests where the house.id (association value) matches the current user.
Currently, I have got it to work depending if the user has created it. But I would like the landlord to see all requests that are related to their properties.
requests_controller.rb:
def index
unless current_user.estate_agent?
#requests = Request.where(user_id: current_user).search(params[:search])
else
#requests = Request.search(params[:search])
end
end
request.rb
class Request < ActiveRecord::Base
belongs_to :user
belongs_to :house
def self.search(search)
key = "%#{search}%"
if search
where('description LIKE ? OR title LIKE ?', key, key)
else
all
end
end
end
house.rb
class House < ActiveRecord::Base
belongs_to :user
belongs_to :tenant
has_many :requests
belongs_to :contract
mount_uploader :energy, EnergyUploader
validates_presence_of :house_title, :description, :doorno, :postcode, :price, :bedroom, :house_type
def full_house_name
"#{doorno} #{house_title}"
end
def self.search(search)
key = "%#{search}%"
if search
where('doorno LIKE ? OR house_title LIKE ? OR price LIKE ? OR postcode LIKE ? OR house_type LIKE ? OR bedroom LIKE ?', key, key, key, key, key, key)
else
all
end
end
end
If I understood you correctly, you want to show all requests that were made by the user or made for his real-state.
The code below will create your initial filter, then you can call your search method over it.
Request.where(user_id: current_user.id).or(Request.where(house: { user_id: current_user.id}))

Custom belongs_to attribute writer

An application I'm working on, is trying to use the concept of polymorphism without using polymorphism.
class User
has_many :notes
end
class Customer
has_many :notes
end
class Note
belongs_to :user
belongs_to :customer
end
Inherently we have two columns on notes: user_id and customer_id, now the bad thing here is it's possible for a note to now have a customer_id and a user_id at the same time, which I don't want.
I know a simple/better approach out of this is to make the notes table polymorphic, but there are some restrictions, preventing me from doing that right now.
I'd like to know if there are some custom ways of overriding these associations to ensure that when one is assigned, the other is unassigned.
Here are the ones I've tried:
def user_id=(id)
super
write_attribute('customer_id', nil)
end
def customer_id=(id)
super
write_attribute('user_id', nil)
end
This doesn't work when using:
note.customer=customer or
note.update(customer: customer)
but works when using:
note.update(customer_id: 12)
I basically need one that would work for both cases, without having to write 4 methods:
def user_id=(id)
end
def customer_id=(id)
end
def customer=(id)
end
def user=(id)
end
I would rather use ActiveRecord callbacks to achieve such results.
class Note
belongs_to :user
belongs_to :customer
before_save :correct_assignment
# ... your code ...
private
def correct_assignment
if user_changed?
self.customer = nil
elsif customer_changed?
self.user = nil
end
end
end

Rails select value into attribute

Say I have a simple model like this with a field called "name" and an attribute called "aliased_name":
class User < ActiveRecord::Base
attr_accessor :aliased_name
end
User.create(name: "Faye Kname")
I can do:
user=User.select(:id, :name)
user.name # Faye Kname
But how can I use select to populate the aliased_name attribute.
user=User.select(:id, "name AS aliased_name")
user.aliased_name # nil
user[:aliased_name] # Faye Kname
I can access on the :aliased_name symbol, but the attribute is not assigned. I'd like to not have to do
user.aliased_name = user[:aliased_name]
I'm actually doing a more complex join on another table and I'm trying to select a field from the join table into the alias, but figured this would be a simpler example.
Typically I do these kinds of aliases with methods instead of attr_accessors. Something like
def aliased_name
has_attribute?(:aliased_name) ? read_attribute(:aliased_name) : self.name
end
The has_attribute? is there in case you didn't load the attribute with your query, so you can have a default value.
So the attr_accessor is looking for the instance variable #aliased_name which I don't think is being set in your code. You can set it with #aliased_name = "some value" or using the attr_accessor aliased_name = "some value", but it's not going to be set with the initial query that returns the object, or in the second SELECT query, at least as it's written now.
One route that might make sense would be to use both a separate method and attr_writer. Something like this
attr_writer :aliased_name
def aliased_name
#aliased_name ||= self.name
end
This sets the instance variable the first time it's called and leaves you free to change it with the attr_writer. I'm not sure how this fits in with the more complex join, but this is a fairly simple way to solve the problem you describe initially.
You may be better using alias_attribute:
#app/models/user.rb
class User < ActiveRecord::Base
alias_attribute :aliased_name, :name
end
Although it will only take user.name data & put it into user.alias_attribute
I'm trying to select a field from the join table into the alias
Done this before:
Rails Scoping For has_many :through To Access Extra Data
Accessing additional values on has_many through Rails
You have two options. Either use an SQL ALIAS column, or access the proxy_association method in your model. I have worked extensively with both:
--
SQL Alias
#app/models/parent.rb
class Parent < ActiveRecord::Base
has_many :joins
has_many :children, -> { select("#{Parent.table_name}.*, #{Join.table_name}.attr AS alias_name") }, through: :joins, dependent: :destroy
end
This will give you...
#parent.children.each do |child|
child.alias_name
end
--
Association Extensions
The next method is a lot more complicated; more efficient:
#app/models/parent.rb
class Parent < ActiveRecord::Base
has_many :joins
has_many :children, through: :joins, -> { extending AliasAttribute }
end
#app/models/concerns/alias_attribute.rb
module PlayerPermission
#Load
def load
alias_names.each do |permission|
proxy_association.target << permission
end
end
#Private
private
#Names
def names
return_array = []
through_collection.each_with_index do |through,i|
associate = through.send(reflection_name)
associate.assign_attributes({name: items[i]})
return_array.concat Array.new(1).fill( associate )
end
return_array
end
#######################
# Variables #
#######################
#Association
def reflection_name
proxy_association.source_reflection.name
end
#Foreign Key
def through_source_key
proxy_association.reflection.source_reflection.foreign_key
end
#Primary Key
def through_primary_key
proxy_association.reflection.through_reflection.active_record_primary_key
end
#Through Name
def through_name
proxy_association.reflection.through_reflection.name
end
#Through
def through_collection
proxy_association.owner.send through_name
end
#Captions
def items
through_collection.map(&:name)
end
#Target
def target_collection
#load_target
proxy_association.target
end
end
Each time you call an association, you have access to the .association object for it. Within the association itself, you have access to proxy_association objects; all of which can be manipulated to insert the aliased data into your parent data.
The above will allow you to use:
#parent = Parent.find x
#parent.children.each do |child|
child.alias_name
end
I can provide support if required.

How to combine two scopes with OR

I have a tag feed and a friend feed.
I want to combine these two and build the ultimate "all" feed.
For friend feed:
class Post < ActiveRecord::Base
scope :friendfeed, lambda{|x| followed_by}
def self.followed_by(user)
where("user_id IN (?) OR user_id = ?", user.watched_ids, user.id)
end
end
For tag feed:
class Post < ActiveRecord::Base
scope :tagfeed, lambda{|x| infatuated_with}
def self.infatuated_with(user)
joins(:attachments).where("attachments.tag_id IN (?)", user.tags).select("DISTINCT pages.*")
end
end
And I would call something like this from the controller (I'm using Kaminari gem for pagination):
#tag_feed = Post.tagfeed(current_user).page(params[:page]).per(21)
#friend_feed = Post.friendfeed(current_user).page(params[:page]).per(21)
Now I want to have a universal feed, but I'm lost. Scopes are meant for narrowing down, but in this case I'm trying to do an OR operation. Doing stuff like
#mother_of_all_feed = #tag_feed + #friend_feed
would be redundant, and I wouldn't be able to control the number of posts appearing on a single page. How can I go about doing this? Thanks!
By the way, for tags I have association set up like this:
class Post < ActiveRecord::Base
has_many :attachments
has_many :tags, :through => :attachments
end
class Tag < ActiveRecord::Base
has_many :attachments
has_many :posts, :through => :attachments
end
class Attachment < ActiveRecord::Base
belongs_to :tag
belongs_to :post
end
There's a rails pull request for this feature (https://github.com/rails/rails/pull/9052), but in the meantime, some one has created a monkey patch that you can include in your initializers that will allow you to or scopes and where clauses in one query and still give you an ActiveRecord::Relation:
https://gist.github.com/j-mcnally/250eaaceef234dd8971b
With that, you'd be able to OR your scopes like this
Post.tagfeed(current_user).or.friendfeed(current_user)
or write a new scope
scope :mother_of_all_feed, lambda{|user| tagfeed(user).or.friendfeed(user)}
Answering my own question. I think I figured out a way.
where("pages.id IN (?) OR pages.id IN (?)",
Page.where(
"user_id IN (?) OR user_id = ?",
user.watched_ids, user.id
),
Page
.joins(:attachments)
.where("attachments.tag_id IN (?)", user.tags)
.select("DISTINCT pages.*")
)
It seems to be working so far, hope this is it!
Here's an example of how I combined two scopes.
scope :reconcilable, -> do
scopes = [
with_matching_insurance_payment_total,
with_zero_insurance_payments_and_zero_amount
]
where('id in (?)', scopes.flatten.map(&:id))
end

Resources