<< operator in ruby - ruby-on-rails

I am quite new to ruby,
I came across the following code in rails, but I don't know how the "<<" operator works and what it does in the below code
def <<( rate )
r = Rating.new
r.rate = rate
r.rateable = proxy_owner
...
...
end
class << ActiveRecord::Base
...
...
end
Can anybody explain to me?
Edit: here is the code https://github.com/azabaj/acts_as_rateable/blob/master/lib/acts_as_rateable.rb

def <<( rating ):
In your example, this is used to add a rating to a rateable model. (E.g. in acts_as_rateable.rb:41), similar to appending something to a string (str << "abc"). As it is within a module, it will only be included for the models that you declare as rateable.
class << ClassName:
All the methods inside of this block will be static / class methods (see this blog entry). (In this case, all the models will have the methods Model.example_static_method.)

Nearly all operators in Ruby are actually instance methods called on the object preceding them.
There are many different uses for << depending on the object type you're calling it on. For example, in an array this works to push the given value onto the end of the array.
It looks like this is for a Rails model object, so in that case I would say that this is an auxiliary method called when you append a model object to model object collection. For example, in this case you might be appending a Rating to a Product.
If you showed the whole method definition and showed what class it's in, I could provide a more specific answer.

Related

How to return something from a Concern and update a record?

