ctilley79
ctilley79

Reputation: 2195

Rails Limit Model To 1 Record

I am trying to create a section in my app where a user can update certain site wide attributes. An example is a sales tax percent. Even though this amount is relatively constant, it does change every few years.

Currently I have created a Globals model with attributes I want to keep track of. For example, to access these attributes where needed, I could simply do something like the following snippet.

(1+ Globals.first.sales_tax) * @item.total

What is the best way to handle variables that do not change often, and are applied site wide? If I use this method is there a way to limit the model to one record? A final but more sobering question.......Am I even on the right track?

Upvotes: 2

Views: 1529

Answers (3)

OneChillDude
OneChillDude

Reputation: 8006

Ok, so I've dealt with this before, as a design pattern, it is not the ideal way to do things IMO, but it can sometimes be the only way, especially if you don't have direct disk write access, as you would if deployed on Heroku. Here is the solution.

class Global < ActiveRecord::Base
  validate :only_one

  private

  def only_one
    if Global.count >= 1
      errors.add :base, 'There can only be one global setting/your message here' 
    end
  end
end

If you DO have direct disk access, you can create a YAML config file that you can read/write/dump to when a user edits a config variable.

For example, you could have a yaml file in config/locales/globals.yml

When you wanted to edit it, you could write

filepath = "#{Rails.root}/config/locales/globals.yml"
globals = YAML.load(File.read("#{Rails.root}/config/locales/globals.yml"))
globals.merge!({ sales_tax: 0.07 })

File.write(filepath) do |f|
  f.write YAML.dump(globals)
end

More on the ruby yaml documentation

You could also use JSON, XML, or whatever markup language you want

Upvotes: 2

Andreas Kavountzis
Andreas Kavountzis

Reputation: 417

It seems to me like you are pretty close, but depending on the data structure you end up with, I would change it to

(1+ Globals.last.sales_tax) * @item.total

and then build some type of interface that either:

  1. Allows a user to create a new Globals object (perhaps duplicating the existing one) - the use case here being that there is some archive of when these things changed, although you could argue that this should really be a warehousing function (I'm not sure of the scope of your project).
  2. Allows a user to update the existing Globals object using something like paper_trail to track the changes (in which case you might want validations like those presented by @Brian Wheeler).

Alternatively, you could pivot the Global object and instead use something like a kind or type column to delineate different values so that you would have:

(1+ Globals.where(kind: 'Colorado Sales Tax').last) * @item.total

and still build interfaces similar to the ones described above.

Upvotes: 2

rb512
rb512

Reputation: 6948

You can create a create a class and dump all your constants in it.

For instance:

class Global
  @sales_tax = 0.9

  def sales_tax
    @sales_tax
  end
end

and access it like:

Global.sales_tax

Or, you can define global variables something on the lines of this post

Upvotes: 0

Related Questions