How to map a model's integer attribute to a string? - ruby-on-rails

I have a Hotels table in my database, and one of the columns is :status (integer). I'm looking to convert these integers into strings, so 1 = "Awaiting Contract", 2 = "Designing" and so on...
I have searched Stack for some answers, and the lack of them makes me think that I'm coming at this problem from the wrong angle? I used to do this in PHP whilst pulling the data. New-ish to Rails so any help, or best practise advice would be much appreciated.

Check enum of ActiveRecord - doc.
Here you can configure your :status:
class Hotel < ActiveRecord::Base
enum status: { waiting_contract: 1, designing: 2 }
def format_status
status.to_s.humanize
end
end
It'll create methods like this:
hotel.waiting_contract?
hotel.designing?
hotel.waiting_contract!
hotel.format_status # => "Waiting contract"
Hope that helps!
UPDATE
Similar functionality might be achieved by overriding the status method itself, although having separate methods is more advised:
class Hotel < ActiveRecord::Base
enum status: { waiting_contract: 1, designing: 2 }
def status
super.to_s.humanize
end
end
Furthermore, decorators are something you should look into for view-specific methods.

It depends what you need the list for. An alternative to the above ideas, is to create a hash. Hashes are very Ruby and designed just for this sort of paired data.
Create the hash, (enumeration typing is automatic.)
my_h = { "waiting" => 1, "design" => 2 }
Then to access
my_h["waiting"] = 1
There's much more you can do with hashes. This is just the simplest case.
A hash may or not fulfill your needs, but it's a splendid tool that comes with a nice set of Ruby worker methods.
http://ruby-doc.org/core-2.2.0/Hash.html

Related

How to map using constants in Ruby

