I’m new at RoR, so please excuse me if this is a very basic question.
I’ve tried some googling, but haven’t found an answer yet.
Is there any established, or preferred way to limit a table to have
one and only one row? I have some default values that I will use in
multiple places in my app, and I want to be able to modify them during
production, so I don’t want to hard code them. So I made a “Defaults”
model. I can simply limit my code to only accessing the first row, but
it seems cleaner to me if there’s someway to prevent more than one row
from existing.
Is the best/easiest way just to add a validate method in the model
that checks on creating if there’s already an entry in the database,
and returns an error? Or is there some simpler way I’m overlooking?
Thank you in advance!
Are these constant variables that won’t change during execution? If
so, you can just put them in your config/environment.rb, defined in
all caps. It sounds like a bad idea to continually hit the database
just to grab a few constants.
If you rely on them to be mutable, you’re effectively creating global
variables which is a sign that you should probably refactor your code.
If you really, really want to go down this route, you can trap the
before_create event on your Default model so that no new records can
get made. It doesn’t others from inserting rows from outside rails.
They could change during execution.
They’re some default values that we will use when creating some models
(they are percentages for allocating a budget into several
categories). Changing the values won’t affect already created budgets,
but will affect new budgets.
I don’t want to hard code them in environment.rb, because I’d like
“less technical” people to change them while the app is live.
How would you refactor this then?
Thanks for the advice!
I would not jump to the conclusion that singleton tables are bad or
imply bad design. For things like current conversion rates for
currency, desired margin for budgets, etc it is quite reasonable to
want modifiable values that are global. You will add overhead to read
that row over and over. You should think about what type of caching
you can do to reduce the database I//O. I am assuming that you are
going to have a model class to access these values and it will be able
to perform caching. At a minimum it could keep it locally in a class
variable and only read it if there is no value in the variable
already.
Michael
Nathaniel Martin wrote:
Is there any mechanism to do a singleton table, or is using
before_create and validate the best way?
You could start by not adding any create methods to your Default
controller. Then other users couldn’t create new db rows via your
application.
David C. wrote:
Nathaniel Martin wrote:
Is there any mechanism to do a singleton table, or is using
before_create and validate the best way?
You could start by not adding any create methods to your Default
controller. Then other users couldn’t create new db rows via your
application.
I was thinking exactly the same thing, just update and view methods in
the controller should do the trick. That way you can change the
existing default record and view it’s contents but never add a new row.
Seems quite sensible.
Thanks for the advice.
These values are really only going to be accessed when a new user
signs up, so I’m not too concerned with I/O usage.
Is there any mechanism to do a singleton table, or is using
before_create and validate the best way?
Hey,
another option is to split out your one row into multiple rows:
create_table :defaults do |t|
t.column :name, :string
t.column :value, :string
end
class Default < ActiveRecord::Base
validates_uniqueness_of :name
def self.
find_by_name(default_name) or raise
ActiveRecord::RecordNotFound, “cannot find a default named #{name}”
end
def to_i
self.value.to_i
end
etc for different data types
end
Default[:some_limit].to_i
This serves 2 purposes:
-
it ensures that if a requirement for a new default value turns up
you’re not redefining your schema
-
it avoids the ‘singleton’ issue because each default value can
have at most one row
The main drawbacks are hitting the database even more often than
getting a “single-row collection of defaults” and that you have to
write accessors that convert the string representation into an
intrinsic value (like the to_i I included above).
Just something you might want to consider.
Trevor
On Mar 21, 5:49 pm, Trevor S. [email protected] wrote:
another option is to split out your one row into multiple rows:
snip
This serves 2 purposes:
-
it ensures that if a requirement for a new default value turns up
you’re not redefining your schema
-
it avoids the ‘singleton’ issue because each default value can
have at most one row
This is a good approach. Every single commercial app I’ve written has
ended up with a Settings model in it, looking very similar to this
one.
The main drawbacks are hitting the database even more often than
getting a “single-row collection of defaults” and that you have to
write accessors that convert the string representation into an
intrinsic value (like the to_i I included above).
One way to avoid this would be to marshal your native Ruby object
(like an integer, float, whatever) before you save it in the DB, and
then unmarshal it on the way out.
Or, I’ve done things where the table contains a separate column for
each type (integer, float, string, boolean), and the model checks the
behaviour of the object to be saved, and saves it in the most
appropriate native format in the correct DB column. Then it reverses
the translation on the way out. Bit hacky, but if you like your
database tables to contain real native data, then it’s an alright
approach.
Chris
On Mar 21, 5:44 pm, Phil P. [email protected]
wrote:
the controller should do the trick. That way you can change the
existing default record and view it’s contents but never add a new row.
Seems quite sensible.
No, this kind of restriction should be built in at the model level.
Omitting the controller actions is a nice additional guard, but it
doesn’t prevent you from inadvertently messing it up in another
controller, or in a piece of code called from script/runner, or by
messing around in the console, or whatever.
before_save and/or validate is how to achieve this.
Chris
On Mar 21, 2:42 pm, “Chris M.” [email protected] wrote:
database tables to contain real native data, then it’s an alright
approach.
Chris
I am really only asking for information here; it is not a suggestion.
What would be the impact of just marshaling the settings into a YAML
or JSON or XML string and storing that in a single cell of the single
row? I really like the idea about separate columns for each type of
value, but I was just wondering about this single-cell approach.
Ron