I'm trying to construct a system in Rails where I've got a Project model with a "type" column, and I'm not sure whether if I should explicitly store the type as a string in the projects table, or if I should store a type_id instead. The thing is, I feel like it would be stupid to create a type model; Types cannot be created or destroyed, there are simply a fixed number of them. But if I don't create a model, the only other way I can think to do it would be to create a Type class in /lib which has a get_name(type_id) method, and this seems like total overkill.
If I decided to just store the string, I'd be using unnecessary space, and filtering by type would be weird.
Any thoughts?
If you're sure the types are a fixed set you can define some numeric constant in the Project model and just store these number in a column of your projects table.
Here an example (not tested of course) where I call the column category_id to avoid to use the type name that would cause problems as rjz said:
class Project < ActiveRecord::Base
# Project categories are constants
CHEAP_PROJECT = 1
SOUND_PROJECT = 2
GRAPHIC_PROJECT = 3
SECRET_PROJECT = 4
# Force project_category_id to be a valid category identifier
validates :category_id, :inclusion => {:in => 1..4}
# At this point you can use the constants
# Here an example of a scope to get the secret projects
scope :secret_projects, where(:category_id => SECRET_PROJECT)
end
Be sure to validate the category_id values to be one of which you defined.
Once you have these constans you can even use from other places using something like Project::SOUND_PROJECT.
I think is a solution pretty clear, but if your requirements change (they always change...) you have to create a model and insert these project categories maintaining these identifiers.
Related
Oftentimes, I come across a situation that kind of irks me because I don't feel like my solution for it is up to par.
Say that I have a User model. There can be two TYPES of users - admins and the regular folk. In this particular case, I've simply split User and Admin into separate models - fair enough.
In some cases when there are numerous types of the same model (Foo can be green, red, purple, yellow, etc)
I don't see much sense in dealing with 10 separate models like YellowFoo, GreenFoo, etc - so I add a "type" attribute to the model and then the code for, say, finding the correct object is simplified to User.where(:type => "some_type").
Is this an acceptable way of doing things in Ruby on Rails? Should the type be set to a be Symbol instead of a string so that User.where(:type => :some_type) looks prettier or is this a hack no matter how you look at it?
Same goes for views that I create. In the above example, there is an admin controller with a whole separate dashboard from the users, even though the code for the dashboard is more or less the same, with a few exceptions (but in the future, potentially far more different).
Is this an acceptable way of doing things or the newb way?
Thanks in advance!
type attribute is good enough to use it inside the model to separate different types of objects. But type attribute name itself used by Rails AR class method, so I'd suggest to use model name specified field type name like 'user_type' for User model to avoid conflicts.
Having defined model's types you can add very handy model's scopes and methods.
For example:
class Order
# I prefer to define type constants as camelized strings, but it would be probably
# better to use strings in downcase for localization purposes
ORDER_TYPES = (OrderTypePersonal, OrderTypeOrganization = 'Personal', 'Organization')
scope :personal, where(order_type: OrderTypePersonal)
scope :organization, where(order_type: OrderTypeOrganization)
def personal?; order_type == OrderTypePersonal end
def organization; order_type == OrderTypeOrganization end
end
# find all personal orders
personal_orders = Order.personal.all
# build new organization order
organization_order = Order.organization.new
# check order type
Order.find(14).personal?
# user order types as select
f.select :order_type, Order::ORDER_TYPES
I would suggest for different types/roles of users in our application, we can define a Role model.
We can define as the associations between User and Role as per our requirement. More basically,
In Role Model we will define
has_many :users
In User model we will define
belongs_to :role
A role will have multiple users. We can check each users role in User model
def has_role?(roleSymbol)
(role.role_name.underscore.humanize.downcase.to_sym == roleSymbol)? true :false
end
Here we can check the user has a role of "Admin" or "Normal" user.
For view parts we can check the user roles and can include separate partial for different types of users.
I'm working up an app that interfaces with a legacy database which has a type column used for single table inheritance. This database is still used by an existing PHP application so I have to work with what is there. I'm trying to set up some models for this and one of the key tables is set up with an STI scheme and a type column, however, all of the types in that column are entirely lowercase.
In my testing so far, rails works fine with the type column if I change the value to match the class name (for example, Claimant instead of claimant). However I don't want to go changing those values in the production database even though it would probably be ok, nor do I want to have to go in and modify the legacy app to save the names differently...
In order to fix this I have two questions...
1) Is there anyway I can configure the model to recognize that type = 'claimant' maps to class = 'Claimant'?
2) failing that, is there a way I can tell rails to not use STI on this table even though it has a type column?
I've done some googling and haven't come up with much yet...
I haven't tried this in an STI setting, but when using a legacy table I was able to use the method "set_table_name" on the ActiveRecord model.
class Claimant < ActiveRecord::Base
set_table_name 'claimant'
end
Or with a custom method:
def table_name
'claimant'
end
Apologies I haven't got an STI table handy to test this on, but thought it might help solve your problem.
In answer to the second part of your question, I believe you can disable Rails looking at the type column, by just specifying a non-existant column name.
class Claimant < ActiveRecord::Base
inheritance_column = :_type_column_disabled
end
Sorry about the awkward phrasing of the title -- not quite sure of the best way to title this but here's what I'm seeing assistance with:
In a Rails app, let's say we've got a model for a Product, and one of the attributes of the product is Price.
On the admin side of my app, I'd like to be able to set a "default" price that could be referred to if any new Product created isn't assigned a Price. If a Product does have a value for Price, then it would be used.
This is, of course, an example -- and I ask this question because I've got to imagine this is a common pattern. This can be applied to any resource that might have user-configurable global defaults or resource-specific values.
In pure Ruby, this would be solved, I think, with a class variable, so I'd be able to define ##default_price within the Product class and be able to refer to Product.default_price if the instantiated object's value doesn't exist.
My research here has pointed me towards the rails-settings-cached gem, which would allow for something like MyApp.default_price, but I'm wondering if there's a more elegant (non-plugin) way to accomplish this within the base Rails framework.
Note I'd like to setup this structure in code, but I want to be able to define the actual values through my app (i.e. config files aren't the solution I'm looking for).
Can someone enlighten me with the Rails way of handling this?
ActiveRecord picks up default attribute values from the database schema. However, these are baked into the migration and table schema and not configurable.
If you want configurability, the pattern that I've used is a before_validation callback method to set a value if the attribute is blank, e.g.:
class Product < ActiveRecord::Base
before_validation :set_price_if_blank
validates :price, :presence => true # sanity check in case the default is missing
has_one :price
private
def set_price_if_blank
self.price = Price.default if self.price.blank?
end
end
class Price < ActiveRecord::Base
def self.default
##default ||= Price.where(:default => true).first
end
end
This assumes that your price table is populated with a row that has a default flag. You could achieve this, e.g. through a seeds.rb file. I've added a validation rule to make sure that you still get an error if no default exists. It adds robustness to your application.
Also note that it's best to use Integers or Decimals for price data, not floats. See this answer.
Does anyone know if Mongoid has built in support to alias field names? I've been running some tests where I have a collection with a minimal amount of fields (7 fields). If I use descriptive names and load real data and then use greatly shortened names and load the same real world data I see a 40% reduction in the total size of my collection. In looking at other drivers for MongoDB (non Ruby) I see that some of them have built in support where you can write code against a descriptive name but the persistence is smart enough to use developer defined aliases. I'm just trying to determine if Mongoid has anything similar.
actually the best way to do this is NOT using ruby alias but:
field :fn, :as => :firstname
as outlined here: http://groups.google.com/group/mongoid/browse_thread/thread/ce3298d6a167bd70
Actually, the following must also be included for passing a hash to new or update_attributes:
alias :filtered_process :process
def process(attrs = nil)
attrs[:fn] = attrs[:first_name] unless attrs.nil?
filtered_process(attrs)
end
This allows your alias to be mapped to the field on the create or update calls. It would be trivial to then track alias to field mappings in order to abstract this process.
According to this answer, you should be able to just use alias like this:
class Foo
include Mongoid::Document
field :fn, :type => String
alias :first_name :fn
end
Keep in mind that this will not let you use the alias in queries and may cause some bugs. It should be fine for simple usage in instance methods and views.
Lets say you have a model like the following:
class Stock < ActiveRecord::Base
# Positions
BUY = 1
SELL = 2
end
And in that class as an attribute of type integer called 'position' that can hold any of the above values. What is the Rails best practice for converting those integer values into human readable strings?
a) Use a helper method, but then you're force to make sure that you keep the helper method and model in sync
def stock_position_to_s(position)
case position
when Stock::BUY
'buy'
when Stock::SELL
'sell'
end
''
end
b) Create a method in the model, which sort of breaks a clean MVC approach.
class Stock < ActiveRecord::Base
def position_as_string
...snip
end
end
c) A newer way using the new I18N stuff in Rails 2.2?
Just curious what other people are doing when they have an integer column in the database that needs to be output as a user friendly string.
Thanks,
Kenny
Sounds to me like something that belongs in the views as it is a presentation issue.
If it is used widely, then in a helper method for DRY purposes, and use I18N if you need it.
Try out something like this
class Stock < ActiveRecord::Base
##positions => {"Buy" => 1, "Sell" => 2}
cattr_reader :positions
validates_inclusion_of :position, :in => positions.values
end
It lets you to save position as an integer, as well as use select helpers easily.
Of course, views are still a problem. You might want to either use helpers or create position_name for this purpose method
class Stock < ActiveRecord::Base
##positions => {"Buy" => 1, "Sell" => 2}
cattr_reader :positions
validates_inclusion_of :position, :in => positions.values
def position_name
positions.index(position)
end
end
Is there a good reason for the app be converting the integer to the human readable string programmatically?
I would make the positions objects which have a position integer attribute and a name attribute.
Then you can just do
stock.position.name
#HermanD: I think it's a lot better to store the values in an integer column rather than a string column for numerous reasons.
It saves database space.
Easier/faster to index on an integer than a string.
Your not hard coding a human readable string as values in a database. (What happens if the client says that "Buy" should become "Purchase"? Now the UI shows "Purchase" everywhere but you need to keep setting "Buy" in the database.)
So, if you store certain values in the database as integers, then at some point, you're going to need to show them to the user as strings, and I think the only way you can do that is programatically.
You could move this info into another object but, IMHO, I'd say this is overkill. You'd then have to add another database table. Add another 'admin' section for adding, removing and renaming these values and so on. Not to mention that if you had several columns, in different models that needed this behavior, you'd either have to create lots of these objects (ex: stock_positions, stock_actions, transaction_kinds, etc...) or you'd have to design it generically enough to use polymorphic associations. Finally, if the position name is hard coded, then you lose the ability to easy localize it at a later date.
#frankodwyer: I'd have to agree that using a helper method is probably the best way to go. I was hoping their might be a "slicker" way to do this, but it doesn't look like it. For now, I think the best method is to create a new helper module, maybe something like StringsHelper, and stuff a bunch of methods in their for converting model constants to strings. That way I can use all the I18N stuff in the helper to pull out the localized string if I need to in the future. The annoying part is that if someone needs to add a new value to the models column, then they will also have to add a check for that in the helper. Not 100% DRY, but I guess "close enough"...
Thanks to both of you for the input.
Kenny
Why not use the properties of a native data structure? example:
class Stock < ActiveRecord::Base
ACTIONS = [nil,'buy','sell']
end
Then you could grab them using Stock::ACTIONS[1] #=> 'buy' or Stock::ACTIONS[2] #=> 'sell'
or, you could use a hash {:buy => 1, :sell => 2} and access it as Stock::ACTIONS[:buy] #=> 1
you get the idea.
#Derek P. That's the implementation I first went with and while it definitely works, it sort of breaks the MVC metaphor because the model, now has view related info defined in its class. Strings in controllers are one thing, but strings in models (in my opinion) are definitely against the spirit of clean MVC.
It also doesn't really work if you want to start localizing, so while it was the method I originally used, I don't think it's the method for future development (and definitely not in an I18N world.)
Thanks for the input though.
Sincerely,
Kenny
I wrote a plugin that may help a while ago. See this. It lets you define lists and gives you nice methods ending in _str for display purposes.