I am struggling with checking the param user_id. How can I change that from => to something similar this operator: !=
And fortunately how can I do the same with <= and >=
- #jobsforyou.where(:is_finished => false, :is_active => true, :user_id => current_user, :sort <= 2).limit(10).each do |job|
The code-example gives an error.
For not you can directly do this
#jobsforyou.where.not(user_id: current_user)
For <= and >= you can use something similar to this
#jobsforyou.where("sort < ?", 3)
=> in your example is not equal or bigger than. It is the syntax of a hash. To write more complex queries you will need to use another syntax - for example the pure string syntax:
#jobsforyou.where(:is_finished => false, :is_active => true, :user_id => current_user
.where('sort <= 2')
.limit(10)
Read more about how to build queries in Ruby on Rails in the Rails Guides.
Related
No hair left on my head (and I have had lots :) ), I have been pulling out my hair and for the life of me I can't figure this out.
I have a one to many relations between 2 tables. I have installed the Datagrid Gem for reporting. I need to get the report from one model based on the other one.
Please have a look at my code.
reports_grid.rb
class ReportsGrid
include Datagrid
scope do
Land.includes(:estate)
end
filter(:estate, :enum, :select => proc { Estate.group("title").select("title").map {|c| [c.title] }})
column(:id, :header => "Land ID")
column(:current_stage, :header => "Stage")
column(:price)
column(:status)
end
reports_controller.rb
class ReportsController < ApplicationController
def index
#grid = ReportsGrid.new(params[:reports_grid]) do |scope|
if params[:reports_grid].present?
if params[:reports_grid][:estate].present?
scope.joins(:estate).where("estates.title = ? ",params[:reports_grid][:estate]).page(params[:page])
**# when I get the #grid.assets here all good and return correct number of rows**
else
scope.page(params[:page])
end
else
scope.page(params[:page])
end
end
end
end
Land.rb
belongs_to :estate
estate.rb
has_many :lands
Now when I go to /reports and try to run the filter I get the following error
PG::UndefinedColumn: ERROR: column lands.estate does not exist LINE 1: ..._id" WHERE (estates.title = 'Olive Gardens' ) AND "lands"."e... ^ : SELECT COUNT(*) FROM "lands" INNER JOIN "estates" ON "estates"."id" = "lands"."estate_id" WHERE (estates.title = 'Olive Gardens' ) AND "lands"."estate" = 'Olive Gardens'
Why is the Gem tries to add "lands"."estate" = 'Olive Gardens' to the query when I have defined it at the instance.
Please let me know if you need me to add anything. Thank you in advance.
Edit:
This is what I have done and worked in the Filter:
I have done this:
filter(:estate_id, :enum,
:select => lambda {Estate.all.map {|p| [p.title, p.id]}},
:multiple => false,
:include_blank => true
) do |value|
self.where(:lands => {:estate_id => value})
end
Do you it is a good approach?
I guess in the scope I could say Land.joins(:estate) then use the scope.all.map... in the query.
Datagrid filter designed to filter data but not to just be by default.
If you have some reason why estate should not filter data by itself then add :dummy => true option:
filter(:estate, :enum, :select => ..., :dummy => true)
But I'would recommend it. Do this instead and your hair will start growing instantly:
filter(:estate, :enum, :select => ...) do |scope, value|
scope.joins(:estate).where("estates.title = ? ", value)
end
It seems obvious from documentation here:
https://github.com/bogdan/datagrid/wiki/Filters#filter-block
Try using references
Land.includes(:estate).references(:estates)
(#products + #collections + #users + #questions).map do |r|
#results << {
:label => ["Product", "Collection", "User"].include?(r.class.name) ? r.name : r.question,
:category => r.class.name,
:href => eval("#{r.class.name.downcase}_path(r)")
}
end
I am currently looking if there is a way not to use eval on the string to convert it to a helper.
Note: This code is currently in the controller. Rails 2.3.11
Use polymorphic_path
#results = (#products + #collections + #users + #questions).map do |r|
{
:label => ["Product", "Collection", "User"].include?(r.class.name) ? r.name : r.question,
:category => r.class.name,
:href => polymorphic_path(r)
}
end
You can try something like this:
:href => self.send("#{r.class.name.downcase}_path".to_sym, r)
Since I'm not totally sure of the context here I'm not 100% confident this will work, but if this is a method you're trying to reference, then self is the most likely target for it.
There's 3 ways you can dynamically call methods with benchmarks shown here. I'l summarize the article below:
One way to invoke a method dynamically in ruby is to send a message to the object :
p s.send(:length) #=> 6
p s.send(:include?,"hi") #=> true
A second way is instantiate a method object and then call it:
method_object = s.method(:length)
p method_object.call #=> 6
method_object = s.method(:include?)
p method_object.call('hi') #=> true
And the third way is to use the eval method:
eval "s.length" #=> 6
eval "s.include? 'hi'" #=>true
According to the benchmarks results the SLOWEST is eval so I'd use send instead.
#######################################
##### The results
#######################################
#Rehearsal ----------------------------------------
#call 0.050000 0.020000 0.070000 ( 0.077915)
#send 0.080000 0.000000 0.080000 ( 0.086071)
#eval 0.360000 0.040000 0.400000 ( 0.405647)
#------------------------------- total: 0.550000sec
# user system total real
#call 0.050000 0.020000 0.070000 ( 0.072041)
#send 0.070000 0.000000 0.070000 ( 0.077674)
#eval 0.370000 0.020000 0.390000 ( 0.399442)
There are a few things you should be doing different, some of which have been alluded to or explicitly stated in other answers, but I think this does a better job:
#results = (#products + #collections + #users + #questions).map do |r|
{
:label => r.try(:question) || r.name,
:category => r.class.model_name.human,
:href => send(:"#{r.class.model_name.underscore}_path", r)
}
end
You don't need to build your result array manually — you're already using map, just assign the result to #results.
There's no need to do all the manual work of determining whether or not you should be posting the question or the name. You could give all these models a to_label method, which would be pretty easy, but assuming that questions don't have names using try and a binary or is just a dead simple way around the problem.
Single word model names convert pretty easily to display titles, but when you end up with a class like BedSheet you're going to want it to display as "Bed Sheet". Might as well do it right to start.
The other side of that same problem is converting the class name to the path/url helper method: you want bed_sheet_path, not bedsheet_path.
Using send over eval, as explained in other answers. No need to use an explicit to_sym though since Ruby supports using double-quotes to create a symbol outright.
Another quick note, I don't know if Rails 2.x was the same, but in Rails 3 you don't need to use the path helper to link to an instance of a model since most html helper methods will convert automatically (e.g. link_to 'A Product', #product).
Voila.
You could use a look-up table:
class_procs = {
Product => {
:path => lambda { |r| product_path(r) },
:label => lambda { |r| r.name }
},
Collection => {
:path => lambda { |r| collection_path(r) },
:label => lambda { |r| r.name }
}
User => {
:path => lambda { |r| user_path(r) },
:label => lambda { |r| r.name }
},
Question => {
:path => lambda { |r| question_path(r) },
:label => lambda { |r| r.question }
}
}
(#products + #collections + #users + #questions).map do |r|
procs = class_procs[r.class]
#results << {
:label => procs[:label].call(r),
:category => r.class.name,
:href => procs[:path].call(r)
}
end
And if your needs get more complicated then you could easily convert the per-class Hashes to individual classes to keep the inner Hashes from getting too big and complicated.
I want to use find_or_create_by, but this statement does NOT work. It does not "find" or "create" with the other attributes.
productproperty = ProductProperty.find_or_create_by_product_id(:product_id => product.id, :property_id => property.id, :value => d[descname])
There seems to be very little, or no, information on the use of dynamic finders in Rails 3. "and"-ing these together gives me a an unknown method error.
UPDATE:
Originally I couldn't get the following to work. Please assume I'm not an idiot and "product" is an instance of Product AR model.
product.product_properties.find_or_create_by_property_id_and_value(:property_id => 1, :value => "X")
The error methods was:
no such keys: property_id, value
I couldn't figure that out. Only this morning did I find the reference to passing the values like this instead:
product.product_properties.find_or_create_by_property_id_and_value(1, "X")
And voilá, it works fine. I would have expected a hash to work in the same situation but I guess not.
So I guess you get a down vote if you miss something on the internet?
If you want to search by multiple attributes, you can use "and" to append them. For example:
productproperty = ProductProperty.find_or_create_by_product_id_and_property_id_and_value(:product_id => product.id, :property_id => property.id, :value => d[descname])
There is one minor catch to be aware of. It will always return the object you've specified, even if that object can't be saved due to validation errors. So make sure you check to see if the returned object has an id (or is_valid?). Don't assume its in the database.
Alternatively, you can use the 'bang' version of the method to raise an error if the object cannot be saved:
http://guides.rubyonrails.org/active_record_querying.html#find-or-create-by-bang
This applies to Rails 3.
See http://api.rubyonrails.org/classes/ActiveRecord/Base.html:
With single query parameter:
productproperty = ProductProperty.find_or_create_by_product_id(product.id) { |u| u.property_id => property_id, u.value => d[descname] } )
or extended with multiple parameters:
productproperty = ProductProperty.find_or_create_by_product_id(:product_id => product.id, :property_id => property_id, :value => d[descname]) { |u| u.property_id => property_id, u.value => d[descname] } )
Would work with:
conditions = { :product_id => product.id,
:property_id => property.id,
:value => d[descname] }
pp = ProductProperty.find(:first, :conditions => conditions) || ProductProperty.create(conditions)
In Rails 4, you can use find_or_create_by(attr1: 1, attr2: 2) to find or create by multiple attributes.
You can also do something like:
User.create_with(
password: 'secret',
password_confirmation: 'secret',
confirmation_date: DateTime.now
).find_or_create_by(
email: 'admin#domain.com',
admin: true
)
If you need to create the user with some attributes, but cannot search by those attributes.
You could also use where(...).first_or_create - ActiveRecord::Relation#first_or_create.
product_property_attrs = { product_id: product.id,
property_id: property.id,
value: d[descname] }
product_property = ProductProperty.where(product_property_attrs).first_or_create
I've found in Rails 3.1 you do not need to pass the attributes in as a hash. You just pass the values themselves.
ProductProperty.find_or_create_by_product_id_and_property_id_and_value(
product.id, property.id, d[descname])
I have a few places in a model that does stuff like
def ServerInfo.starttime(param)
find(:all, :conditions => "name ='#{param}_started'", :select => "date").first.date.to_datetime
end
Now, for reasons not relevant to the question, it can happen that this particular row is not in the database at all and the code above fails with NoMethodError (undefined method `date' for nil:NilClass):. My current fix is
res = find(:all, :conditions => "name ='#{param}_started'", :select => "date")
check_time = res.first.nil? ? 0 : res.first.date.to_datetime
This works find, but I feel it's not right to sprinkle that code all over the place. Is there some more ruby-ish / rail-ish way to prevent dereferencing nil?
In order to avoid the NoMethodError for nil, you should define a begin rescue block,
def ServerInfo.starttime(param)
begin
find(:all, :conditions => "foo").first.date.to_datetime
rescue
0
end
end
I also like the Rails try method:
find(:all, :conditions => "foo").first.try(:date).try(:to_datetime) || 0
maybe this is cleaner:
check_time = res.first.date.to_datetime if res.first
btw, don't use:
:conditions => "name ='#{param}_started'" # SQL injection vulnerability.
use this one instead:
:conditions => ["name = ?", "#{param}_started"] # This is safer. Pure clean Ruby
it's safer
You may also define a scope. For instance in a Rails3 app you should try:
In your ServerInfo.rb model:
scope :starttime, lambda{|param|
if self.has_attribute?(param+'_started')
where("name = ?", param+'_started' ).select('date')
else
false
end
}
// Remember to never put your params directly in your sql query, that is bad practice since you risk some sql injection //
Then in a controller:
res = ServerInfo.starttime('a_param')
check_time = res.first.date.to_datetime if res
I didn't try that code, then you may need to adapt it to your need (or to your Rails2 app)
Here's some of my production code (I had to force line breaks):
task = Task.find_or_create_by_username_and_timestamp_and_des \
cription_and_driver_spec_and_driver_spec_origin(username,tim \
estamp,description,driver_spec,driver_spec_origin)
Yes, I'm trying to find or create a unique ActiveRecord::Base object. But in current form it's very ugly. Instead, I'd like to use something like this:
task = Task.SOME_METHOD :username => username, :timestamp => timestamp ...
I know about find_by_something key=>value, but it's not an option here. I need all values to be unique. Is there a method that'll do the same as find_or_create_by, but take a hash as an input? Or something else with similat semantics?
Rails 3.2 first introduced first_or_create to ActiveRecord. Not only does it have the requested functionality, but it also fits in the rest of the ActiveRecord relations:
Task.where(attributes).first_or_create
In Rails 3.0 and 3.1:
Task.where(attributes).first || Task.create(attributes)
In Rails 2.1 - 2.3:
Task.first(:conditions => attributes) || Task.create(attributes)
In the older versions, you could always write a method called find_or_create to encapsulate this if you'd like. Definitely done it myself in the past:
class Task
def self.find_or_create(attributes)
# add one of the implementations above
end
end
I also extend the #wuputah's method to take in an array of hashes, which is very useful when used inside db/seeds.rb
class ActiveRecord::Base
def self.find_or_create(attributes)
if attributes.is_a?(Array)
attributes.each do |attr|
self.find_or_create(attr)
end
else
self.first(:conditions => attributes) || self.create(attributes)
end
end
end
# Example
Country.find_or_create({:name => 'Aland Islands', :iso_code => 'AX'})
# take array of hashes
Country.find_or_create([
{:name => 'Aland Islands', :iso_code => 'AX'},
{:name => 'Albania', :iso_code => 'AL'},
{:name => 'Algeria', :iso_code => 'DZ'}
])