Creating a status attribute for a model - ruby-on-rails

I want to create a status attribute for my Taskmodel that would indicate where it is in a three part progress in this order: open => in-progress => complete. It would work in a way similar to how an Amazon package is delivered: ordered => shipped => delivered. I was wondering what would be the best way to setup this attribute. I may be wrong but creating three separate boolean attributes seemed sort've redundant. What's the best way to accomplish this?

Rails 4 has an built in enum macro. It uses a single integer column and maps to a list of keys.
class Order
enum status: [:ordered, :shipped, :delivered]
end
The maps the statuses as so: { ordered: 0, shipped: 1, delivered: 2}
It also creates scopes and "interrogation methods".
order.shipped?
Order.delivered.all
It will also map the enum values when writing queries with hash arguments:
Order.where(status: [:shipped, :delivered])

You should use the aasm gem. It has aasm_states for models, callback functionality etc.

Related

Query Rails Wildcard Enum Type

Trying to figure out how to utilize a "wildcard" feature with rails enums, where I don't want to have a dedicated int/symbol mapping for a number of variations of types.
For example, I have a ticketing system, where the majority of tickets are unanswered or answered, but the user can also set a custom status in special cases.
class Ticket
enum status: { unanswered: 0, answered: 1, *wildcard: 2 }
end
I'd like to be able to query for Ticket.where(status: :wildcard) and return all tickets with a status value of 2. Digging into the rails source code, I think I want to override.
I figure I can skip the assert_valid_value validations by having a before_save callback that converts any non-existent strings to the right status code, but how would I query?

How does Rails deal with method-name-unfriendly enum values?

