ActiveRecord accessor dot(.) vs bracket([]) - ruby-on-rails

With Active Record, we can access a value like
method access
user = User.find(1)
user.name #=> 'John'
or
hash access
user[:name] #=> 'John'
I just wonder when to use which, or is there any best practice out there?
Personally I'd prefer method access because I feel that is more like ruby way. However when I see code by others, I face the hash access.

Rails convention is to use ActiveRecord::AttributeMethods::Read#read_attribute (dot notation), rather than its alias ActiveRecord::AttributeMethods#[], which:
Returns the value of the attribute identified by attr_name after it
has been typecast (for example, “2004-12-12” in a date column is cast
to a date object, like Date.new(2004, 12, 12)). It raises
ActiveModel::MissingAttributeError if the identified attribute is
missing.

I would strongly advise against using bracket notation as it breaks the inheritance hierarchy of method calls and makes refactoring harder.
If my model has an attribute name, and I decide I want to enhance the name every time someone reads it, a very idiomatic way to do that would be:
def name
"Awesome #{super}!"
end
Any place in my app that uses the method version would work fine, any place that uses the [] notation would return raw database data. I could overwrite the [] but then I would need special conditions checking for specific attributes. The whole thing would be a nightmare.
Another scenario, let's say I had an attribute that used to be stored in the database, but after a while decide that it should be computed on the fly, and end up dropping the database column. With the method version all I would need to do is add methods to my model. With the [] notation the process would be much much harder.
Also [] provides an insignificant performance improvement so though it looks like it's "closer" to the raw data it really isn't.

Related

NoSQL Injection with a simple find_by in RoR

I am new to RoR and I have a basic question.
A colleague told me that the following code can cause a NoSQL Injection:
User.find_by(username: param[:username])
I know that I am not supposed to read directly from param but I still think that the code cannot generate any NoSQL injections. Am I wrong?
Your colleague is wrong, and should produce a working exploit if he believes this is true.
Providing a hash of key/value pairs to Rail's find_by or where methods is the intended, secure, and correct way of using those methods. The keys and values are always made safe before being used in a query.
There are certain problems that Rails will not protect you from, for example, the value of params[:username] could be a gigabyte of text, a File object, a nested hash of key/value pairs, etc. but this isn't an "injection" per se. Any special characters that could cause the meaning of the query to be changed will have no effect.

Rails methods that can work with multiple tables and column names

hope you are all well!
I have a general enquiry that I was wondering if some kind soul could help with? it's really a matter of curiosity at the minute but I feel like it could be quite a useful snippet of information in the future.
Is it possible to write a method that can be passed the name of a table and the name of an attribute (column) and perform operations on these? I suppose the main use for such methods would be for keeping code dry when doing repetitive operations on tables.
as an example (though entirely a toy example) suppose I had a method:
def switch(table_name, column_name)
#do some operation on table_name.column_name
end
I have figured out how to access a table by doing something like this:
def model_for_table(table_name)
table_name.to_s.classify.constantize
end
this will take an underscored_lowercase_string and return the table name so that something like model_for_table("registered_user").find(1) though this is unnecessary in situations where the table name can be hard coded
But it does not like model_for_table("registered_user").column_name as used in the example above. is there something analogous to the model_for_table method supplied above to turn a string into an attribute name?
Does anybody know how I could implement this? is it even possible?
Thanks in advance
The problem is that you need an instance of the model you are working on in order to access a column. If you have a RegisteredUser model try doing (in a Rails console) RegisteredUser.id (or any attribute name). It won't work. However, if you did RegisteredUser.first.id (assuming you have one saved) it will work.
So it depends on what you want to accomplish. If your switch method is meant to do something with instances of your model, then this can still work.
def switch(table_name, column_name)
model = model_for_table(table_name)
model.all.each do |model_instance|
puts "model_instance #{column_name} is #{model_instance.send(column_name)}"
end
end
Note: The send method takes in a symbol or a String and executes the method with that name on the instance it was called on. This is a normal Ruby thing, not a Rails thing.
Remember, your model_for_table method is returning back the class, not an instance.

