Rails Active Records using maximum and minimum simultaneously - sql

Below both the query work fines
Event.joins(:visit).where(:page_id => 3).group(:visit_id).minimum('events.time')
Event.joins(:visit).where(:page_id => 3).group(:visit_id).maximum('events.time')
I want to do find the diff maximum('events.time') - minimum('events.time') and group by visit id
For this I am writing below query
Event.joins(:visit).where(:page_id => 3).group(:visit_id).(maximum('events.time') - minimum('events.time'))
I am getting error, (undefined method maximum for main:Object)
can anyone help me out for this active records query

That's because you're trying to call maximum and minimum as though they were helper or instance methods. You have to call them on an instance of an ActiveRecord object. In your case I'm guessing that you have class Event < ActiveRecord::Base at the top of your model file.
Even if you change your code to use the ActiveRecord methods, you still have malformed Ruby code at (:visit_id).(maximum. So if the calculation performs and you get a date your code would look like this:
Event.joins(:visit).where(:page_id => 3).group(:visit_id).(SomeTimeStamp), which will throw a different error.
You'll have to rework your query either way, but the important thing to note is that you can't call minimum or maximum as helper methods.

Related

Rails ActiveRecord scope with multiple conditions

I have a Rails app where I have a Unit model and Status model. Status has_many units and Unit belongs_to Status.
I wrote a scope on Unit to find all of the Units with a specific Status, "In Service" like so:
scope :in_service, lambda { where(status_id: Status.find_by_unit_status("In Service").id)}
This allows me to call Unit.in_service.count to get a count of all Units that are In Service.
But what I really want to do is write a scope that will allow me to scope out all Units with a Status of In Service, At Post, and At Station to get an accurate view of Units since these other two Statuses are considering the Unit to be available.
I'm not sure how I would write something like this. Where the scope contains multiple conditions or data fields.
Can anyone lend a hand?
Update
I tried writing a class method called available like this:
def self.available
Unit.where(status_id: Status.find_by_unit_status("In Service").id)
.where(status_id: Status.find_by_unit_status("At Post").id)
.where(status_id: Status.find_by_unit_status("At Station").id)
end
I'm not sure if this method even is what I'm looking for since I want to find Units with each of these statuses. I think what I just wrote might be constraining the method to where the Unit must have all of these statuses.
You've got a couple things going on here. First if you want to find all units where the status is one of many, you'll need to pass that as an array like so:
scope :in_service, lambda { where(status_id: Status.where(unit_status: ["In Service", "At Post", "At Station"]).map(&:id))}
Second, in your updated method example, you're chaining a bunch of where clauses together, which will result in each one joined with an AND.

In a Rails ActiveRecord model, is using after_initialize callbacks a very bad idea?

Let's suppose we have this model
class Account < ActiveRecord::Base
after_initialize :set_name
def set_name
self.name = ‘My Account’
end
end
Now I want run a query that returns only some attributes of the model but not all of them, in particular is not returning the "name" attribute that it is used in after_initialize callback
Account.group(:name).select("count(*), id").first
And then this execution raises the following error because the set_name callback uses an attribute that has not been "loaded" or selected into the records returned by the query.
ActiveModel::MissingAttributeError: missing attribute: name
Fortunately for some particular cases I can execute the same sql query without using the Account model at all to get the desired result
sql = Account.group(:name).select("count(*), id").to_sql
ActiveRecord::Base.connection.execute(sql).first
=> #<Mysql2::Result:0x00000106eddbc0>
But the point is, what if I want to get Account objects instead of a Mysql2::Result one? Should the .select method return "complete" objects with all their attributes (e.g. filling the missing columns with Nil's)? Or is just a very bad idea to use after_initialize callbacks for our ActiveRecord models? Of course we can also add some code in the callback to check if the property exists or not but, in my opinion, this is unnatural or sounds weird working in an OO language.
Most uses of after_initialize can be (and SHOULD be) replaced with defaults on the corresponding database columns. If you're setting the property to a constant value, you may want to look into this as an alternative.
EDIT: if the value isn't constant, a call to has_attribute?(:name) will guard against this error - ActiveModel::MissingAttributeError occurs after deploying and then goes away after a while
No, it is not a bad idea, in fact I use it very often at work. The valid use case for this would be when you want code to run before you try and do anything with the object. Here is a breakdown of some of the filters offered.
# Before you intend to do anything with the object
after_initialize
# Before you intend to save the object
before_save
# After you've saved the object
after_save
# Before you save a new record
before_create
# After you create a new object
after_create

Finding Records using Rails without a Match (Fault without Fault Cleared)

I'm attempting to write a site in Rails where a user in a manufacturing plant can see what devices are failing. The program storing the alarm data stores one entry when a device faults, and then stores another entry when the device gets fixed. The entries are linked only by having the same value in the EventAssociationID column. How might I write a named scope in Rails to check which faults have been fixed and which ones haven't?
I wasn't able to do it in a named scope, however, I was able to define a method for the model that solved the problem:
def inAlarm
return ConditionEvent.count(:all, :conditions => ['EventAssociationID = ?', self.EventAssociationID]) == 1
end

ActiveRecord Delete...?

I'm just starting to learn ActiveRecord, and I am just trying out little things to figure out how everything works. I just tried the following code on the following sqlite3 database.
Ruby:
class Balances < ActiveRecord::Base
def initialize
#balance = 50
update_attribute(:balance, #balance)
end
def withdraw amount
update_attribute(:balance, #balance-amount)
end
end
SQL:
CREATE TABLE balance(
balance 50
);
When I write:
balance = Balances.new
I Get:
NoMethodError: undefined method `delete' for nil:NilClass
from /Users/Solomon/.rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.3/lib/active_record/attribute_methods/write.rb:28:in `write_attribute'
from /Users/Solomon/.rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.3/lib/active_record/attribute_methods/dirty.rb:67:in `write_attribute'
from /Users/Solomon/.rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.3/lib/active_record/attribute_methods/write.rb:14:in `balance='
from /Users/Solomon/.rvm/gems/ruby-1.9.3-p125/gems/activerecord-3.2.3/lib/active_record/persistence.rb:180:in `update_attribute'
Why is this? Am I doing something wrong?
I notice several things:
The class name should be Balance (capitalized, singular). The table name in the database will be lower case, plural. Eg, balances
Don't define an initialize method for an ActiveRecord model. Instead use after_initialize callback. A post. Also, the Rails docs.
Added Also, the file name for the model should be balance.rb (lower case, singular)
Added some more You probably don't want to change the balance of the record back to 50 every time an instance of the record is initialized. -- That's what your example is currently doing. If you want to set the opening balance of new records in the database to be 50, then use the "before_create" callback.
Remember that ActiveRecord model classes are associated with, but different from, the records in the underlying database. For example, you can create an instance of an ActiveRecord model, and then not create a matching record in the database. -- The database record will only be created when and if you call the save or create methods.

Rails3: Cascading Select Writer's Block

I have a big, flat table:
id
product_id
attribute1
attribute2
attribute3
attribute4
Here is how I want users to get to products:
See a list of unique values for attribute1.
Clicking one of those gets you a list of unique values for attribute2.
Clicking one of those gets you a list of unique values for attribute3.
Clicking one of those gets you a list of unique values for attribute4.
Clicking one of those shows you the relevant products.
I have been coding Rails for about 4 years now. I just can't unthink my current approach to this problem.
I have major writer's block. Seems like such an easy problem. But I either code it with 4 different "step" methods in my controller, or I try to write one "search" method that attempts to divine the last level you selected, and all the previous values that you selected.
Both are major YUCK and I keep deleting my work.
What is the most elegant way to do this?
Here is a solution that may be an option. Just off the top of my head and not tested (so there is probably a bit more elegant solution). You could use chained scopes in your model:
class Product < ActiveRecord::Base
scope :with_capacity, lambda { |*args| args.first.nil? ? nil : where(:capacity=>args.first) }
scope :with_weight, lambda { |*args| args.first.nil? ? nil : where(:weight=>args.first) }
scope :with_color, lambda { |*args| args.first.nil? ? nil : where(:color=>args.first) }
scope :with_manufacturer, lambda { |*args| args.first.nil? ? nil : where(:manufacturer=>args.first) }
self.available_attributes(products,attribute)
products.collect{|product| product.send(attribute)}.uniq
end
end
The code above will give you a scope for each attribute. If you pass a parameter to the scope, then it will give you the products with that attribute value. If the argument is nil, then the scope will return the full set (I think ;-). You could keep track of the attributes they are drilling down in in the session with 2 variables (page_attribute and page_attribute_value) in your controller. Then you call the entire chain to get your list of products (if you want to use them on the page). Next you can get the attribute values by passing in the set of products and the attribute name to Product.available_attributes. Note that this method (Product.available_attributes) is a total hack and would be inefficient for a large set of data, so you may want to make this another scope and use :select=>"DISTINCT(your_attribute)" or something more database efficient instead of iterating thru the full set of products as I did in the hack method.
class ProductsController < ApplicationController
def show
session[params[:page_attribute].to_sym] = params[:page_attribute_value]
#products = Product.all.with_capacity(session[:capacity]).with_weight(session[:weight]).with_color(session[:color]).with_manufacturer(session[:manufacturer])
#attr_values = Product.available_attributes(#products,params[:page_attribute])
end
end
Again, I want to warn you that I did not test this code, so its totally possible that some of the syntax is incorrect, but hopefully this will give you a starting point. Holla if you have any questions about my (psuedo) code.