I have a (Rails) class that has the following enum value:
class DeploymentRequest
enum frequency: ['One-off', 'Monthly', 'Quarterly', 'Annual']
#...
end
As per the docs (although slightly to my surprise, case-sensitively), a DeploymentRequest has such instance methods as #Monthly?, #Monthly!, #Quarterly? and so on (and the class method .Monthly). But the 'One-off' value is throwing it and/or me. I've tried
dr.One_off?
dr.Oneoff?
dr.oneoff? # (one lives in hope)
But none work (and Pry/IRB aren't suggesting any helpful alternatives). What's happening here? Has Rails just not defined any such set of methods, or has it named them something I haven't found?
Rails converts these enums into symbols. You'll be able to see some of these methods by looking at the difference in instance methods between your model and ActiveRecord::Base (assuming that's what you're using):
DeploymentRequest.instance_methods - ActiveRecord::Base.instance_methods
> [:Quarterly?, :Quarterly!, :Annual?, :Annual!, :"One-off?", :"One-off!", :Monthly?, :Monthly!, ....
You can see that the method you need is ":One-off?", which is hard to call. But you can use send:
dr.send(:"One-off?")
Ugly, but without changing the enum value might be your only option.
This is a pretty gross misuse of ActiveRecord::Enum. Its not intended to store the human friendly strings you want to use on the frontend. Rather its meant to be used to map a set of integers in the database to named states in the model. If the mapping does not follow the Ruby conventions for method naming you're doing it wrong.
If you want to display the mappings of an enum on the frontend you should use the I18n module or some other kind of mapping that maps the enum keys to the human readable version:
en:
deployment_request:
frequencies:
one_off: 'One-off'
monthly: 'Monthly'
quarterly: 'Quarterly'
annual: 'Annual'
I18n.t :"deployment_request.frequencies.#{deployment_request.frequency}"

How to get the id value from a Rails model enum?

Trying to understand how to use enums to help manage the different user status levels.
class User < ActiveRecord::Base
enum user_status: [:active, :pending, :trial]
end
So now active is 0, pending is 1, and trial is 2.
So if I do this:
user.active?
This works fine, but currently when I am setting the value I am still doing this:
user.user_status = 0
I was hoping I could do something like:
user.user_status = User.UserStatus.trial
The point is I don't want to have to remember what index each enum value is.
Also, if I change the order or add more user_status values, the index will change and I want to prevent bugs from me hardcoding the values in my codebase.
Is there a better way to handle the enum index values?
You can easily find the answer simply reading the documentation:
user.trial!
will set the status and update the record. For more variants you can refer to the docs.
it works in next way:
user.trial!
more detail

How to build a select menu using STI types in rails with simple_form

I'm new to STI and I'm trying to build a select menu that lists all the STI types in a Model. I am using simple_form and Rails 4.1. How do I get a drop down menu with the StiRecord.types? I'd like to store the type in the database in a string.
def new
#record = Record.new
#sti_records = StiRecord.subclasses.collect{|x| x.to_s}
end
example subclass
class SampleStiRecord < StiRecord
end
_form.html.erb
<%= f.input :restricted_to, wrapper_html: {class: 'col-md-3 col-md-offset-2'}, collection: #sti_records %>
In your parent model you can define a method to get them:
def self.types
unscoped.select(:type).distinct.pluck(:type)
end
Then just use Model.types and you'll get an array with the types present in the database.
If you want to see all the defined types, you'll need to go to the classes way, to check which classes are defined.
You can do it with:
Model.descendants.map {|klass| klass.name.demodulize }
Although remember that you have to set the config.eager_load = true in the development environment, otherwise it will not work, unless you have already loaded all the classes.
Also remember, sometimes the best way it's the dumbest way, so you could just create an array with the types. Everytime you will create a new type, you'll need to create a model... so it's was just a matter of adding another element to the array.
Paulo's answer is not bad, but it has a major problem: it will list types that are already saved in the DB, not those that have defined subclasses. This might work for you, but not necessarily.
The biggest problem is that, after you've introduced a new type your prod ENV would not show such type to be chosen from, because no such record has been added yet. You'd need to add a "dummy" record to allow users to pick such type or do other weird hacks.
Eager load works as well, but it's such a big change to the whole dev process for this minor issue (in big apps you can work for ages to load rails console with preloading, stuff that can make devs quit for other employers)
He mentions the dumbest way with an array and I want to expand on that:
Define possible values for the type attribute using enum declaration:
class StiRecord
enum type: %w[OneType OtherType].to_h { |v| [v,v] }
# ...
And now you can do
StiRecord.types
=> {"OneType" => "OneType", "OtherType" => "OtherType"}
And your solution does not depend on records existing in the database.
It's not ideal, you need to repeat yourself: When adding a new subclass, you also have to mention it in the enum declaration.

break down a complex search query in Rails 3

I have a controller which has a lot of options being sent to it via a form and I'm wondering how best to separate them out as they are not all being used simultaneously. Ie sometimes no, tags, sometimes no price specified. For prices I have a default price set so I can work around with it always being there, but the tags either need to be there, or not. etc.
#locations = Location.find(params[:id])
#location = #locations.places.active.where("cache_price BETWEEN ? AND ?",price_low,price_high).tagged_with([params[:tags]).order(params[:sort]).paginate :page => params[:page]
I haven't seen any good examples of this, but I'm sure it must happen often... any suggestions? Also, even will_paginate which gets tacked on last should be optional as the results either go to a list or to a google map, and the map needs no pagination.
the first thing to do when refactoring a complex search action is to use an anonymous scope.
Ie :
fruits = Fruit.scoped
fruits = fruits.where(:colour => 'red') if options[:red_only]
fruits = fruits.where(:size => 'big') if options[:big_only]
fruits = fruits.limit(10) if options[:only_first]
...
If the action controller still remains too big, you may use a class to handle the search. Moreover, by using a class with Rails 3 and ActiveModel you'll also be able to use validations if you want...
Take a look at one of my plugins : http://github.com/novagile/basic_active_model that allows you to easily create classes that may be used in forms.
Also take a look at http://github.com/novagile/scoped-search another plugin more specialized in creating search objects by using the scopes of a model.

Resources