I'm trying to map an array of custom values using a constant that's already defined. I'm running into some problems.
This works perfectly:
Car.where(brand: car_brands.map(&:Car_honda))
Although I have all the car brands already defined in my file, so I would prefer to use the constants over rewriting the names. For example:
HONDA = "Car_honda"
When I try and map this constant to the array it doesn't seem to work properly:
Car.where(brand: car_brands.map(&:HONDA))
I tried to use a block with map, but I still got the same result:
Car.where(brand: car_brands.map {|c| c.HONDA}))
Are we able to use constants with map?
Just use send:
Car.where(brand: car_brands.map { |c| c.send(HONDA) })
I'm not sure where you're going with this, or precisely where you're coming from, but here's an example that follows Rails conventions:
class Brand < ActiveRecord::Base
has_many :cars
end
class Car < ActiveRecord::Base
belongs_to :brand
end
Then you can find all cars associated with the "Honda" brand:
Brand.find_by(name: 'Honda').cars
Or find all cars matching one or more arbitrary brand names using a JOIN operation:
Car.joins(:brand).where(brand: { name: %w[ Honda Ford ] })
If you go with the flow in Rails things are a lot easier.
Are you able to use constants with map?
Nope. Not like this, anyhow.
car_brands.map { |c| c.HONDA }
This means, for every thing in car_brands call method HONDA on it and return results of the invocations. So, unless you have method HONDA defined (which you absolutely shouldn't), this has no chance to work.
Constants are defined on the class, not on the object. You can invoke them through .class.
:005 > Integer.const_set('ABC', 1)
=> 1
:006 > Integer::ABC
=> 1
:007 > [1,2,3].map {|i| i.class::ABC}
=> [1, 1, 1]
This will not work for your use case unless car_brands contains an array of different Car classes.
First off, you probably don't want to things this way. Perhaps it's the way your example is worded, but the only way it makes sense, as I'm reading it, is if Car_brands is an array of classes. And if that's the case, if doesn't make sense to have a constant called HONDA. If anything, you would have a constant called BRAND that might equal "Honda" for a given class. I strongly recommend you rethink your data structures before moving forward.
All that said, you can use const_get to access constants using map. e.g.
class CarBrand1
BRAND = 'Honda'
end
class CarBrand2
BRAND = 'Toyota'
end
car_brands = [CarBrand1, CarBrand2]
car_brands.map{|car_brand| car_brand.const_get("BRAND")}
car_brands.map{|car_brand| car_brand::BRAND} # Alternatively
# => ["Honda", "Toyota"]

rubocop string interpolation and size condition

Before I except these two methods I wanted to see if anyone in the community had a better idea to structure these and make the cops pass. The first one with to_s seems a bit crazy too. I was thinking of refactoring the other method but that would be a single line or two.
Thoughts?
Code Examples One:
def destroy(resource_name, id)
delete "#{resource_name.to_s.pluralize}/#{id}"
end
Code Examples Two:
def all_products
products_map = fetch(:products).map { |x| [x['id'], x] }.to_h
variants = fetch :variants
variants.group_by { |x| x['product']['resource']['id'] }.to_a.map do |product_id, product_variants|
product.merge 'variants' => product_variants if product == products_map[product_id]
end.compact
end
For Code example One, maybe this can be used:
delete [resource_name.to_s.pluralize, id].join('/')
For Code example Two, yes you definitely need to refactor it.
Maybe you need to create a separate method that does all the grouping and merging, etc. for the variants part.
I am not sure if this is a good practice, but you can create a private method for it.

Clean url with rails

I can't use any of the gems for creating clean Urls in rails. Instead I am rolling out my own implementation. I have created the following entry in routes.rb
match "/:slug" => "cleanurls#index"
Where cleanurl is a controller for handling all such requests. In the cleanurl controller:
class CleanurlsController < ApplicationController
def index
slug = params['slug']
url = Url.where(:slug => slug).first
case(url.url_type)
when 'profile'
user_id = url.id.to_i
#profile = Profile_info.getProfileDetails(user_id)
render '/profiles/index'
end
end
end
I have created the table urls which stores the slug,id (as relevant) and the type of page. Right now I have only the profile page to deal with but in the future I will have different types of pages with clean urls.
My first Question:
1) Is this implementation the right approach? And is this okay from a performance perspective given the tables have all the right indexes.
I am making the profile url like this:
def self.makeProfileUrl(id,name)
name = name.strip.titleize
extension = User.where(:name => name).count - 1
slug = name.split(" ").join("-")
if extension != 0
slug += "-#{extension}"
end
Url.create(:slug => slug, :id => id.to_i, :url_type => 'profile')
end
I am using extension to append a count in case their are users who share the same name.
Question:
Is this the right way to create the slug and ensure it being unique? Fetching the count of a name from the other table does not seem right.
Answering the question #1:
I don't know the details of what's your overall goal, but if you'd like
to have such URLs that are based on records from the database - then yes: it's
a good approach.
Answering question #2 (regarding slugs):
I'd rather use something much more elaborate and well tested like:
https://github.com/norman/friendly_id
My 50 cents about some other things:
Is this one of your first projects in Ruby/Rails? If so - congratulations! :)
I'm asking because I noticed that you're using camel case here and there...
Also:
user_id = url.id.to_i
Why do you call this #to_i method here? Did you set up this id as a string
or something?
Hope this helps

Fill array with nils for when nothing returned from query in ruby (RoR)

