I have a MailingList model that has_may :people
For most of my application, I only want to get people that are active
So #mailing_list.people should only return people that are active
In my model, I can't do
def people
self.people.find_all{ |p| !p.activated_at.nil? }
end
because that keeps calling itself. What is the ruby/rails way to automatically filter the people. Another possible issue is that I think self.people returns an array of active record objects where self.people.find_all... will return an array. This will cause some of my code to break. It's easy fixes but is there a way to return active record objects? It would be nice to have the option.
Thanks!
This is a perfect example for a named scope:
class Person < ActiveRecord::Base
named_scope :active, :conditions => 'activated_at is not null'
end
Then just call it:
# equivalent to Person.find(:all, :conditions => 'activated_at is not null')
#active_people = Person.active
You can also filter at the association level.
has_many :people, :conditions => {:activated => true}
You can used the standard find method or a dynamic finder. Your find might read as follows:
people.find(:all, :conditions => "activated_at = nil")
OR
people.find_all(:conditions => "activated_at = nil")
A dynamic version of this might read as:
people.find_by_activated_at(nil)
Related
I am upgrading a Rails 3 app to rails 5.1. In Rails-3 model I have a condition like
has_one :current_contract, :class_name => 'Contract',
:conditions => Proc.new { "contract_vw.country = '#{country_id}'"
if country_id }
I guess previous developer follow this hack
https://makandracards.com/makandra/983-dynamic-conditions-for-belongs_to-has_many-and-has_one-associations
I am not sure how to convert it to Rails 5.1
Any help appreciated.
The Rails 4+ way is to write the scope like so:
has_one :account, -> (country_id) { where('contract_vw.country = ?', country_id) }, class_name: 'Contract'
You've written the if country_id into the association though, which seems real weird to me. Although where('contract_vw.country = ?', country_id) if country_id might work, I'd probably extract that into a method like:
def country?
country_id.present?
end
And then, wherever you need it:
#model.account if #model.country?
If I understand your use-case correctly you're not bound to using has_one and in this instance I think it should not be used, use a regular method instead.
def current_contract
contracts.where("contract_vw.country = ?", country_id).first if country_id.present?
# or ...
contracts.find_by(country: country_id)
end
I have Person<ActiveRecord::Base model
class Person < ActiveRecord::Base
has_many :sent_messages, :class_name => 'Message', :inverse_of => :sender
has_many :received_messages, :class_name => 'Message', :inverse_of => :receiver
#.....
end
and another modeActiveRecord::Base model
class Message < ActiveRecord::Base
belongs_to :sender, :class_name => 'Person'
belongs_to :receiver, :class_name => 'Person'
# ......
end
; and ActiveRecord::Migration class supporting these models. Now my problem is that I need to output the messages received by current user with the Person name of sender by querying database, but while querying it seems that some of the person(user) who sent the messages has been deleted from database(but messages once exchanged b/w two persons is saved permanently in database and no body have the permission to delete it, So each message will be there with all the details like receiver_id and sender_id .). So when I query like this.
#messages = Message.where(:receiver_id => current_user.id)
.includes(:sender).order("updated_at DESC")------------(1)
it works fine if person who has sent the message to currently logged in user has it's delete_at attribute null(meaning not deleted from database and can be used without any error in views for sender name) but for those which has been deleted it gives this error in browser console
in application controller asset is undefined method or variable ----------(2)
where a method in application controller to handle no record found exception
def respond_to_not_found(*types)
flash[:warning] = t(:msg_asset_not_available, asset)
respond_to do |format|
format.html { redirect_to(redirection_url) }
# ...
end
end
please tell What is the t and asset is in answer ???
So as a workaround of problem (1) -------- (3).
I queried this and
def show_list
#messages = Array.new
#person = Array.new
messages_temp = Message.where(:receiver_id => current_user.id).
includes(:sender).order("updated_at DESC")
i = 0
messages_temp.each do |msg|
if(!(msg.sender.nil?))
#messages[i] = msg
#person[i] = msg.sender
i = i+1
end
end
end
My questions:-
Q.1) I haven't much worked with database so this is valid question for me(as the project I am working on explicitly destroys the requested user):- Why the user info has not been deleted from database but rather it's deleted_at attribute has become not null whereas for others which are intact have same attribute null. And when I find(query by method find) an object of Person from database then while querying it's SQL conversion query for deleted_at attribute should be null. So why is happening am I missing something in my project code or it is general behaviour. If it general then how to completely expunge the data.
Even if such behaviour is general or not how to recover it, without manually changing each deleted Person deleted_at attribute to null. And how to access some of it's attribute without fully restoring the Person(or if such thing is possible).
Q.2) What might be other reasons for getting error #(2) even after workaround. As I tested the workaround and it worked fine for some of deleted sender's. But even after at some places I am getting error #(2) so in general what does this error stand for and what might be it's other cause then the one I mentioned???
Q.3)Is there better solution then the workaround given in eq #(3) because you see in my workaround msg.sender.nil? is true for the deleted object so I think there might be some. I tried the net but with no success. So, how to filter out those messages whose associated object is deleted how to query them all at once then doing it one by one as in #(2).
As workaround is giving me hard time to paginate the output because all the available pagination gem (for example 'will_paginate', 'kaminari', 'pagination') works on ActiveRelation but in my workaround I have Array object i.e #messageson which I can not use these methods. So it would be great help if one can answer a way to paginate my workaround i.e object #messages which is an array or a way to filter out those messages whose sender is deleted from database then I can paginate in this way :-
#messages = Message.<ActiveRecord::Base method to get the desired output>.paginate[params]
PS:- I know the question is tedious but I believe it is essential for this question I am seeking answer to. Any help will be appreciated even the partial answer to the question.
Thanks a lot!!!
Ok...
Well it took me couple of days but with some help from my vicinity I did figure out answer to most of the confusion and ambiguity:
Ans:-
1:) There is a way to soft delete the data meaning hidden from normal query... Most of ambiguities of question one can be answered from this [link][1]. This gem(acts_as_paranoid) helps soft deleting the data. yes, attributes of soft deleted object can be accessed without restoring the date; and data can be completely expunged as well(everything in the link). The changes I needed to make in my code base to avail these benefits are these:-
In Person<ActiveRecord::Base model
class Person < ActiveRecord::Base
# has_many :sent_messages, :class_name => 'Message', :inverse_of => :sender
# has_many :received_messages, :class_name => 'Message', :inverse_of => :receiver
has_many :sent_messages, :class_name => 'Message',
:inverse_of => [:sender, :sender_including_deleted]
has_many :received_messages, :class_name => 'Message',
:inverse_of => [:receiver, :receiver_including_deleted]
#.....
end
and in the model ActiveRecord::Base
class Message < ActiveRecord::Base
belongs_to :sender, :class_name => 'Person'
belongs_to :receiver, :class_name => 'Person'
# Add these two lines:-
belongs_to :sender_including_deleted, :class_name => 'Person',
:foreign_key => 'sender_id', :with_deleted => true
belongs_to :receiver_including_deleted, :class_name => 'Person',
:foreign_key => 'receiver_id', :with_deleted => true
# ......
end
So, adding these two lines worked for me but most importantly the gem helped me a lot.
2:) One of prominent reason for getting error in (2) is that one may be trying to directly access the attribute of object which may be deleted. Suppose a Person is deleted(soft or hard doesn't matter whose id was 15) so first_name = Person.find(15).first_name will give error where as person = Person.find(15) if(!person.nil?) return first_name will not as we are checking if returned person is nil or not. and if Person is deleted with gem acts_as_paranoid then a soft deleted(meaning deleted_at attribute is not null. The deleted_at(this attribute has to be added in migration to use) attribute is updated when object is deleted for clear picture see the embedded link here) object accessed first_name = Person.with_delete.find(15) like this won't generate an error
Note:- I don't what might be other reason. please answer this part if you can think of any.
3:) Although I do not have direct answer to question asked in 3 but since now I can access the message sender info even though they are deleted(in active relation form) so ordering and pagination works but I surely like to know how to paginate if object in not active relation.
So, this one I best answered like this:-
def show_list
# #messages = Array.new
# #person = Array.new
# messages_temp = Message.where(:receiver_id => current_user.id).
# includes(:sender).order("updated_at DESC")
# i = 0
# messages_temp.each do |msg|
# if(!(msg.sender.nil?))
# #messages[i] = msg
# #person[i] = msg.sender
# i = i+1
# end
# end
# REPLACE ABOVE CODE BY THIS. THe following code will
# include deleted messages as well.
#person = Array.new
#messages = Message.where(:receiver_id =>current_user.id).
includes(:sender_including_deleted).
order("created_at DESC").paginate(params).readonly
i = 0
#messages.each do |msg|
#person[i] = msg.sender_including_deleted(:with_deleted => true)
i = i + 1
end
end
I hope the people who stumbled upon this question get what they are looking. If they didn't get any part of my answer or question then they are welcome to comment here and I will be happy to answer to best of my abilities.
I have 2 models. Report and Server that have a belongs_to and has_many relationship. I created an accessor method using delegate that allows a Report to find its associated Server.company_id. Now, I want to run a query on Report that allows me to find all Report that are associated with a specific Server that has a specific company_id attribute of 5.
Here are my two models. And yes I know the current query wont work since Report does not have an attribute company_id.
And no, I dont want to store company_id inside of Report since that information doesn't belong in Report.
Report
class Report < ActiveRecord::Base
belongs_to :server
delegate :company_id, :to => :server
class << self
def method(url, base_url)
#Report.where(company_id: 5)
end
end
end
Server
class Server < ActiveRecord::Base
attr_accessible :company_id
has_many :reports
end
You can perform a query like this:
Report.joins(:servers).where(:servers => {:company_id => 5})
To me, this is the cleaner solution to raw SQL.
I'm using Rails 4.1.7 and the accepted answer did not work for me. What did work is
Report.joins(:server).where(:servers => {:company_id => 5})
Note that the difference is the key in the where clause is pluralized (:servers instead of :server)
This should do the trick
Report.joins(:server).where('servers.company_id = ?', 5)
you could also add a scope for this like so
scope :with_company_id, lambda {|id| joins(:server).where('servers.company_id = ?', id) }
and then write
Report.with_company_id(5)
This maybe an overkill but we have Arel:
# Get the SQL
# arelise will return the SQL (string)
def arelise(company_id)
# Tables to query
reports = Report.arel_table # Main table
servers = Server.arel_table.alias('servers')
reports
.join(servers, Arel::Nodes::InnerJoin).on(reports[:server_id].eq(servers[:id]))
.where(servers[:company_id].eq(company_id))
# Select all (*)
.project(reports[Arel.star])
.to_sql
end
sql = arelise(1)
# puts sql
# To Execute:
reports = Report.find_by_sql(sql)
# puts reports
I'm looking to define a method that lets me pass options; something like:
#user.tasks(:completed => true)
I thought something like this would work in my user model (but it's not):
User.rb model
def tasks(options)
tasks.find(:all, options)
end
How would I define the method correctly to let me use #user.tasks(:completed => true)?
This is basically how I'd do it:
def tasks(options={})
unless options[:something].blank?
# do stuff
end
end
There are some different ways to pass options, but you definitively want to pass a hash with a default value (so that you can call the method without options).
In your case the following should address what you want to do:
def tasks(options={})
Task.find(:all, options[:conditions])
end
Edit: and then call it #thing.tasks( {:conditions => "blah"} )
I haven't tested but it should be ok
Edit 2: But like EmFi said it's not optimal to do this. Consider using an association instead. You'll be able to go #thing.tasks.find(:all, :conditions => {blah})
Does User have a has_many :tasks association? That seems to be what you're after here. In that case Rails provides finders for you, which you can access like this:
#user.tasks.find :all, :conditions => { :completed => true }
Or even shorter:
#user.tasks.all :conditions => { :completed => true }
If that's not terse enough and you always want to use a particular condition, try a named scope:
# In your Task model:
named_scope :completed, :conditions => { :completed => true }
# Then you can just call...
#some_user.tasks.completed # => Only completed Tasks for #some_user
Why would you associate a find all on another model with an instance method? I could understand if it was a relation and the find required find options based on the calling record. But there's ActiveRecord Associations for that.
Then there's ActiveRecord::Base#all(options) which is an alias for Task.find(:all, options)
Together make things simpler:
class User < ActiveRecord::Base
has_many :tasks
end
#user.tasks.all(:conditions => {:completed => true})
what you need is:
options[:conditions] in your method
Activerecord provides a method called with_scope, so to pass any additional conditions
#user.tasks(:completed => true)
you can define the task method as
def tasks(options={})
with_scope :find => options
User.all :order => 'id desc'
end
end
and this will merge any hash passed as options parameter with the actual find
The only caveat is you need to modify your method call slightly
#user.tasks(:conditions => {:completed => true})
or to something like
#user.tasks(:select => 'username')
But if there is an association between user and tasks model then I would do what Jordan has in his post
I'm working on a legacy database that is complete non-sense. I have a table called movie that contains columns with names like c00, c01, c02 and so on. The table also uses non-standard primary_keys. So I've created a class called movie like this:
class Movie < ActiveRecord::Base
set_table_name "movie"
set_primary_key "idMovie"
belongs_to :media_file, :foreign_key => "idFile"
def title
self.c00
end
def plot
self.c01
end
end
I'd like to be able to do something like Movie.find_by_title("Die Hard") and have it return the right result. Also I'd like to be able to say Movie.create(:title => "Die Hard"). How do I do this?
I think you want alias_attribute. Check out Brian Hogan's excellent presentation from RailsConf this year.
You really just need a combination of Sarah's answer and Ben's answer:
class Movie < ActiveRecord::Base
# gives you Movie.find_by_title
# and lets you chain with other named scopes
named_scope :find_by_title, lambda { |title| { :conditions => { :c00 => title } } }
# gives you
# movie.title
# movie.title=(title)
# and
# Movie.new(:title => title)
alias_attribute :title, :c00
end
The find_by_* methods use reflection, so it just isn't going to happen with Rails out-of-the-box. You can, of course, define your own methods:
def self.find_by_title(title)
first(:conditions => { :c00 => title })
end
The next step would be to iterate over a hash of column_aliases => real_columns to use as fodder for calls to alias_attribute and define_method.