Rails Association Extensions: Override a HABTM assignment (collection=) method - ruby-on-rails

In a question I've previously answered, I used an association extension to override a HABTM collection's append (<<) method (Also, a similar question):
has_and_belongs_to_many(:countries) do
def <<(country)
if [*country].all? {|c| c.is_a?(String) }
countries = Country.where(:code => country)
concat(*countries)
else
concat(country)
end
end
end
This is probably not encouraged, but my question is, how can one override, if even possible, the assignment operator, so I can do countries = ['IL', 'US'] with the same results?

I think that it could be something like:
def =(countries)
values = countries.kind_of?(Hash) ? countries.values : countries
values.each do |country|
self << country
end
end
I'm not sure if you can use the operator << that way.

Related

Creating a scope from an array constant

I have a situation where I have an array constant that I'd like to perform a string search on through a scope. I usually use AR to accomplish this but wasn't sure how to incorporate this with a static array. Obviously using a where clause wouldn't work here. What would be the best solution?
class Skills
SALES_SKILLS = %w(
Accounting
Mentoring
...
)
# Search above array based on "skill keyword"
scope :sales_skills, ->(skill) { }
end
May be using Enumerable#grep and convert string to case ignoring regexp with %r{} literal
class Skills
SALES_SKILLS = %w(
Accounting
Mentoring
#...
)
def self.sales_skills(skill)
SALES_SKILLS.grep(%r{#{skill}}i)
end
end
Skills.sales_skills('acc')
#=> ["Accounting"]
Skills.sales_skills('o')
#=> ["Accounting", "Mentoring"]
Skills.sales_skills('accounting')
#=> ["Accounting"]
Skills.sales_skills('foo')
#=> []
It would be better to create a method for this as you want to return a string. Scope is designed to return an ActiveRecord::Relation:
Scoping allows you to specify commonly-used queries which can be referenced as method calls on the association objects or models. With these scopes, you can use every method previously covered such as where, joins and includes. All scope bodies should return an ActiveRecord::Relation or nil to allow for further methods (such as other scopes) to be called on it.
Reference: https://guides.rubyonrails.org/active_record_querying.html#scopes
class Skills
SALES_SKILLS = %w(
Accounting
Mentoring
#...
)
def self.sales_skills(skill)
SALES_SKILLS.select do |sales_skill|
sales_skill.downcase.include?(skill.downcase)
end
end
end

In a Rails helper method, is it possible to specify a column name as a helper method variable and then use it?

I want to create a helper method that can turn the results of a Rails find into a sentence, where I specify the the results, and the column to use for making the sentence. For example:
def items_to_sentence(items, label_column)
items.map { |u| u.(label_column) }.to_sentence
end
I'm just not sure how to tell Rails to use my specified column.
Thanks for looking.
If items contains ActiveRecord objects (or any other objects that have accessor methods that match up with your column names), then you could use send:
def items_to_sentence(items, label_column)
items.map { |u| u.send(label_column) }.to_sentence
end
Or equivalently:
def items_to_sentence(items, label_column)
items.map(&(label_column.to_sym)).to_sentence
end
Or, if that's too noisy:
def items_to_sentence(items, label_column)
sym = label_column.to_sym
items.map(&sym).to_sentence
end

Rails, using time_select on a non active record model

I am trying to use a time_select to input a time into a model that will then perform some calculations.
the time_select helper prepares the params that is return so that it can be used in a multi-parameter assignment to an Active Record object.
Something like the following
Parameters: {"commit"=>"Calculate", "authenticity_token"=>"eQ/wixLHfrboPd/Ol5IkhQ4lENpt9vc4j0PcIw0Iy/M=", "calculator"=>{"time(2i)"=>"6", "time(3i)"=>"10", "time(4i)"=>"17", "time(5i)"=>"15", "time(1i)"=>"2009"}}
My question is, what is the best way to use this format in a non-active record model. Also on a side note. What is the meaning of the (5i), (4i) etc.? (Other than the obvious reason to distinguish the different time values, basically why it was named this way)
Thank you
You can create a method in the non active record model as follows
# This will return a Time object from provided hash
def parse_calculator_time(hash)
Time.parse("#{hash['time1i']}-#{hash['time2i']}-#{hash['time3i']} #{hash['time4i']}:#{hash['time5i']}")
end
You can then call the method from the controller action as follows
time_object = YourModel.parse_calculator_time(params[:calculator])
It may not be the best solution, but it is simple to use.
Cheers :)
The letter after the number stands for the type to which you wish it to be cast. In this case, integer. It could also be f for float or s for string.
I just did this myself and the easiest way that I could find was to basically copy/paste the Rails code into my base module (or abstract object).
I copied the following functions verbatim from ActiveRecord::Base
assign_multiparameter_attributes(pairs)
extract_callstack_for_multiparameter_attributes(pairs)
type_cast_attribute_value(multiparameter_name, value)
find_parameter_position(multiparameter_name)
I also have the following methods which call/use them:
def setup_parameters(params = {})
new_params = {}
multi_parameter_attributes = []
params.each do |k,v|
if k.to_s.include?("(")
multi_parameter_attributes << [ k.to_s, v ]
else
new_params[k.to_s] = v
end
end
new_params.merge(assign_multiparameter_attributes(multi_parameter_attributes))
end
# Very simplified version of the ActiveRecord::Base method that handles only dates/times
def execute_callstack_for_multiparameter_attributes(callstack)
attributes = {}
callstack.each do |name, values|
if values.empty?
send(name + '=', nil)
else
value = case values.size
when 2 then t = Time.new; Time.local(t.year, t.month, t.day, values[0], values[min], 0, 0)
when 5 then t = Time.time_with_datetime_fallback(:local, *values)
when 3 then Date.new(*values)
else nil
end
attributes[name.to_s] = value
end
end
attributes
end
If you find a better solution, please let me know :-)