Which approach should I use to store translation of products in database?

I'm creating an application where products will be created by my customer (something like an e-commerce website), so I obviously require translated descriptions stored in database, I can't force my customer to learn git/yml.
I have two ideas on how to correctly localize descriptions (and eventually product name) and store them in database, but if there is a well-known approach that I should use, I would be really happy to know it.
The first idea seems the most logical for me, but I would like to make it "transparent" to me, I don't want to write joins everywhere, so some suggestion on how to achieve this, if it's the correct one, would be appreciated.
Idea 1:
Create a database table products (with name and description field set maybe to the default locale language), then a products_translations table which contains a table structured in this way:
products_translations
- id
- locale
- product_id
- name
- description
As an example: product_translation: { id: 1, locale: 'en', product_id: 3, name: 'toy', description: 'play' }
But I want to access to translations without the requirement to write a lot of IFs everywhere. So if I write product.name it should return en or it based on current locale.
Bonus: Are there any gems that can help me to achieve this?
Idea 2: The other idea is to have a table with name_locale1, name_it and so on, but I don't like this approach because will pollute my model objects with fields and I will have a giant table.
However, in this way I can avoid join on every query for that object.
If there is a greater approach which I don't know about (a database pattern or similar), it's ok that too, I'm not forced to strict to only these two ideas, however I have to choose between the two and I really don't know which could be better.
Important: I would like to keep translations stored in yml files, except for dynamic contents, which obviously require translations in database.
I agree with PinnyM that the first approach is the better of the two, but rather than implement your own schema, I would highly recommend you implement Globalize3 where most of the structural decisions have been taken for you (and by Mr Fuchs himself, no less). Also, with the rails helpers, you just call something like product.name on a model instance and the gem will figure out how to display it in the correct locale, awesome!
The first approach is the recommended one. As you surmised, the second approach is not as clean and requires more work on the coding end with no real gain since you still have to join on this monster table. To the contrary, the first method requires at most one join, while the second approach requires a join on each attribute you may want to add localization support.
You can simply append a scope on all your product calls such as:
scope :for_locale, lambda{|locale| joins(:product_translations).
where(product_translations: {locale: locale || 'en'}) }
and pass in the session locale (or wherever you are storing it).

With Rails 2.x, how do I handle a table with a "valid" column?

