how to have unique student id in ruby on rails - ruby-on-rails

I have a simple model for students but I want to also have unique student code for each student, I know I can use the student_id but I would like student code to be something like 'STU0001', 'STU0002. How can I implement something like this so that whenever I create a student, it will create the student code and add it to the DB?
Also how can I search students by their ids in this case?

You can do it using before-create hook in your model, something like follow -
class Student < ActiveRecord::Base
# Callbacks
before_create :set_student_id
def set_student_id
self.student_id = "STU" + "%04d" % self.id
end
end
output -
$> student = Student.create()
$> puts student.student_id
$> STU0001

IMHO if you want id to be prefixed as STU just for a nice display purpose then I would suggest not to store that way in database.
Let current implementation of auto incrementing id be untouched.
Just while displaying you can prepend it with STU.
For handy use you can create method to wrap this logic as
def formatted_id # OR friendly_id or whatever you like
"STU#{'%04d' % self.id}"
end
Reasons for not storing prefixed id:
You will have to write a setter
Your column data type will change to string
Your performance will go down as compared to integer column
In future if you decide to change the prefix, then you will have to update existing ids. With my solution you can change all (existing and future) ids formatting anytime by just changing formatting in the method.
Searching depends on exactly what is your usecase. If you can explain that then I can guide you better.
To search students you can write a handy method like below in model (i assume it will be Student or User)
def self.find_by_formatted_id(formatted_id)
self.find(formatted_id.gsub(/^STU/, ''))
end
def self.find_by_formatted_ids(formatted_ids) # formatted_ids will be an array
self.where(id: formatted_ids.map{ |fid| fid.gsub(/^STU/, '') })
end

Related

query phone number with different format in the model

There's a table named "Person" with attribute id as primary key and phone_number which is from user input so they are formatted in different ways. I need query the id with phone number.
For example, Person.where(:phone_number => 4155332321)
However, the number in the model could be 415-533-2321 or 4155332321. How could I write the query for that?
BTW, I can't change the phone number format in the model. Otherwise, I can convert the phone in the query and model to be the same format.
Thanks
I think you'll need a two-part approach to this.
First, you'll want to also save a "normalized" phone number to the database. This contains no formatting at all -- just numbers. This will require you to add a new column to your database, say, normalized_phone_number and write a before_save callback to store this value.
class Person
before_save :normalize_phone_number
def self.normalize_number(number)
number.gsub(/[^\d]/, '') if string.present?
end
def normalize_phone_number
self.normalized_phone_number = Person.normalize_number(self.phone_number)
end
end
Next, you'll want to write a custom class method to find the person based on a normalized number from user input. Your Person class will now include:
class Person
def self.with_normalized_phone_number(phone_number)
where(normalized_phone_number: normalize_number(phone_number)).first
end
end
You could also write Person.with_normalized_phone_number as an ActiveRecord scope, which would be my preference, but "Using a class method is the preferred way to accept arguments for scopes."

Field name in model versus field name in database

I have a model named end. It works fine in my development environment where I use SQLite.
But in production I get an error because of PostgreSQL where end is a reserved word.
I don't want to rename the field in the model, because there are too many files to edit.
Instead, I want to declare a mapping rule so that field name in model stay "end" but name of this field in database became end_date.
How I can do it?
Your best bet, long-term, is almost certainly to suck it up and change all your Ruby code to use end_date. Obviously, that's going to be tedious because end is a Ruby keyword too, meaning Search & Replace won't Just Work; so if you really can't face it try this.
Change the name in the database, then add the following two methods to your model:
class Widget < ActiveRecord::Base
def end
end_date
end
def end=(val)
self.end_date = val
end
end

Associating A User Model with an Array

