I have a built a little online tool using rails. The app has no database.
(I know I should have used another simpler framework, like Sinatra for Ruby).
So I need to capture selected car model from the interface and use its power, speed, and dimensions properties
Is it ok to use case statement, like the code below:
class Car
def get_car_properties (dropdown_selection)
info = {}
info=Hash.new{2}
case dropdown_selection
when 'Mazda 3'
info = { speed: 'fast', power: 'real strong', dimensions: '4X3X3'}
when 'Lancer 81'
info = { speed: 'slow', power: 'real weak', dimensions: '2X2X2'}
else
info={}
end
return info
end
end
selected_car=Car.new()
puts selected_car.get_car_properties('Mazda 3')
For simplifications, I have not used the exact scenario. Also note that my case statement is about 30 options big and the hash 10 symbols.
Big case statements are a code smell, meaning they are a hint that there may be an opportunity to improve your code.
In this case, while your app does not have a database, you still have data. The reason why your code looks messy right now is that you have not separated that data from your program logic.
I would recommend storing the data about each car in a hash, and then treat that hash as if it were your database.
class Car
CAR_INFO = {
'Mazda 3' => {
speed: 'fast',
power: 'real strong',
dimensions: '4X3X3'
},
'Lancer 81' => {
speed: 'slow',
power: 'real weak',
dimensions: '2X2X2'
},
# etc
}
def get_car_properties (dropdown_selection)
CAR_INFO.fetch(dropdown_selection, {})
end
end
selected_car=Car.new()
puts selected_car.get_car_properties('Mazda 3')
Why is this better? For one thing, the hash is pure data. It's clear that there's no additional control logic hiding somewhere in one of the branches of your case statement. You can now tell at a glance exactly what get_car_properties is doing - it's looking up the selection in a data structure, and returning an empty hash if nothing is found. You may wish to move CAR_INFO into a separate file, or perhaps into a proper database later on - get_car_properties won't change much in either of those cases.
The code you're written probably works, but definitely smells and would violate the ruby style guide.
I suggest you utilize a code analyzer, such as rubocop which will enforce the ruby style guide, and help you write conforming code and avoid common pitfalls.
Related
My app has various data formatting rules such as if value == "-" then display "N/A" or if value == "NULL" then display "N/A" or if value.is_a? Numeric then value.round(2) and various other such rules. I currently have a helper method that does it as follows
def display_formatted
case
when '.'
#...
end
end
Is it ok to MonkeyPatch the Object class to create a formatter like
class Object
def my_app_format
case self
when is_a?(Numeric)
# some rules
when "-"
# some other rule
else
self
end
end
Currently the code is littered with display_formatted(value) in hundreds of places and I want to add different formatters so I can use it as follows and on any data type and in plain Ruby not Rails!
value.as_count_formatted
value.as_default_formatted
value.as_default_rounded`
Reason for patching it on the Object class is that the data type is not always known as the source data is not schematic so a field can have numeric or string values.
Yes and no. But mostly no.
As stefan mentions in the comment, "you're merely replacing foo_bar(obj) with obj.foo_bar". I agree that this assessment seems to generally sum up your changes.
This change would be "okay" in the sense that there are no obvious reasons for it to problematically change program behavior.
But there is a problem that monkey patching is generally frowned up and regarded as hacky. A big reason for this is that monkey patching can be done anywhere and it can be difficult to track down if you are not the person who wrote it. Even worse, it can also be the source of subtle and hard to track down bugs if replacing an existing function (although that is not the case here).
Ruby gives almost unlimited flexibility to programmers, but care must be taken with that flexibility.
On a purely technical level (will it work and keep correct functionality?) the answer is yes. On a more opinionated level (is this considered good practice?) I would say no - and that would be my advice (don't do it). In general, best practice is to avoid monkey patching unless there is a very good reason for it.
Let's say I have a User with attributes name and badge_number
For a JavaScript autocomplete field I want the user to be able to start typing the user's name and get a select list.
I'm using Materialize which offers the JS needed, I just need to provide it the data in this format:
data: { "Sarah Person": 13241, "Billiam Gregory": 54665, "Stephan Stevenston": 98332 }
This won't do:
User.select(:name, :badge_number) => { name: "Sarah Person", badge_number: 13241, ... }
And this feels repetitive, icky and redundant (and repetitive):
user_list = User.select(:name, :badge_number)
hsh = {}
user_list.each do |user|
hsh[user.name] = user.badge_number
end
hsh
...though it does give me my intended result, performance will suck over time.
Any better ways than this weird, slimy loop?
This will give the desired output
User.pluck(:name, :badge_number).to_h
Edit
Though above code is one liner, it still have loop internally. Offloading such loops to database may improve the performance when dealing with too many rows. But there is no database agnostic way to achieve this in active record. Follow this answer for achieving this in Postgres
If your RDBMS is Postgresql, you can use Postgresql function json_build_object for this specific case.
User.select("json_build_object(name, badge_number) as json_col")
.map(&:json_col)
The whole json can be build using Postgresql supplied functions too.
User.select("array_to_json(array_agg(json_build_object(name, badge_number))) as json_col")
.limit(1)[0]
.json_col
I am aiming to serialise a set of objects into a file so as to create a backup. I have the start of that working, using a methods on the models (simplified here, assuming I have two ActiveRecords foo and bar):
def backup(file, foo, bar)
file.write(foo.to_json(root: true))
file.write(bar.to_json(root: true))
end
This gives me a file as I desire, in this case with two records:
{"foo":{"Account_id":1,"Name":"F","created_at":"2013-04-16T10:06:19Z","id":1,"updated_at":"2013-04-20T11:36:23Z"}}
{"bar":{"Account_id":1,"Name":"B","created_at":"2013-04-16T10:06:19Z","id":1,"updated_at":"2013-04-20T11:36:23Z"}}
At a later date I then want to read that backup in and reinstantiate those objects, probably then persisting them back to the database. My aim is to iterate through the file checking the type of each object, then instantiating the right object.
I have part of the logic, but not yet all of it, I haven't worked out how I determine the type of each serialised object before I instantiate it. The code I have for a restore is as follows:
def restore(file)
file.each_line do |line|
**<some magic that parses my line into objectType and objectHash>**
case objectType
when :foo
Foo.new.from_json(objectHash)
Foo.process
Foo.save!
when :bar
Bar.new.from_json(objectHash)
Bar.process
Bar.save!
end
end
end
What I'm looking for is the bit that goes in the "some magic" section. I can just write the code to parse the line directly to determine whether it's a foo or a bar, but I feel like there's probably some tricky Rails/Ruby way to do this that is automatic. Unfortunately, in this case Google is not being my friend. All I can see are pages that are focused on json in the web requests, but not parsing json back in this way. Is there something I'm missing, or should I just write the code to split the string directly and read the object type?
If I do write the code to split the string directly, I would write something along the lines of:
objectType = line[/^{"(\w*)"=>(.*)}/, 1]
objectHash = line[/{"(\w*)"=>(.*)}/, 2]
This is pretty ugly and I'm sure there's a better way (which I'm still looking into), but I'm not sure that this is even the right approach v's there being something that automatically looks at a json representation and knows from the root value what object to instantiate.
Lastly, the actual instantiation using from_json isn't working either, it isn't populating any of the fields on my ActiveRecord. It gives me nil parameters, so I think the parse syntax isn't right.
So, that makes three questions:
Is there a way to determine which object it is that I'm just missing, that is much cleaner?
If there isn't and I need to use a regexp, is there a syntax to get both bits of the line parsed in a single go, rather than my two lines with the same regexp?
The from_json syntax appears unhappy. Is there a syntax I'm missing here? (no longer a question - the code above is fixed, I was using as_json when it should have been to_json, although the documentation is rather unclear on that....)
(Note: edits over time to clarify my question, and because I've now got a regexp that works (didn't before), but still not sure it's very elegant.)
Further information - one of the problems here, as I dig into it further, is that the as_json isn't actually giving me json - what I have in the file is a hash, not json at all. Further, the values for created_at and lastupdated_at in the hash aren't quoted - so basically that's what's causing the parse on the way back in to fail. I've worked out that I should use to_json instead of as_json, although the documentation suggests that as_json should work.
I'm not sure I fully understand you're methodology, but I think using JSON.parse() would help.
There's some good information here http://mike.bailey.net.au/2011/02/json-with-ruby-and-rails/
This would help you translate the raw object back to a hash.
OK, so I think I've got something that works. I'm not convinced at all that it's elegant, but it gives me the result. I'll spend some time later trying to make it cleaner.
The code looks like this:
file.each_line do |line|
objectType = line[/^{"(\w*)":(.*)}/, 1]
objectJSON = line[/{"(\w*)":(.*)}/, 2]
objectHash = JSON.parse(objectJSON)
case objectType
when 'foo'
restoredFoo = Foo.new(objectHash.except('id', 'created_at', 'updated_at'))
restoredFoo.created_at = objectHash['created_at']
restoredFoo.updated_at = objectHash['updated_at']
restoredFoo.save!
end
when 'bar'
restoredBar = Bar.new(objectHash.except('id', 'created_at', 'updated_at'))
restoredBar.created_at = objectHash['created_at']
restoredBar.updated_at = objectHash['updated_at']
restoredBar.save!
end
end
Items of note:
I feel like there should be a way to create the object that isn't a JSON.parse, but rather would make use of the from_json method on the model. I'm not sure what the from_json is good for if it doesn't do this!!
I'm having fun with mass_assignment. I don't really want to use :without_protection => true, although this would be an option. My concern is that I do want the created_at and updated_at to be restored as they were, but I want a new id. I'm going to be doing this for a number of entities in my application, I didn't really want to end up replicating the attributes_protected in the code - it seems not very DRY
I'm still pretty sure my reg exp can give me both objectType and objectJSON in one call
But having said all that, it works, which is a good step forwards.
What is the best way to handle a static data set (non-dynamic)?
For instance, let's say you have a model that has a set of 10 different instances, each of which is unique, but none of which will ever change throughout the lifetime of your application. It seems overkill to create an activerecord model and store this data in the database, but it seems ugly to create a generic class and store this data in the code.
What is accepted as a best practice?
Example:
You have a Rate and a User. A User can have a level from 1-10, when the level changes, the rate changes. The rate might have other information, so simply storing it as an attribute on the User might be more trouble than it's worth. Would it make sense to tie it to a Rate or to create it as a method on the User like this:
def rate
case self.level
when 1:
{ value: "foo", something: "bar", else: "baz" }
when 2:
# etc
end
end
It seems that neither of the solutions are ideal, but I'm not sure if there is something else ideal that could happen.
I would store this information in a YAML file. You could use the RailsConfig gem and create a YAML file like
level:
1:
some: value
another: value
2:
some: second value
another: second value
And then access it with
rate = 2
val = Settings.level[rate.to_s].some
(I'm not completely sure with numbers as keys in YAML, maybe you have to escape them)
I use constants in this cases: constants do not change after the declaration, but the declaration can be dynamic:
OS =
case RUBY_PLATFORM
when /linux/ then :linux
when /osx/ then :osx
when /windows/ then :windows
else :unknown
Performance should be better when using constants for static values, because they should be memoized (and because staticity should be their purpose, so probably Ruby implementations trust about it; I read something about JRuby and constants implementation, I'll post it if I'll find. EDIT I found it: http://blog.headius.com/2012/09/avoiding-hash-lookups-in-ruby.html).
I was reading a text describing Ruby and it said the following:
Ruby is considered a “reflective”
language because it’s possible for a
Ruby program to analyze itself (in
terms of its make-up), make
adjustments to the way it works, and
even overwrite its own code with other
code.
I'm confused by this term 'reflective' - is this mainly talking about the way Ruby can look at a variable and figure out whether it's an Integer or a String (duck typing), e.g.:
x = 3
x = "three" # Ruby reassigns x to a String type
To say Ruby is "reflective" means that you can, for instance, find out at runtime what methods a class has:
>> Array.methods
=> ["inspect", "private_class_method", "const_missing",
[ ... and many more ... ]
(You can do the same thing with an object of the class.)
Or you can find out what class a given object is...
>> arr = Array.new
=> []
>> arr.class
=> Array
And find out what it is within the class hierarchy...
>> arr.kind_of?
>> arr.kind_of? Array
=> true
>> arr.kind_of? String
=> false
In the quote where they say "it’s possible for a Ruby program to analyze itself" that's what they're talking about.
Other languages such as Java do that too, but with Ruby it's easier, more convenient, and more of an everyday part of using the language. Hence, Ruby is "reflective."
No, it means that you can issue a ruby command to get information about, well, just about anything. For example, you can type the command File.methods() to get a listing of all methods belonging to the File module. You can do similar things with classes and objects -- listing methods, variables, etc.
Class reopening is a good example of this. Here's a simple example:
class Integer
def moxy
if self.zero?
self - 2
elsif self.nonzero?
self + 2
end
end
end
puts 10.moxy
By reopening a standard Ruby class - Integer - and defining a new method within it called 'moxy', we can perform a newly defined operation directly on a number. In this case, I've defined this made up 'moxy' method to subtract 2 from the Integer if it's zero and add two if it's nonzero. This makes the moxy method available to all objects of class Integer in Ruby. (Here we use the 'self' keyword to get the content of the integer object).
As you can see, it's a very powerful feature of Ruby.
EDIT: Some commenters have questioned whether this is really reflection. In the English language the word reflection refers to looking in on your own thoughts. And that's certainly an important aspect of reflection in programming also - using Ruby methods like is_a, kind_of, instance_of to perform runtime self-inspection. But reflection also refers to the the ability of a program to modify its own behavior at runtime. Reopening classes is one of the key examples of this. It's also called monkey patching. It's not without its risks but all I am doing is describing it here in the context of reflection, of which it is an example.
It refers mainly at how easy is to inspect and modify internal representations during run-time in Ruby programs, such as classes, constants, methods and so on.
Most modern languages offer some kind of reflective capabilities (even statically typed ones such as Java), but in Ruby, it is so easy and natural to use these capabilities, that it really make a real difference when you need them.
It just makes meta-programming, for example, an almost trivial task, which is not true at all in other languages, even dynamic ones.