So I have a subscription table in my database.
I would like to have a state column which will have any one of the following values
Valid
Invalid
Cancelled
In Trial
Non Renewing
Future
Can someone explain how to use these values as enum values in rails 4?
Credit to: https://hackhands.com/ruby-on-enums-queries-and-rails-4-1/
Declare an enum attribute where the values map to integers in the database, but can be queried by name. Example:
class Conversation < ActiveRecord::Base
enum status: [ :active, :archived ]
end
# conversation.update! status: 0
conversation.active!
conversation.active? # => true
conversation.status # => "active"
# conversation.update! status: 1
conversation.archived!
conversation.archived? # => true
conversation.status # => "archived"
# conversation.update! status: 1
conversation.status = "archived"
# conversation.update! status: nil
conversation.status = nil
conversation.status.nil? # => true
conversation.status # => nil
Scopes based on the allowed values of the enum field will be provided as well. With the above example, it will create an active and archived scope.
You can set the default value from the database declaration, like:
create_table :conversations do |t|
t.column :status, :integer, default: 0
end
Good practice is to let the first declared status be the default.
Finally, it's also possible to explicitly map the relation between attribute and database integer with a Hash:
class Conversation < ActiveRecord::Base
enum status: { active: 0, archived: 1 }
end
Note that when an Array is used, the implicit mapping from the values to database integers is derived from the order the values appear in the array. In the example, :active is mapped to 0 as it's the first element, and :archived is mapped to 1. In general, the i-th element is mapped to i-1 in the database.
Therefore, once a value is added to the enum array, its position in the array must be maintained, and new values should only be added to the end of the array. To remove unused values, the explicit Hash syntax should be used.
In rare circumstances you might need to access the mapping directly. The mappings are exposed through a class method with the pluralized attribute name:
Conversation.statuses # => { "active" => 0, "archived" => 1 }
Use that class method when you need to know the ordinal value of an enum:
Conversation.where("status <> ?", Conversation.statuses[:archived])
Where conditions on an enum attribute must use the ordinal value of an enum.
More info: http://api.rubyonrails.org/v4.1.0/classes/ActiveRecord/Enum.html
You can use gem called Workflow. It enables you to use custom statuses and gracefully handle transitions between states. I've used it on many Rails3 and Rails 4 apps.
Example from the documentation.
class Article
include Workflow
workflow do
state :new do
event :submit, :transitions_to => :awaiting_review
end
state :awaiting_review do
event :review, :transitions_to => :being_reviewed
end
state :being_reviewed do
event :accept, :transitions_to => :accepted
event :reject, :transitions_to => :rejected
end
state :accepted
state :rejected
end
end
And later:
article = Article.new
article.accepted? # => false
article.new? # => true
Edit: My answer here does not use ActiveRecord magic enums. What I suggest below works for any DB library (like Sequel), and I happen to think it slightly more robust. For example, it does not rely on automatic ordering of values, and it uses foreign key constraints in the RDBMS to ensure that your value is valid (unlike the ActiveRecord solution). However, it is not the Rails idiomatic methodology. See Giri's answer (a copy/paste of the documentation page) for the idiomatic answer.
Create a states table in your database with these string values populated along with a unique ID, e.g.
states
id | name
---+--------------
1 | Valid
2 | Invalid
3 | Cancelled
4 | In Trial
5 | Non Renewing
6 | Future
Use a standard foreign-key association in your subscription table referencing an entry from states.
In your models, create a State class with constants matching the IDs, e.g.:
class State < ActiveRecord::Base
VALID = 1
INVALID = 2
# etc.
end
Now you are a) guaranteed that your table data is valid, and b) you can use convenient references such as asking for subscriptions
Subscription.where( state_id: State::VALID )
Related
I’m using Rails 4.2.7. I have an attribute in my model that doesn’t have a database field underneath it
attr_accessor :division
This gets initialized when I create a new object.
my_object = MyObject.new(:name => name,
:age => get_age(data_hash),
:overall_rank => overall_rank,
:city => city,
:state => state,
:country => country,
:age_group_rank => age_group_rank,
:gender_rank => gender_rank,
:division => division)
What I would like is when this field gets set (if it is not nil), for two other fields that do have mappings in the database to get set. The other fields would be substrings of the “division” field. Where do I put that logic?
I'd probably drop the attr_accessor :division and do it by hand with:
def division=(d)
# Break up `d` as needed and assign the parts to the
# desired real attributes.
end
def division
# Combine the broken out attributes as needed and
# return the combined string.
end
With those two methods in place, the following will all call division=:
MyObject.new(:division => '...')
MyObject.create(:division => '...')
o = MyObject.find(...); o.update(:division => '...')
o = MyObject.find(...); o.division = '...'
so the division and the broken out attributes will always agree with each other.
If you try to use one of the lifecycle hooks (such as after_initialize) then things can get out of sync. Suppose division has the form 'a.b' and the broken out attributes are a and b and suppose that you're using one of the ActiveRecord hooks to break up division. Then saying:
o.division = 'x.y'
should give you o.a == 'x' but it won't because the hook won't have executed yet. Similarly, if you start with o.division == 'a.b' then
o.a = 'x'
won't give you o.division == 'x.b' so the attributes will have fallen out of sync again.
I see couple of options here
You can add it in your controller as follows
def create
if params[:example][:division]
# Set those params here
end
end
Or you can use before_save In your model
before_save :do_something
def do_something
if division
# Here!
end
end
Each user has one address.
class User
include Mongoid::Document
has_one :address
end
class Address
include Mongoid::Document
belongs_to :user
field :street_name, type:String
end
u = User.find(...)
u.address.update(street_name: 'Main St')
If we have a User without an Address, this will fail.
So, is there a good (built-in) way to do u.address.update_or_initialize_with?
Mongoid 5
I am not familiar with ruby. But I think I understand the problem. Your schema might looks like this.
user = {
_id : user1234,
address: address789
}
address = {
_id: address789,
street_name: ""
user: user1234
}
//in mongodb(javascript), you can get/update address of user this way
u = User.find({_id: user1234})
u.address //address789
db.address.update({user: u.address}, {street_name: "new_street name"})
//but since the address has not been created, the variable u does not even have property address.
u.address = undefined
Perhaps you can try to just create and attached it manually like this:
#create an address document, to get _id of this address
address = address.insert({street_name: "something"});
#link or attached it to u.address
u.update({address: address._id})
I had this problem recently. There is a built in way but it differs from active records' #find_or_initialize_by or #find_or_create_by method.
In my case, I needed to bulk insert records and update or create if not found, but I believe the same technique can be used even if you are not bulk inserting.
# returns an array of query hashes:
def update_command(users)
updates = []
users.each do |user|
updates << { 'q' => {'user_id' => user._id},
'u' => {'address' => 'address'},
'multi' => false,
'upsert' => true }
end
{ update: Address.collection_name.to_s, updates: updates, ordered: false }
end
def bulk_update(users)
client = Mongoid.default_client
command = bulk_command(users)
client.command command
client.close
end
since your not bulk updating, assuming you have a foreign key field called user_id in your Address collection. You might be able to:
Address.collection.update({ 'q' => {'user_id' => user._id},
'u' => {'address' => 'address'},
'multi' => false,
'upsert' => true }
which will match against the user_id, update the given fields when found (address in this case) or create a new one when not found.
For this to work, there is 1 last crucial step though.
You must add an index to your Address collection with a special flag.
The field you are querying on (user_id in this case)
must be indexed with a flag of either { unique: true }
or { sparse: true }. the unique flag will raise an error
if you have 2 or more nil user_id fields. The sparse option wont.
Use that if you think you may have nil values.
access your mongo db through the terminal
show dbs
use your_db_name
check if the addresses collection already has the index you are looking for
db.addresses.getIndexes()
if it already has an index on user_id, you may want to remove it
db.addresses.dropIndex( { user_id: 1} )
and create it again with the following flag:
db.addresses.createIndex( { user_id: 1}, { sparse: true } )
https://docs.mongodb.com/manual/reference/method/db.collection.update/
EDIT #1
There seems to have changes in Mongoid 5.. instead of User.collection.update you can use User.collection.update_one
https://docs.mongodb.com/manual/reference/method/db.collection.updateOne/
The docs show you need a filter rather than a query as first argument but they seem to be the same..
Address.collection.update_one( { user_id: user_id },
'$set' => { "address": 'the_address', upsert: true} )
PS:
If you only write { "address": 'the_address' } as your update clause without including an update operator such as $set, the whole document will get overwritten rather than updating just the address field.
EDIT#2
About why you may want to index with unique or sparse
If you look at the upsert section in the link bellow, you will see:
To avoid multiple upserts, ensure that the filter fields are uniquely
indexed.
https://docs.mongodb.com/manual/reference/method/db.collection.updateOne/
I have a model
class Transaction < ActiveRecord::Base
end
I have a transaction_type column which is an integer.
How can I create an enumeration that I could map values to names like:
one_time = 1
monthly = 2
annually = 3
So in the db column, the values would be 1, 2 or 3.
Also, whenever I create a new instance, or save a model and the field wasn't set like:
#transaction = Transaction.new(params)
It should default to 1 (on_time).
I'm not sure how I can do this?
basically the same answer as Amit, slight variation
class TransactionType
TYPES = {
:one_time => 1,
:monthly => 2,
:annually => 3
}
# use to bind to select helpers in UI as needed
def self.options
TYPES.map { |item| [item[0], item[1].to_s.titleize] }
end
def self.default
TYPES[:one_time]
end
end
one way to control the default value
class Transaction < ActiveRecord::Base
before_create :set_default_for_type
def set_default_for_type
type = TransactionType.default unless type.present?
end
end
but - best way is to just apply the defaults on your database column and let ActiveRecord get it from there automatically
NOTE: it might also make sense to just have a TransactionType ActiveRecord object instead of above, depends on your situation, i.e.
# on Transaction with type_id:integer
belongs_to :type, class_name: "TransactionType"
You can map the values by creating a constant either in the same Transaction model or by creating a new module and place it inside that as explained by #KepaniHaole
In Transaction model, you can do it like :
class Transaction < ActiveRecord::Base
TRANSACTION_TYPES = { 'one_time' => 1, 'monthly' => 2, 'monthly' => 3 }
end
You can access these values by accessing the constant as
Transaction::TRANSACTION_TYPES['one_time'] # => 1
Transaction::TRANSACTION_TYPES['monthly'] # => 2
Transaction::TRANSACTION_TYPES['monthly'] # => 3
To add a default value to transaction_type column just create a new migration with :
def up
change_column :transactions, :transaction_type, :default => Transaction::TRANSACTION_TYPES['one_time']
end
With this, every time you create a Transaction object without passing transaction_type, the default value 1 with be stored in it.
Maybe you could try something like this? Ruby doesn't really support c-style enums..
module TransactionType
ONCE = 1
MONTHLY = 2
ANUALLY = 3
end
then you could access their values like so:
#transaction = Transaction.new(TransactionType::ONCE)
I'm trying to pass the following Hash to Active Record to save:
[{
"id"=>"WSECOUT",
"realtime_start"=>"2013-02-10",
"realtime_end"=>"2013-02-10",
"title"=>"Reserve Bank Credit - Securities Held Outright",
"observation_start"=>"1989-03-22",
"observation_end"=>"2013-02-06",
"frequency"=>"Weekly, Ending Wednesday",
"frequency_short"=>"W",
"units"=>"Billions of Dollars",
"units_short"=>"Bil. of $",
"seasonal_adjustment"=>"Not Seasonally Adjusted",
"seasonal_adjustment_short"=>"NSA",
"last_updated"=>"2013-02-08 08:32:33-06",
"popularity"=>"42",
"notes"=>"The amount of securities held by Federal Reserve Banks. This quantity is the cumulative result of permanent open market operations: outright purchases or sales of securities, conducted by the Federal Reserve. Section 14 of the Federal Reserve Act defines the securities that the Federal Reserve is authorized to buy and sell."
}]
My ruby class looks like this:
require 'rubygems'
require 'active_record'
require 'logger'
ActiveRecord::Base.establish_connection(
:adapter => "mysql2",
:host => "localhost",
:username => "root",
:password => "*********",
:database => "fred"
)
class Series < ActiveRecord::Base
attr_accessible :id, :realtime_start, :realtime_end, :title, :observation_start,
:observation_end, :frequency, :frequency_short, :units, :units_short,
:seasonal_adjustment, :seasonal_adjustment_short, :last_updated,
:popularity, :notes
end
require_relative 'wsecout'
#series = Wsecout.new.getSeries "wsecout"
#series = #series['series']
test = Series.create(#series)
The #series variable contains the Hash. When I run this code, the object is created as is the row in mysql, however, there is no data in the fields. I know I'm missing a step here, but I am unable to figure out which step. Also, is there going to be a problem with my Hash containing an id, because Active Record creates it's own id?
Answer to your second question "map that "id" to a new field called :series_id":
#series['series'][0]['series_id'] = #series['series'][0]['id']
#series['series'][0].delete('id')
Or if you want to change multiple keys based on some criteria, then use it in the if condition as below:
#series['series'][0].keys.each do |k|
if(k == 'id')
#series['series'][0]['series_id'] = #series['series'][0][k]
#series['series'][0].delete(k)
end
end
This will iterate through each key of the hash and if the key matches to id then it add another key series_id with the same value and delete the id.
New to rails so not sure what the best approach is here. I want to define a simple c++ style enum which can then be used as a custom type in my db. The enum can be simulated with an array or a custom module but how do I go about turning that into a custom type for my table?
Here's a pattern I follow in rails:
In my model class, I add a module to hold the possible values of the column. Then I put them into an array and define validation against the array of possible values.
Imagine I have a column/attribute called status and it can be three possible values. I'd do this:
class MyModel < ActiveRecord::Base
# This validates that status can't be null
validates :status, :presence => true
# Define a module with all possible values
module Status
IN_DEVELOPMENT = 'in development'
DISABLED = 'disabled'
ACTIVE = 'active'
end
# Now create an array of possible status values, and add a validation
STATUSES = [ Status::DISABLED, Status::ACTIVE, Status::IN_DEVELOPMENT]
validates :status, :inclusion => { :in => STATUSES, :message => "%{value} is not a valid status value" }
end
Have you considered using the built-in enumeration support in your database? Lots of common RDMBSes have enum support, such as Postgres (see http://www.postgresql.org/docs/9.1/static/datatype-enum.html) and MySQL (see http://dev.mysql.com/doc/refman/5.5/en/enum.html). With that, you can directly create the type in your data store and then use it via one of the ActiveRecord plugins (such as enum_type for Postgres: https://github.com/riscfuture/enum_type).
Alternatively, you could use something like active_enum to structure the enumeration as you described and store fields as integers in the database.
Depending on how you plan to utilize this enum type in your code I've found that using scopes accomplishes close to the same thing along with an enum type in the database to ensure only specific values are set.
Example:
scope :trial, :conditions => { :utype => 'TRIAL' }
scope :registered, :conditions => { :utype => 'REGISTERED' }
scope :active, :conditions => { :status => 'ACTIVE' }
scope :abuse, :conditions => { :status => 'ABUSE' }