So in my rails app, I'm creating a table of users, and in that table I have columns such as "email, password, and image". I want to add a concrete list of nationalities that a user can be associated with.
So, I created a user model:
rails g model User email password image:string
Now I want an array of nationalities... so instead of creating an array, I created a separate model for nationalities and want to associate it to my users. Assuming one can have multiple nationalities, I set it up like this:
rails g model Nationality name:string
So in user.rb I wrote:
has_many :nationalities
and in nationality.rb I wrote:
belongs_to :user
When a user signs up, I will have a list for them to choose from: American, Canadian, French, etc...
My gut tells me this might be an antipattern since a user won't ever be adding new nationalities to the list... only selecting, but an admin can always add more nationalities.
I'm not getting any errors, but it isn't working in my favor either.
Did I set it up correctly, or should I just use an array and store it in the user's table under a column called "nationalities"?
edit: Oops, I didn't address the part of having multiple nationalities. You may want to consider using acts_as_taggable_on
https://github.com/mbleigh/acts-as-taggable-on
It nicely handles the many-to-many behavior of tagging with some built in conveniences.
I think it's fine to use some kind of global array (in a namespace) for Nationalities for now, because as you note correctly, the kind of Nationalities will not change.
However, I think things will be a little more complicated than a straight-up Array...You say you will give choices such as American, Canadian, French...and that's fine, but what if the context of a view calls for using U.S., Canada, France instead? Then your Array becomes a Hash...I doubt it'll get complicated enough to require a full fledged Rails model. A simple Hash should cover most of those use cases.
I've found it helpful in such situations to create a separate Class or Module. For example, with Gender, it's most definitely not worth creating a separate DB table...and yet, even if all I store in a user's gender column is "M", "F", "O" (for other, or maybe not specified), I'll create a Gender module that can handle that domain logic:
class Gender
# better to make this private so that
# only the factory constructor is exposed
def initialize(g)
#g = g
end
def to_s # and equivalent methods for serializing Gender into a M/F for database
return #g.to_s
end
def pronoun
case #g
when :M
'he'
when :F
'she'
else
'they'
end
end
def Gender(str)
case str
when /^(?:m|male|man|boy)$/i
Gender.new(:M)
when /^(?:f|female|woman|girl)$/i
Gender.new(:F)
else
Gender.new(:O)
end
end
This is really more a database design question than a Rails question, but I would just have the Nationality table serve as the source for a dropdown, but the User table would simply have a nationality column. I don't see the need for the extra join. Of course, the values in the nationality column would come from user selections from that dropdown populated by the Nationality table.
Still, your needs may vary. Stick with the join if multiple nationalities are a requirement. As Knuth said, "Premature optimization is the root of all evil."
Are you using Rails 4? Is Postgresql your DB? If so this combination allows you to store arrays on your user model.
Basically you would modify your migration to look like this:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :name # or whatever attributes you've already defined
t.string :nationalities, array: true, default: []
t.timestamps
end
end
end
Your ActiveRecord model would contain this:
class User < ActiveRecord::Base
attr_accessible :nationalities
end
This would allow you to create the nationalities list like so:
user = User.create(name: 'Bob', nationalities: ['English', 'Australian', 'French'])
And obviously extract them like:
user.nationalities
=> ['English', 'Australian', 'French']
Still, the decision about having an association or a column on the user goes beyond the ease of creating the user with a nationality in a form. The real question is "what role will the nationality play in the app"? Meaning, does the nationality have additional data that would warrant it being it's own model? Or, can it all just sit in the user table?
If the nationality has as much functionality as a user name, than it can stay on the user. However, if nationality can have other attributes on it such as 'date_aquired' than it may have to be it's own model.

Rails - Good way to associate units of measurement with my database columns?

