I have following models:
class Category < ActiveRecord::Base
has_many :items
default_scope where(:enabled => true, :out_of_stock => false)
scope :enabled, where(:enabled => true)
scope :out_of_stock, where(:out_of_stock => true)
end
class Item < ActiveRecord:Base
belongs_to :category
end
I've faced following code duplication, repeating scope conditions across entire project, when using joins:
Category.joins(:offers).where(:items => {:merchant_id => #merchant.id, :enabled => true, :out_of_stock => false})
It would be nice, if applying specified scope in joins will be possible:
Category.joins(:offers).where(:items => {:merchant_id => #merchant.id, :scope => :default})
Try using & like this:
Category.joins(:offers, :items) & Item.default.where(:merchant_id => #merchant.id)
I think this is called interpolation. It joins the two queries together, preserving the first one as the base (it return Category objects).
Related
Ok, I got the following simple model class:
class Baby < ActiveRecord::Base
attr_accessible :name, :born_at
...
end
And I need to collect the youngest 20 babies with unique names
Baby.all(:order => "born_at desc", :limit => 20)
But I don't know what to add to the request so the names of the babies to be unique.
Disclaimer: I'm pretty new to SQL databases, so don't judge me for my lame question.
Baby.group(:name).order('born_at desc').limit(20)
Baby.all(:order => 'born_at desc', :limit => 20, :group => :name)
Or if you need only names
Baby.select(:name).limit(20).order('born_at desc')
Given the following invoice model:
validates :po_number, :invoice_number, :invoice_date, :date_received, :state_id, :division_id, :pending_state_id, :approver_username, :approver_email, :presence => true
validates :po_number, :uniqueness => {:scope => :invoice_number}
There are times when an invoice record is canceled (state_id = 4), but then needs to be re-created.
Can you help me with how to still validate uniqueness on po_number and invoice_number so the new record can be created even though the same combination exists with a different state_id if it was canceled?
Based on what you described, I believe it may be sufficient to include the :state_id in the scope and pass an :unless (or :if) option to exclude cancelled (or any other states) from the check:
validates :po_number, :uniqueness => {
:scope => [:invoice_number, :state_id],
:unless => :cancelled?
}
# assumes an instance method like
def cancelled?
state_id == 4
end
I think you'll need a custom validator. Something like this might work:
validate :unique_po_number
def unique_po_number
errors.add(:po_number, 'must be unique') if self.class.where('state != 4').where(:po_number => po_number).count > 0
end
I've a table with an integer column called "map_id", I want to add an activeadmin filter to filter if this column IS NULL or IS NOT NULL.
How could this be implemented ?
I tried the following filter
filter :map_id, :label => 'Assigned', :as => :select, :collection => {:true => nil, :false => ''}
But, I get the following error message :
undefined method `map_eq' for #
If anyone is happening on this thread belatedly, there is now an easy way to filter for null or non null in active admin :
filter :attribute_present, :as => :boolean
filter :attribute_blank, :as => :boolean
It is no longer necessary to add a custom method to the scope to accomplish this.
Not found a good solution but here is a how.
filters of Active_admin are accomplished by meta_search, you can override the functions automatically generated by meta_search in your model to get the behavior that you want, the best way is to use the scope since you need to return a relation in order to chain with other query/scopes, as stated here
in your model:
for :as=>:select filters, acitve_admin use the _eq wheres, here is the source code
scope :map_eq,
lambda{ |id|
if(id !='none')
where( :map_id=> id)
else
where( :map_id=> nil)
end
}
#re-define the search method:
search_method :map_eq, :type => :integer
in your ative_admin register block:
filter :map_id, :label => 'Assigned', :as => :select, :collection => [['none', 'none'], ['one', 1],['tow', 2]]
# not using :none=>nil because active_admin will igore your nil value so your self-defined scope will never get chained.
Hope this help.
seems search_method doesn't work in recent rails version, here is another solution:
add scope to your model:
scope :field_blank, -> { where "field is null" }
scope :field_not_blank, -> { where "field is not null" }
add to /app/admin/[YOUR MODEL]
scope :field_blank
scope :field_not_blank
you will see buttons for these scopes appear (in top section, under model name, not in filter section)
The new version of ActiveAdmin uses Ransacker. I manage to got it working this way:
On the admin
filter :non_nil_map_id, :label => 'Assigned', :as => :select, :collection => [['none', 'none'], ['one', 1],['tow', 2]]
For consistency, I took the same code from #Gret answer just changing the filter name
On your model
ransacker :not_nil_map_id, :formatter => proc {|id| map_id != 'none' ? id : 'none' } do |parent|
parent.table[:id]
end
This should trigger a search against nil in case the id is 'none', and active record will return all the nil id entries.
This thread helped a lot.
With ransackable scopes:
On the ActiveAdmin resource definition:
filter :map_id, :label => 'Assigned', as: :select, :collection => [['Is Null', 'none'], ['Not null', 'present']]
On your model:
scope :by_map_id, ->(id) { (id == 'none' ? where(map_id: nil) : where('map_id IS NOT NULL')) }
def self.ransackable_scopes(_auth_object = nil)
%i[by_map_id]
end
My Model:
class Student < ActiveRecord::Base
has_many :lack_knowledge_points, :through => :knowledge_point_infos,
:conditions => ['knowledge_point_infos.level <= ?',10],:source => :knowledge_point
I want that 10 be dynamic
What is my best practice?
Define a method and find_by_sql? Or can Rails do something else for me?
I am not clear how you want 'level' to be dynamic. Anyway, you could use scope with lambda or define a method in the model.
#If you want it to return an array
def lack_knowledge_points(threshold)
knowledge_point_infos.where('level <= ?', threshold).map{|info|info.knowledge_point}
end
I am having trouble accessing the attributes of a join in Rails3.
There is two models/tables : Place and Address. One place can have many addresses (i.e. a specific street address, a "corner of" address, etc.). For historical reasons, the tables do not follow standard Rails conventions:
class Place < ActiveRecord::Base
set_table_name :PLACE
set_primary_key :PLACE_ID
has_many :addresses, :class_name => "Address", :foreign_key => :PLACE_ID
end
and
class Address < ActiveRecord::Base
set_table_name :ADDRESS
set_primary_key :ADDRESS_ID
belongs_to :place, :foreign_key => "PLACE_ID"
end
I am trying to get all the addresses for one particular place:
pa = Place.joins(:addresses).where(:place_id => 68)
The SQL that is generated looks fine:
pa.to_sql
"SELECT [PLACE].* FROM [PLACE] INNER JOIN [ADDRESS] ON [ADDRESS].[PLACE_ID] = [PLACE].[PLACE_ID] WHERE ([PLACE].[place_id] = 68)"
The returned relation also has the right size, as that particular place has 6 addresses associated with it:
irb(main):050:0> pa.size
=> 6
However, the returned relation pa only contains the attributes of the Place model, it doesn't contain any attributes of the Address model.
Pre Rails3 I used to do a find_by_sql and could access the attributes of the two joined table easily in the returned Hash, however I simply cannot get Rails3 to reveal to me the
attributes from the joined Address table.
I must be missing something very basic here - anyone care to point it out to me ?
You're looking for includes instead of joins. This will do exactly what you want.
pa = Place.includes(:addresses).where(:place_id => 68)
If you want to order by address.street_name in your comment to captaintokyo's answer. You can do then add an order like this:
pa = Place.includes(:addresses).where(:place_id => 68).order(:addresses => :street_name)
The difference between the two includes and joins is that joins will join the supplied models to the produced query for the sake matching in a where clause. As opposed to includes which, in addition to adding joining the model, will also include that model's fields in the select statement.
So to recap:
Place.includes(:addresses).where(:place_id => 68)
produces this:
"SELECT [PLACE].*, [ADDRESS].* FROM [PLACE] INNER JOIN [ADDRESS] ON [ADDRESS].[PLACE_ID] = [PLACE].[PLACE_ID] WHERE ([PLACE].[place_id] = 68)"
while
Place.joins(:addresses).where(:place_id => 68)
produces this:
"SELECT [PLACE].* FROM [PLACE] INNER JOIN [ADDRESS] ON [ADDRESS].[PLACE_ID] = [PLACE].[PLACE_ID] WHERE ([PLACE].[place_id] = 68)"
What you need in arel way is just like
pa = Place.joins(:addresses).where(:place_id => 68).select('PLACE.*, ADDRESS.*')
and then you will get all the attributes in pa for these two tables.
But I am wondering if there is a better design for you:
class Place < ActiveRecord::Base
set_table_name :PLACE
set_primary_key :PLACE_ID
has_many :addresses, :class_name => "Address", :foreign_key => :PLACE_ID
end
class Address < ActiveRecord::Base
set_table_name :ADDRESS
set_primary_key :ADDRESS_ID
belongs_to :place, :foreign_key => "PLACE_ID"
scope :with_place, lambda {|place_id| joins(:place).where(:place_id => place_id).select('ADDRESS.*, PLACE.*')}
end
addresses = Address.with_place(68)
or just use the delegate function if you need to use Place instance at the same time
class Address < ActiveRecord::Base
set_table_name :ADDRESS
set_primary_key :ADDRESS_ID
belongs_to :place, :foreign_key => "PLACE_ID"
delegate :any_place_attribute, :to => :place
end
address = Address.find(x)
address.any_place_attribute #you can access PLACE table attribute now