I want to move some class methods for some of my models that are the same to a concern.
One method takes an input and returns something which then should be used to update a record like this:
def create_sort_title
self.sort_title = self.title.gsub(/^the |^a |^an /i, '').gsub(/'|"|!|\?|-/, '').gsub(/(?<!\w) /,'')
end
While it's clear that I need to refactor it to take an input:
def create_sort_title(title)
self.sort_title = title.gsub...
...
I don't understand how I can RETURN a value to update a record - i. e. how to replace the self.sort_title above with something that then is used to update the sort_title of the corresponding record?
The method is called by a before_safe hook:
before_save :create_sort_title
(and I understand once I take an argument, I need to use a lambda like this:
before_save -> { create_sort_title(title) }
You don't really need to pass any arguments if you're using a concern that is included on the model. When you do include ConcernName you're adding class and/or instance methods on the model class/instance itself. So your create_sort_title method that is called from before_save callback will have access to all instance methods, including title and will work as-is just fine

Using Variables for Model Names in Active Record

I am writing a seed file that will make several API calls via HTTParty in order to populate the database. I am pulling the same information for several different models and I would like to be able to use a single method for all of them. However, I cannot figure out how to reference the model name through a variable. Specifically I am having difficulties because each of these must belong to another model. I have tried the following:
def create_assets(subject, model, geokit_hoods)
response = HTTParty.get("https://raw.githubusercontent.com/benbalter/dc-maps/master/maps/#{subject}.geojson")
parsed = JSON.parse(response)
collection = parsed["features"]
collection.each do |station|
coordinates = station["geometry"]["coordinates"].reverse
point = Geokit::LatLng.new(coordinates[0], coordinates[1])
geokit_hoods.each do |hood|
if hood[1].contains?(point)
hood[0][model].create(coordinates: coordinates, name: station["properties"]["NAME"], address: station["properties"]["ADDRESS"])
break
end
end
end
end
Which I called via the following:
create_assets("metro-stations-district", "metros", geokit_hoods)
hood[0] refers to an existing neighborhood model, and hood[1] is the polygon associated with that neighborhood. The code works when referring to hood[0].metros.create(...), but I am looking for a way to make this method useful across many models.
Any ideas would be appreciated!
For now I'm going to assume that what you have in the variable is a String that is the name of the class in table-name format. eg in your example you have metros in the variable... from that I assume you have a Metro class which you are trying to create.
If so... you first need to convert your lowercase table-name style variable ("metros") into a class name-style eg "Metro"
Note: this is title cased and singular (rather than plural).
Rails has a method to do this to strings for exactly the purpose you want: classify eg you could use it thus:
model_name = hood[0][model] # 'metros'
model_name.classify # 'Metro'
Note that it's still just a string, and you can't call create on a string.. so how do you make it the real class? constantize
Use this to turn the string into the actual model-class you're trying to find... which you can then call create on eg:
model_name = hood[0][model] # 'metros'
the_klass = model_name.classify.constantize # Metro
your_instance = the_klass.create(...)

How does the model respond to the method :find?

I have been trying to get used to Rails and Ruby for the last two months and I`ve got a small question that stuck in my mind in order to be able to see the bigger picture more clearly.
Let`s assume I have a model named Playlist and I already have some entries in my database:
class Playlist < ActiveRecord::Base
end
As far as I understand we can reach a model instance which should correspond to a raw in database via the following:
p = Playlist.find(2)
First, the result "p" is an instance of the model Playlist, right? Second, I am trying to understand where the ":find" method is implemented or how the model responds to it. Is it a class method for my model or a class method for ActiveRecord::Base? Because I couldn`t list ":find" in any of the outputs below.
puts p.class.methods(false).sort
puts p.class.superclass.methods(false).sort
puts p.class.superclass.superclass.methods(false).sort
Thanks.
It's a class method of Playlist that it has inherited from ActiveRecord::Base.
Try:
ActiveRecord::Base.methods
https://github.com/rails/rails/blob/master/activerecord/lib/active_record/relation/finder_methods.rb
Try this to get the Classes and/or Modules containing the :find method:
x = Playlist.ancestors
arr = []
m = :find
x.each{ |i| arr << i if i.method_defined?(m) }
puts "The following Classes and/or Modules contain the :#{m.to_s} method:"
puts arr
The first printed result is where the method m is located. The results below it list the ancestor Class and/or Modules (in order) containing the 'original' method before modification (although they may contain different methods entirely using the same method-name). This may come in handy if you want to investigate the relevant method further.
N.B. Only public and protected methods are matched using the method_defined? method.

Where should method go (model?, somewhere else)

I've got a method in one of my models which returns data which will be fed into a charting gem.
class MyModel < ActiveRecord::Base
def ownership_data
format_data(item_ownerships.group(:owned).count)
end
end
I need to guarantee that the data return always has 2 values in the result. Something like this:
{ "yes" => 4, "no" => 2 }
In order to do this, I've written another method which is used in the first method:
def format_data(values)
values[false].nil? ? values = values.merge({ "no" => 0 }) : true
values[true].nil? ? values = values.merge({ "yes" => 0 }) : true
return values
end
My question is, where should this method go and how can I unit test it using rspec? I've currently got it in the model, however in trying to test it with rspec, my specs look like this:
let(:values) { { "yes" =>2 } }
it "it will return 2 values" do
result = MyModel.new.format_data(values)
expect(result.keys.count).to eq(2)
end
I'm not too happy about having to instantiate an instance of the model to test this. Any guidance is appreciated.
As AJ mentioned in the comment, we probably need more information to go on to give more specific advice. I'll give some anyway...
If you have a object that isn't necessarily depending on the model itself, you should consider moving it to a plain old ruby object or a service object. Those can live in a separate folder (lib/services) or in your models folder without inheriting from ActiveRecord::Base.
Your method could also be a class method def self.method_name(values, values_2) instead of a instance method (if you don't need specific values from the model).
When it comes to data manipulation for reporting for my projects, I've built specific folder of ruby service objects for those under 'lib/reports' and they take raw data (usually in init method) and return formatted data from a method call (allowing me to have multiple calls if the same data can be formatted in different output options). This makes them decoupled from the model. Also, this makes testing easy (pass in known values in Class.new expect specific values in method outputs.

How to determine a const in some model class?

There is the following model for 'delivery_types' table:
class DeliveryType < ActiveRecord::Base
end
I want to determine a special delivery type, for example, "DELIVERY_BY_TIME", and I want that this const returns DeliveryType.first (I'll put info about this type in my table later). Is it possible? How can I do it? Thanks.
I don't think you can do this, as this is no "real const". What you could do though, is creating a class method, called "by_time", which returns your "by_time" object. I would also not rely on the fact that this is your "first" object. Rather I would use a "find_or_create_by_name("BY_TIME"), which always makes sure you deliver the right object. Combined, something like
def self.by_time
##by_time||= find_or_create_by_name!(name: 'BY_TIME')
end
def by_time?
self == DeliveryType.by_time
end
If you read "Rails anti-patterns", they discourage you from making separate classes for "status" fields. They recommend to just use a string for that in your parent object, with some validators that limit the list of values though...

Resources