Student.find(:all, :conditions => [‘name = ? and status = ?’ ‘mohit’, 1])
Vs
Student.find_all_by_name_and_status(‘mohit’, 1)
Both the queries will result the same set of row but first one is preferable cause in the second way there will be exception generated method_missing and then rails will try to relate it as dynamic method. if fine then result set to returned.
Can any body explain me this in a good manner. What exactly is happening behind the screen. Please correct me if i am wrong.
You are right, the second way will go through a method_missing. ActiveRecord will parse the method name and if it is a valid name, it will generate a method on the fly.
If you look in the source of ActiveRecord::Base, in method_missing you'll see that developers left us a comment of how this generated method would look like:
# def self.find_by_login_and_activated(*args)
# options = args.extract_options!
# attributes = construct_attributes_from_arguments(
# [:login,:activated],
# args
# )
# finder_options = { :conditions => attributes }
# validate_find_options(options)
# set_readonly_option!(options)
#
# if options[:conditions]
# with_scope(:find => finder_options) do
# find(:first, options)
# end
# else
# find(:first, options.merge(finder_options))
# end
# end
So you see that generally it boils down to the same find method.
I would not say that the first way is preferable because of method_missing, because the performance penalty for that is negligible. The second way reads better and works well if you just need to fetch records based on attributes equal to some values.
However, this second form does not allow you to do anything beyond equality comparison (e.g., range comparison, "not equal to" expressions, joins, etc.). In such cases, you'll just have to use the find method with appropriate conditions and other parameters.
Related
I have a Model user with the following method:
def number_with_hyphen
number&.insert(8, "-")
end
When I run it several times in my tests I get the following output:
users(:default).number_with_hyphen
"340909-1234"
(byebug) users(:default).number_with_hyphen
"340909--1234"
(byebug) users(:default).number_with_hyphen
"340909---1234"
(byebug) users(:default).number_with_hyphen
"340909----1234"
It changes the number ?Here are the docs https://apidock.com/ruby/v1_9_3_392/String/insert
When I restructure my method to:
def number_with_hyphen
"#{number}".insert(8, "-") if number
end
If works like expected. The output stays the same!
How would you structure the code, how would you perform the insert?
which method should I use instead. Thanks
If you're using the insert method, which in the documentation explicitly states "modifies str", then you will need to avoid doing this twice, rendering it idempotent, or use another method that doesn't mangle data.
One way is a simple regular expression to extract the components you're interested in, ignoring any dash already present:
def number_with_hyphen
if (m = number.match(/\A(\d{8})\-?(\d+)\z/))
[ m[1], m[2] ].join('-')
else
number
end
end
That ends up being really safe. If modified to accept an argument, you can test this:
number = '123456781234'
number_with_hyphen(number)
# => "12345678-1234"
number
# => "123456781234"
number_with_hyphen(number_with_hyphen(number))
# => "12345678-1234"
number_with_hyphen('1234')
# => "1234"
Calling it twice doesn't mangle anything, and any non-conforming data is sent through as-is.
Do a clone of the string:
"#{number}".clone.insert(8, '-')
That ActiveModel::Dirty doesn't cover Array.push (or any other modify-in-place methods, as I've read extremely recently) for attributes pertaining to, say, postgres arrays is pretty well-established. For example, if an Apple model has an array Apple.seeds, you'll see the following in a Rails console.
johnny = Apple.new()
# => <Apple #blahblahblah>
johnny.seeds
# => [] (assuming [] default)
johnny.seeds << "Oblong"
# => ["Oblong"]
johnny.changed?
# => false
johnny.seeds = []
johnny.seeds += ["Oblong"]
# => ["Oblong"]
johnny.changed?
# => true
So you can use two different ways of changing the array attribute, but Rails only recognizes the one that uses a setter. My question is, is there a way (that won't mangle the Array class) to get push to behave like a setter in the context of an ActiveRecord object, so that johnny.seeds << (x) will reflect in johnny.changes?
(On my end, this is to prevent future developers from using push on array attributes, unwittingly failing to record changes because they were not aware of this limitation.)
This is a problem with any column with a mutable object, not just Array objects.
seeder = Apple.first
seeder.name
=> "Johnny "
seeder.name << " Appleseed"
seeder.changed?
=> false
You're better off leaving a note for future developers, but otherwise you can consider replacing the changed? method
class Apple
alias_method 'old_changed?', 'changed?'
def changed?
return old_changed? if old_changed?
return (seeds.count > 0) if new_record?
return seeds != Apple.find(id).seeds
end
end
However, note that just because changed? comes backtrue, does not assure you that fields with unchanged object_ids will be updated in update_attributes... you may find that they're not. You might need to hire competent rails developers who understand these pitfalls.
I have some code that is chugging through a set of Rails Active Record models, and setting an attribute based on a related value from a 2D Array.
I am essentially setting a US State abbreviation code in a table of US States which was previously only storing the full names. A library of state names is being used to derive the abbreviations, and it contains a 2D Array with each sub-array having a full name, and an abbreviation (i.e., [['New York', 'NY']['Pennsylvania', 'PA'][etc]]). I compare the state name from each record in the database to each full text name in this Array, then grab the corresponding sibling Array cell when there is a match.
This code works fine, and produces the correct results, but its frumpy looking and not easily understood without reading many lines:
# For the following code, StatesWithNames is an Active Record model, which is
# having a new column :code added to its table.
# Sates::USA represents a 2D Array as: [['StateName', 'NY']], and is used to
# populate the codes for StatesWithNames.
# A comparison is made between StatesWithNames.name and the text name found in
# States::USA, and if there is a match, the abbreviation from States::USA is
# used
if StatesWithNames.any?
StatesWithNames.all.each do |named_state|
if named_state.code.blank?
States::USA.each do |s|
if s[0] == named_state.name
named_state.update_column(:code, s[1])
break
end
end
end
end
end
What is the most Ruby style way of expressing assignments with logic like this? I experimented with a few different procs / blocks, but arrived at even cludgier expressions, or incorrect results. Is there a more simple way to express this in fewer lines and/or if-end conditionals?
Yea, there is a few ifs and checks, that are not needed.
Since it is Rails even though it does not state so in question's tags, you might want to use find_each, which is one of the most efficient way to iterate over a AR collection:
StatesWithNames.find_each do |named_state|
next unless named_state.code.blank?
States::USA.each do |s|
named_state.update_column(:code, s[1]) if s[0] == named_state.name
end
end
Also be aware, that update_column bypasses any validations, and if you wish to keep your objects valid, stick to update!.
And last thing - wrap it all in transaction, so if anything goes wrong all the way - it would rollback any changes.
StatesWithNames.transaction do
StatesWithNames.find_each do |named_state|
next unless named_state.code.blank?
States::USA.each do |s|
named_state.update!(:code, s[1]) if s[0] == named_state.name
end
end
end
You might use a different data structure for this.
With your existing 2D array, you can call to_h on it to get a Hash where
a = [['California', 'CA'], ['Oregon', 'OR']].to_h
=> { 'California' => 'CA', 'Oregon' => 'OR' }
Then in your code you can do
state_hash = States::USA.to_h
if StatesWithNames.any?
StatesWithNames.all.each do |named_state|
if named_state.code.blank?
abbreviation = state_hash[named_state.name]
if !abbreviation.nil?
named_state.update_column(:code, abbreviation)
end
end
end
end
the first thing you want to do is convert the lookup from an array of arrays to a hash.
state_hash = States::USA.to_h
if StatesWithNames.any?
StatesWithNames.all.select{|state| state.code.blank?}.each do |named_state|
named_state.update_column(:code, state_hash[named_state.name]) if state_hash[named_state.name]
end
end
I want to filter certain user information so that sensitive info is not captured in the logs. However, in one case, I want to log the format instead of the default, "[FILTERED]".
For example, I'd like to make this transformation:
# user input value to log
"e6132 DG71" => "a9999 AA99"
This would help us troubleshoot users' issues.
Can I use a custom filter (maybe a proc?) for Rails' filter_parameters method?
Append a lambda filter
You can append a lambda filter to Rails.application.config.filter_parameters. Only parameters that aren't caught by the typical filtering will go on to the custom filtering.
# Typical filters - will show as [FILTERED]
Rails.application.config.filter_parameters += [
:password
]
# Custom filter - this only sees what the previous list didn't catch
Rails.application.config.filter_parameters << lambda do |param_name, value|
if %w[foo_param bar_param].include?(param_name) && value.respond_to?(:gsub!)
# Alter the string in place because we don't have access to
# the hash to update the key's value
value.gsub!(/[a-z]/, "a")
value.gsub!(/[A-Z]/, "A")
value.gsub!(/[0-9]/, "9")
# Stick this at the beginning to make it easy to see in logs
value.gsub!(/\A/, "[FORMAT FILTERED]")
end
end
Note that the default filters match as regexes; :password is treated as /password/. You could do likewise for the custom filter:
if [/foo/, /bar/].detect {|r| r.match(param_name) }
# ...
end
Reading the ActionDispatch::Http::FilterParameters docs for Rails 4.2, there is an option to pass a block to parameter_filter to customize the filter behaviour. I assume you can do something like this:
env["action_dispatch.parameter_filter"] = lambda do |k,v|
your_transformation(v) if k =~ /secret/i
end
where your_transformation knows how to deal with the user input labeled secret to transform it into a9999 AA99. I also guess you'll have to handle your already present filtered_attributes to keep the [FILTERED] behavior.
In Grails, if I define a locale, and put a date on specific format on i18n file, like (dd/mm/AAAA), if call one request like:
http://myapp/myaction?object.date=10/12/2013
When I get print: params.date, it comes to me a date object.
How can I do the same on rails?
Normally the Rails handles this for you. For instance, the form helper datetime_select works in conjunction with some activerecord magic
to ensure ensure time/date types survive the round-trip. There are various alternatives to the standard date-pickers.
If this doesn't work for you e.g. rails isn't generating the forms, there are (at least) a couple of options.
One option, slightly evi, is to monkey-patch HashWithIndifferentAccess (used by request params) to do type conversions based on the key name. It could look something like:
module AddTypedKeys
def [](key)
key?(key) ? super : find_candidate(key.to_s)
end
private
# look for key with a type extension
def find_candidate(key)
keys.each do |k|
name, type = k.split('.', 2)
return typify_param(self[k], type) if name == key
end
nil
end
def typify_param(value, type)
case type
when 'date'
value.to_date rescue nil
else
value
end
end
end
HashWithIndifferentAccess.send(:include, AddTypedKeys)
This will extend params[] in the way you describe. To use it within rais, you can drop it into an initialiser, eg confg/initializers/typed_params.rb
To see it working, you can test with
params = HashWithIndifferentAccess.new({'a' => 'hello', 'b.date' => '10/1/2013', 'c.date' => 'bob'})
puts params['b.date'] # returns string
puts params['b'] # returns timestamp
puts params['a'] # returns string
puts params['c'] # nil (invalid date parsed)
However... I'm not sure it's worth the effort, and it will likely not work with Rails 4 / StrongParameters.
A better solution would be using virtual attributes in your models. See this SO post for a really good example using chronic.