I've got one model with about 50 columns of measurement data, each with a different unit of measurement (ie. grams, ounces, etc.). What is a good way to associate units of measurement with columns in my database? The primary use for this is simply for display purposes. (Ruby on Rails)
EDIT: To clarify, my model is an object, and the attributes are different measurements of that object. So, an example would be if I had the model Car and the attribute columns :power, :torque, :weight, :wheelbase, etc. I would want car.power.unit to return hp and car.weight.unit to return lbs., etc. This way, I would be able to do something like this:
<%= car.power + car.power.unit %>
and it would return
400hp
Updated Answer
Since you're storing many columns of data, but each column is only one type, and your concern is strictly presentational, I would just use a decorator to accomplish what you need. See this railscast for an example of a great way to do this using Draper.
Basically, a decorator wraps your model with presentation specific methods, so instead of:
#CarsController.rb
def show
#car = Car.find(params[:id])
end
You would use
#CarsController.rb
def show
#car = CarDecorator.find(params[:id])
end
You would define a decorator like so:
class CarDecorator < ApplicationDecorator
decorates :car
def horsepower
model.power.to_s + "hp" #call to_s just in case
end
end
Then in your view any time you called #car.horsepower you would get 123hp instead of 123. In this way you can build a big long reusable list of presentation methods. You can share methods between objects using inheritance, and you can allow methods from the original model to be called as well. See the railscast and the docs etc. You can use Draper or you could roll your own presenter class if you don't want to use a library.
Previous Answer (Abridged):
I can see two nice, easy ways to do this:
1) Just add a text column for units to your data model. IE: to get "400hp" use [data.value,data.units].join
2) You could get a little richer association by having a Units model, perhaps with help from something like ActiveEnum.
You could add a unit model with a for attribute, where you save the attribute in the messurement, you want to apply the unit to. Example:
def Unit < ActiveRecord::Base
scope :for, lambda{|messurement| find_by_for( messurement.to_s ) }
end
This allows you stuff like:
<%= #car.torque + Unit.for(:torque).symbol %>
I do not know if this is of so much advantage, but its a way to solve your problem...

has_many through blowing away the association's metadata on mass association

Hey,
Not a Rails noob but this has stumped me.
With has many through associations in Rails. When I mass assign wines to a winebar through a winelist association (or through) table with something like this.
class WineBarController
def update
#winebar = WineBar.find(params[:id])
#winebar.wines = Wine.find(params[:wine_bar][:wine_ids].split(",")) // Mass assign wines.
render (#winebar.update_attributes(params[:wine_bar]) ? :update_success : :update_failure)
end
end
This will delete every winelist row associated with that winebar. Then it finds all of the wines in wine_ids, which we presume is a comma separated string of wine ids. Then it inserts back into the winelist a new association. This would be expensive, but fine if the destroyed association rows didn't have metadata such as the individual wine bar's price per glass and bottle.
Is there a way to have it not blow everything away, just do an enumerable comparison of the arrays and insert delete whatever changes. I feel like that's something rails does and I'm just missing something obvious.
Thanks.
Your problem looks like it's with your first statement in the update method - you're creating a new wine bar record, instead of loading an existing record and updating it. That's why when you examine the record, there's nothing showing of the relationship. Rails is smart enough not to drop/create every record on the list, so don't worry about that.
If you're using the standard rails setup for your forms:
<% form_for #wine_bar do |f| %>
Then you can call your update like this:
class WineBarController
def update
#winebar = WineBar.find(params[:id])
render (#winebar.update_attributes(params[:wine_bar]) ? :update_success : :update_failure)
end
end
You don't need to explicitly update your record with params[:wine_bar][:wine_ids], because when you updated it with params[:wine_bar], the wine_ids were included as part of that. I hope this helps!
UPDATE: You mentioned that this doesn't work because of how the forms are setup, but you can fix it easily. In your form, you'll want to rename the input field from wine_bar[wine_ids] to wine_bar[wine_ids_string]. Then you just need to create the accessors in your model, like so:
class WineBar < ActiveRecord::Base
def wine_ids_string
wines.map(&:id).join(',')
end
def wine_ids_string= id_string
self.wine_ids = id_string.split(/,/)
end
end
The first method above is the "getter" - it takes the list of associated wine ids and converts them to a string that the form can use. The next method is the "setter", and it accepts a comma-delimited string of ids, and breaks it up into the array that wine_ids= accepts.
You might also be interested in my article Dynamic Form Elements in Rails, which outlines how rails form inputs aren't limited to the attributes in the database record. Any pair of accessor methods can be used.

Resources