Refactoring Model Methods in Ruby On Rails

A common idiom that my camp uses in rails is as follows:
def right_things(all_things, value)
things = []
for thing in all_things
things << thing if thing.attribute == value
end
return things
end
how can I make this better/faster/stronger?
thx
-C
def right_things(all_things, value)
all_things.select{|x| x.attribute == value}
end
If your things are ActiveRecord models and you only need the items selected for your current purpose, you may, if you're using Rails 2.0 (? definitely 2.1) or above, find named_scopes useful.
class Thing
named_scope :rightness, lambda { |value| :conditions => ['attribute = ?', value] }
end
So you can say
Thing.rightness(123)
, which is (in this case) similar to
Thing.find_by_attribute(123)
in that it boils down to a SQL query, but it's more easily chainable to modify the SQL. If that's useful to you, which it may not be, of course...

Uniq by object attribute in Ruby

What's the most elegant way to select out objects in an array that are unique with respect to one or more attributes?
These objects are stored in ActiveRecord so using AR's methods would be fine too.
Use Array#uniq with a block:
#photos = #photos.uniq { |p| p.album_id }
Add the uniq_by method to Array in your project. It works by analogy with sort_by. So uniq_by is to uniq as sort_by is to sort. Usage:
uniq_array = my_array.uniq_by {|obj| obj.id}
The implementation:
class Array
def uniq_by(&blk)
transforms = []
self.select do |el|
should_keep = !transforms.include?(t=blk[el])
transforms << t
should_keep
end
end
end
Note that it returns a new array rather than modifying your current one in place. We haven't written a uniq_by! method but it should be easy enough if you wanted to.
EDIT: Tribalvibes points out that that implementation is O(n^2). Better would be something like (untested)...
class Array
def uniq_by(&blk)
transforms = {}
select do |el|
t = blk[el]
should_keep = !transforms[t]
transforms[t] = true
should_keep
end
end
end
Do it on the database level:
YourModel.find(:all, :group => "status")
You can use this trick to select unique by several attributes elements from array:
#photos = #photos.uniq { |p| [p.album_id, p.author_id] }
I had originally suggested using the select method on Array. To wit:
[1, 2, 3, 4, 5, 6, 7].select{|e| e%2 == 0}
gives us [2,4,6] back.
But if you want the first such object, use detect.
[1, 2, 3, 4, 5, 6, 7].detect{|e| e>3} gives us 4.
I'm not sure what you're going for here, though.
I like jmah's use of a Hash to enforce uniqueness. Here's a couple more ways to skin that cat:
objs.inject({}) {|h,e| h[e.attr]=e; h}.values
That's a nice 1-liner, but I suspect this might be a little faster:
h = {}
objs.each {|e| h[e.attr]=e}
h.values
Use Array#uniq with a block:
objects.uniq {|obj| obj.attribute}
Or a more concise approach:
objects.uniq(&:attribute)
The most elegant way I have found is a spin-off using Array#uniq with a block
enumerable_collection.uniq(&:property)
…it reads better too!
If I understand your question correctly, I've tackled this problem using the quasi-hacky approach of comparing the Marshaled objects to determine if any attributes vary. The inject at the end of the following code would be an example:
class Foo
attr_accessor :foo, :bar, :baz
def initialize(foo,bar,baz)
#foo = foo
#bar = bar
#baz = baz
end
end
objs = [Foo.new(1,2,3),Foo.new(1,2,3),Foo.new(2,3,4)]
# find objects that are uniq with respect to attributes
objs.inject([]) do |uniqs,obj|
if uniqs.all? { |e| Marshal.dump(e) != Marshal.dump(obj) }
uniqs << obj
end
uniqs
end
You can use a hash, which contains only one value for each key:
Hash[*recs.map{|ar| [ar[attr],ar]}.flatten].values
Rails also has a #uniq_by method.
Reference: Parameterized Array#uniq (i.e., uniq_by)
I like jmah and Head's answers. But do they preserve array order? They might in later versions of ruby since there have been some hash insertion-order-preserving requirements written into the language specification, but here's a similar solution that I like to use that preserves order regardless.
h = Set.new
objs.select{|el| h.add?(el.attr)}
ActiveSupport implementation:
def uniq_by
hash, array = {}, []
each { |i| hash[yield(i)] ||= (array << i) }
array
end
Now if you can sort on the attribute values this can be done:
class A
attr_accessor :val
def initialize(v); self.val = v; end
end
objs = [1,2,6,3,7,7,8,2,8].map{|i| A.new(i)}
objs.sort_by{|a| a.val}.inject([]) do |uniqs, a|
uniqs << a if uniqs.empty? || a.val != uniqs.last.val
uniqs
end
That's for a 1-attribute unique, but the same thing can be done w/ lexicographical sort ...
If you are not married with arrays, we can also try eliminating duplicates through sets
set = Set.new
set << obj1
set << obj2
set.inspect
Note that in case of custom objects, we need to override eql? and hash methods

Resources