I've got a table that includes a column named "valid". This has caused a problem after updating to Rails 2. ActiveRecord is expecting "def valid?" to do validation, not return a boolean value from the database.
How do I work around this problem? Is renaming the column my only option?
As documented elsewhere, there are things you can do, but I'm going to suggest that they're probably going to be more trouble in the long run than biting the bullet and renaming the column.
If your database is not open to other apps, that is - otherwise you're just going to suffer to some extent whatever you do...
Why rename? One of the greatest benefits that we get from Rails is convention over configuration. The "magic", if you will. (Some say that it's actually a bad thing, but go with me one this). If you retain a column named "valid", then nyou're making your models inconsistent: this one needs to work differently from the others and that's bad. Or you could monkey-patch ActiveRecord::Base perhaps, so then all your models work the same but your app no longer follows convention.
From personal experience: I created a column named "user_id" which ActiveRecord, by convention, considered a foreign key (as it does anything ending in "_id"). I coded around it, which I now think was a mistake. Another item on the to-do list...
It's not necessarily wrong to go against Rails conventions: there are plenty of places where you can do so and they're well-documented. On the ActiveRecord side, many are specifically designed to reduce difficulty in connecting to legacy database schemas, for example. Take a good look at the pros and cons, as you're obviously doing, and weigh up your options.
I can prevent the crash by adding the following to my model, but it's not entirely satisfactory:
class << self
def instance_method_already_implemented?(method_name)
return true if method_name == 'valid?'
super
end
end
Do you need to see the column in your model? If not, overriding ActiveRecord::Base.columns will do the trick...
def self.columns
super.delete_if {|c| c.name == 'valid' }
end
You can access the attribute through the [] notation:
row[:valid] = "foo"
You'll get the DangerousAttributeError if you try to initialize an object like this:
row = MyModel.new :valid => "foo"
To prevent that, you can define an attribute setter for valid, like this:
def valid=(x)
self[:valid] = x
end
The valid? method will still be for row validation. You could define a different question method, like val? to get at the boolean, like this:
def val?
query_attribute('valid')
end
Now you can use row.val? to test the boolean

How to iterate ActiveRecord Attributes, including attr_accessor methods

I've looked everywhere for an elegant solution. The essential problem seems to be that ActiveRecord attributes that map to database columns are handled completely differently in ActiveRecord::Base than attr_accessor methods.
I would like to do something like:
model.attribute_names.each do |name|
# do stuff
end
in a way that also includes attr_accessor fields, but not any other instance methods. I know this in not built-in, but what is the most elegant way to do it?
You can't really solve this. You can approximate a hack, but it's not something that will ever work nicely.
model.attribute_names should get you all the ActiveRecord ones, but the attr_accessor fields are not fields. They are just ordinary ruby methods, and the only way to get them is with model.instance_methods.
Idea 1
You could do model.attribute_names + model.instance_methods, but then you'd have to filter out all your other normal ruby methods initialize, save, etc which would be impractical.
To help filter the instance_methods you could match them up against model.instance_variables (you'd have to account for the # sign in the instance variables manually), but the problem with this is that instance variables don't actually exist at all until they are first assigned.
Idea 2
In your environment.rb, before anything else ever gets loaded, define your own self.attr_accessor in ActiveRecord::Base. This could then wrap the underlying attr_accessor but also save the attribute names to a private list. Then you'd be able to pull out of this list later on. However I'd advise against this... monkey-patching core language facilities like attr_accessor is guaranteed to bring you a lot of pain.
Idea 3
Define your own custom_attr_accessor in ActiveRecord::Base, which does the same thing as Idea 2, and use it in your code where you want to be able to retrieve the attribute names. This would be safe as you won't be clobbering the built-in attr_accessor method any more, but you'll have to change all your code to use custom_attr_accessor where neccessary
I guess in summary, what are you trying to do that needs to know about all the attr_accessor fields? Try look at your problem from a different angle if you can.
I came here looking to do the same thing, and found out it was the wrong approach altogether thanks to Orion's answer.
Incase anyone else's use case is similar to mine, here's my solution. I was using attr_accessor to add extra properties to the models after querying them from ActiveRecord. I then wanted to output the results as JSON etc.
A better solution is to first convert the Models from ActiveRecord into regular hashes, and then add the attr_accessor properties as regular hash keys.
Example:
model_hash = model_from_activerecord.attributes.to_options
model_hash[:key] = value
The solution I came up with for myself builds upon Orion Edwards' answer.
The code:
klass_attributes = klass.column_names + klass.instance_methods(false).
map(&:to_s).keep_if{|a| a.include?('=')}.map{|a| a.sub('=', '')}
The breakdown:
klass.instance_methods(false) brings back only instance methods, and not inherited methods.
map(&:to_s) converts the array of symbols into an array of strings so we can use the include? method. Also needed to merge with array of strings returned by column_names.
keep_if{|a| a.include?('=')} will remove all strings within the array that do not have an equals sign. This was important, since I noticed that attr_accessor attributes all had '='. Ex: 'app_url' vs 'login_url='
map{|a| a.sub('=', '')} is the final piece that then removes the '=' from each string in the array.
You end up with an array of strings that represent attributes, including attr_accessor attributes.

Resources