I have a Rails 3.2.18 app that I'm having a problem with.
Basically each record is a Call, and each Call has many units. A unit being a vehicle which has a status such as (In service, Out of Service, etc).
When creating a call I want only the units that are marked as In Service to be listed for dispatch. So I created a scope on the Unit model that scopes that:
Unit.rb
scope :in_service, lambda { where(status_id: Status.find_by_unit_status("In Service").id)}
In my Calls Helper I have a method which selects the Units and appends their unit type and whether or not they are active on another call so dispatchers won't be tempted to double-dispatch a unit.
calls_helper.rb
def unit_select
Unit.active.order("unit_name").map{|unit| unit.calls.where(call_status: "open").empty? ? ["#{unit.unit_name} #{unit.unit_type.unit_type}", unit.id] : ["#{unit.unit_name} (on call) #{unit.unit_type.unit_type}", unit.id] }
end
Here's where the problem comes in. If I change the helper method to the following:
def unit_select
Unit.active.in_service.order("unit_name").map{|unit| unit.calls.where(call_status: "open").empty? ? ["#{unit.unit_name} #{unit.unit_type.unit_type}", unit.id] : ["#{unit.unit_name} (on call) #{unit.unit_type.unit_type}", unit.id] }
end
Appending the in_service scope method to the unit_select helper, it does indeed select only units that are in service. This is fine, but when you go to edit the call and if the unit has changed its status since the calls creation, the unit is then pulled off the record in the form
_form.html.erb
<%= f.select(:unit_ids, unit_select, {}, {:multiple => true, :class => 'select'}) %>
I get why this is happening, as the form is selecting based off of units that have a status of "In Service", but I need to figure out how to get the Unit assignment to persist in the form.
So with some help from a friend we came up with this for the helper method:
def unit_select
(#call.units.to_a + Unit.active.in_service.order("unit_name")).map{|unit| unit.calls.where(call_status: "open").empty? ? ["#{unit.unit_name} #{unit.unit_type.unit_type}", unit.id] : ["#{unit.unit_name} (on call) #{unit.unit_type.unit_type}", unit.id] }
end
Now this works, and the Unit persists in the form when you edit regardless of the Unit's status. Here's where things get weird.
Let's say you edit the record and want to pull the unit off and assign a different unit. It won't allow you to do that. It will continue to keep the value of #call.unit_ids no matter what I do.
Here is what the association looks like:
Call.rb
has_many :call_units
has_many :units, through: :call_units
Unit.rb
has_many :call_units
has_many :calls, through: :call_units
On top of this I found another problem when creating a return call. (Basically taking the original call data and creating a return trip based off the initial call. When creating a return trip it will assign the same unit_id to the call twice in an array. So it looks like there are two units on the call but they are really the same unit listed twice. Here is what my return action looks like in the controller.
calls_controller.rb
def new_return
original_call = Call.find(params[:id])
#call = Call.new(
caller_name: original_call.caller_name,
special_equipment_ids: original_call.special_equipment_ids,
call_status: "open",
caller_phone: original_call.caller_phone,
transfer_from_id: original_call.transfer_to_id,
transfer_from_other: original_call.transfer_to_other,
facility_from_location: original_call.facility_to_location,
transfer_to_id: original_call.transfer_from_id,
transfer_to_other: original_call.transfer_from_other,
facility_to_location: original_call.facility_from_location,
patient_name: original_call.patient_name,
patient_age: original_call.patient_age,
patient_dob: original_call.patient_dob,
patient_sex_id: original_call.patient_sex_id,
insurance_id: original_call.insurance_id,
nature_id: original_call.nature_id,
service_level_id: original_call.service_level_id,
special_equipment_ids: original_call.special_equipment_ids,
transfer_date: original_call.transfer_date,
unit_ids: original_call.unit_ids,
wait_return: "yes",
parent_call_id: params[:id],
)
end
So to summarize, I have found a halfway working way to limit only In Service units to be dispatched onto calls but face the following problems;
Cannot remove a unit from the call
When editing a call a unit won't display in the form unless the unit is In Service status.
When creating a return call, it duplicates the unit_id into an array in #call.unit_ids so the unit displays on the call twice.
So I'd like to be able to select only In Service units on a new call, have the unit show up in the form when editing regardless of its status, and also on the return call only have the unit_ids contain one instance of the unit_id that is supposed to be assigned.
I know this is really confusing and would be happy to pair up with someone, chat, or build a gist so you can see my codebase.
Any help is greatly appreciated, as right now I'm hitting a wall.
Update: 10:44am
I've tried adding .uniq on the units array and I still have the problem. My helper method looks like this:
calls_helper.rb
def unit_select
(#call.units.to_a.uniq + Unit.active.in_service.order("unit_name")).map{|unit| unit.calls.where(call_status: "open").empty? ? ["#{unit.unit_name} #{unit.unit_type.unit_type}", unit.id] : ["#{unit.unit_name} (on call) #{unit.unit_type.unit_type}", unit.id] }
end
Does this make sense and is this the best way to go about it?
I'm trying to fully understand your problem but I must say I'm a bit confused. Have you tried separating each case in unit_select instead of trying to get the perfect one liner? From what I understand you want to be able to select:
Only "In Service" units when the call in the form is a new instance
Show any unit when you edit an existing call
Now, as I said I'm not fully sure I understand your problem but it looks like you're trying to do something like that:
def unit_select
units = Unit.active.in_service.order("unit_name")
if !#call.new_record?
units += #call.units
end
format_unit_select_options(units.uniq)
end
def format_unit_select_options(units)
units.map do|unit|
if unit.calls.where(call_status: "open").empty?
["#{unit.unit_name} #{unit.unit_type.unit_type}", unit.id]
else
["#{unit.unit_name} (on call) #{unit.unit_type.unit_type}", unit.id]
end
end
end
You should also move
Unit.active.in_service.order("unit_name")
in the new and edit action of your Call controller to make sure the responsibility of both the helper and the controller are easier to understand.
Now when you create your return call in your calls_controller, have you tried to use uniq on original_call.unit_ids to make sure the id is only there once? That might only fix the symptom and not the root cause but I am having trouble fully understanding your case...
Hope it helps.
Related
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.
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
I have an example Action in a Controller.
def some_action
product = Product.new
product.name = "namepro"
if product.save
client.update_attribute(:product_id,product.id)
end
end
How to add transactions for this code? I try with this example code:
def some_action
**transaction do**
product = Product.new
product.name = "namepro"
if product.save
client.update_attribute(:product_create,Time.now)
end
**end**
end
But it produces this error:
undefined method `transaction'
I read about using transactions in Controllers is a bad practice but I don't know why is the reason (http://markdaggett.com/blog/2011/12/01/transactions-in-rails/)
In the example, if product has been created and saved and the client update fail... Rails must not do nothing.
thanks.
You can use a transaction in a controller if you really want to. As you noted, it's bad practice, but if you want to do it, just call Product.transaction do instead of transaction do. transaction is a class method on ActiveRecord::Base, so you need to call it on an ActiveRecord-derived class. Any model class in your application will do (nit-picking caveat: if you are connecting to different databases for different models, that may not be true...but you're probably not doing that).
The reason this is a bad practice is that it doesn't properly separate concerns according to the MVC paradigm. Your controller shouldn't be so concerned with your data persistence implementation. A better approach would be to add a method to Product. Maybe something like this:
def save_and_update_create_time
transaction do
if save
client.update_attribute(:product_create, Time.now)
end
end
end
Then instead of calling product.save in your controller, call product.save_and_update_client_create_time. You may need to pass client to that method too; it's unclear from your code where client comes from. If it's an attribute on product, then the method above should work.
There are better, more Railsy ways to do this, too, especially if a product knows about its client without needing any controller data. Then you can just use an after_save callback, like this (add to Product class):
after_save :update_client
private
def update_client(product)
product.client.update_attribute(:product_create, Time.now)
end
Then every time a Product is saved, the field on the associated client will be updated. You'll possibly have to introduce some code to check for the existence of a client first.
The benefit to using callbacks, besides cleaner code, is that the entire callback chain runs in a single transaction along with the save; you don't need to create the transaction manually. You can read more about callbacks in the Rails documentation.
In my Rails App, there's home_controller.rb, in which I'd like to use un-related table(User model).
When I access example.com/home/index, I'd like it to send message to 4th id person in User table.
I'm using mailboxer gem to send message.
I added these to home_controller.rb
class HomeController < ApplicationController
def index
receipt = User.find(4)
receipt.send_message(receipt, "Body2", "subject2")
end
end
in home model, it's totally empty.
It certainly sends message. But it sends to 1st id person, who is current user.
How can I fix this?
Sounds like the send_message method delivers email to the object that receives the method call, not the object in the first argument. Try calling
receipt.send_message(receipt, "Body", "subject")
We have a bar with filters in almost all of our table-based views in a rails app and we need to test the controller action.
An example of the code:
def index
#users = User.
with_status(params[:status]).
with_role(params[:role_id]).
search(params[:q])
end
The above methods are ActiveRecord scopes which are setup to be bypassed if the passed value if blank.
What I need to do now is spec it sanely and test all the esge cases:
no params passed
only role, only status, only search
role + status, role + search, ... (pairs of 2)
role + status + search
The basic spec example I have written is as follows:
context "when filtering by status" do
before do
1.times { Factory(:user, :status => "one") }
3.times { Factory(:user, :status => "other") }
end
it "returns only users with the provided :status" do
get :index, :status => "one"
assigns(:users).size.should == 1
assigns(:users)[0].status.should == "one"
end
end
I want to write a matrix that will mix and match the role, status and search params and generate the appropriate spec examples.
Is the Array#permutation the solution or is there a better way to do it?
I would test the scopes in the model, so make sure that they can handle the blank value correctly, and also handle the set value correctly.
Then inside the controller, I would test the expectation that the chain is called (use stub_chain). The fact that the chain will return the correct result is handled by the fact that each scope individually has the correct behaviour (you tested that), and the combined behaviour is ensured by rails/activerecord. You should test the passed parameters are handled correctly.
The code you built to test the matrix is very impressive. But for me I try to make sure that my tests are readable, I consider them a kind of documentation of what a piece code is expected to do. To me your code is not comprehensible at first sight.
Hope this helps.