I have a model called foo with a date field.
On my index view, I am showing a typical "weekly view" for a specified week. To put the data in my view, I loop through each day of the specified week and query the data one day at time. I do this so that I can make sure to put a NIL on the correct day.
foos_controller.rb
for day in 0..6
foo = Foo.this_date(#date+day.days).first
#foos[day] = foo
end
index.html.haml
- for day in 0..6
%li
- if #foos[day].nil?
Create a new foo?
- else
Display a foo information here
Obviously, there's a lot of things wrong here.
I should find someone smart member to tell me how to write a good query so that I only have to do it once.
I should not have any if/else in my view
My goal here is to either show the content if the it is there for a particular day or show a "create new" link if not.
thanks for the help in advance!!
First, I have no idea what this_date actually does, but I'll assume it's retrieving a record with a specific date from your datastore. Instead of doing 7 queries, you can condense this into one using a date range:
Foo.where(date: (#date..(#date + 6.days)))
You can tack on a .group_by(&:date) to return something similar to the hash you are manually constructing, but using the actual dates as keys instead of the date offset.
To iterate over the dates in the view, I would recommend using Hash#fetch, which allows you to define a default return when a key is not present, e.g:
hash = { :a => 1, :b => 2 }
hash.fetch(:a){ Object.new } #=> 1
hash.fetch(:c){ Object.new } # #<Object:...>
The question now is what object to substitute for nil. If you want to avoid using conditionals here, I'd recommend going with the NullObject pattern (you could involve presenters as well but that might be a bit overkill for your situation). The idea here is that you would create a new class to substitute for a missing foo, and then simply define a method called to_partial_path on it that will tell Rails how to render it:
class NullFoo
def to_partial_path
"null_foos/null_foo"
end
end
You'll need to create partials at both app/views/foos/_foo.html.erb and app/views/null_foos/_null_foo.html.erb that define what to render in each case. Then, in your view, you can simply iterate thusly:
<% (#date..(#date + 6.days)).each do |date| %>
<%= render #foos.fetch(date){ NullDate.new } %>
<% end %>
Is this appropriate for your situation? Maybe it's also a bit overkill, but in general, I think it's a good idea to get in the habit of avoid nil checks whenever possible. Another benefit of the NullObject is that you can hang all sorts of behavior on it that handle these situations all throughout your app.

Ruby on Rails 3: Associating extra data with a model

Pretty much a total beginner to Ruby/Rails.
I have a Video with many Sections:
#video = Video.find(params[:id])
#sections=#video.sections;
I want to associate a colour attribute with each Section, but the colour is calculated in the controller, rather than stored in the database.
So far I have been simply creating a #colours array in my controller where the index matched up with the index of the section
hues = [41, 6, 189, 117, 279]
saturation = 100;
brightness = 45;
#colours = [];
#sections.each { #colours.push Color::HSL.new(hues[j%hues.length], saturation, brightness) }
so that #sections[i] corresponds to #colours[i].
This works fine, but doesn't seem like the best approach. I would like to extend my Sections model so that it has a 'colour' attribute, so that I could access it by doing #sections[i].colour
I tried putting this in models/sectiondata.rb :
class SectionData
extend Section
attr_accessor :colour
end
but when I try to do SectionData.new in my Controller I get an error saying it can't find the class. I also don't know how I would get the original #section to be part of the new SectionData class.
What is the best way to approach this problem? Any tips on my coding style would also be appreciated, Ruby is a big step away from what I'm used to.
I think its a better idea to implement hashes in this situation instead of having two arrays corresponding to each other.
For example,
result_hash = {sports_section => 'blue'}
First I tried putting them as attr_accessors in the Section model, don't do that. Bad coder! They will not show up if ActiveRecord queries the database again for that page load, even if the query is cached. If you need the ActiveRecord methods you can use ActiveModel
Instead, I created a class for the attribute accessors:
sectiondata.rb:
class SectionData
attr_accessor :left, :width, :timeOffset, :colour, :borderColour
end
Then you can work with them using an object which maps a Section object to SectionData:
xxx_controller.rb
#video = Video.find(params[:id])
#sections=#video.sections
require 'sectiondata.rb'
#sectionDatas=Hash.new
#sections.each do |i|
sd=SectionData.new
# set whatever attributes here, e.g.
sd.left= total/multiplier
#sectionDatas[i]=sd
end
Then, given a Section object called 'section', you could access it using #sectionDatas[section], and this will work for any database queries that occur in the page of

Resources