What is the purpose of defining methods in a model? - ruby-on-rails

What is the purpose of defining methods inside a model like the example here? What does this get me? I was under the impression that only the fields of a model are defined in the model.
class Bean
include Mongoid::Document
field :name, type: String
field :roast, type: String
field :origin, type: String
field :quantity, type: Float
has_many :pairings
# has_many :pastries
def pastries
Pastry.find pastry_ids
end
#accepts_nested_attributes_for :pastries
def pastry_ids
pastry_ids_array = []
self.pairings.each do |one_pairing|
if one_pairing.pastry_id
pastry_ids_array.push one_pairing.pastry_id
end
end
pastry_ids_array
end
def pastry_ids=(list)
self.pairings.destroy
list.each do |pastry_id|
self.pairings.create(pastry_id: pastry_id)
end
end
# some way of showing a list
def pastry_list
pastries_string = ""
pastries.each do |one_pastry|
pastries_string += ", " + one_pastry.name
end
pastries_string.slice(2,pastries_string.length - 1)
pastries_string
end
end

I don't know if you know enough ruby but let's say you don't. This is a basic Class question? Defining methods on a model it's like having an helper. Let's say that you have
class CanadianPopulation
attr_accessor :population, :number_of_french_speaker, :number_of_english_speaker
def initialize(a,b,c)
#population = a
#number_of_french_speaker = b
#number_of_english_speaker = c
end
def total_people_that_have_a_different_mother_tongue
#Canadian who speak english or french but have a different mother tongue
self.population - (self.number_of_french_speaker + self.number_of_english_speaker)
end
end
census_2014 = CanadianPopulation.new(34_000_000, 4_000_000, 12_000_000)
let's say that you didn't have the method total_people_that_have_a_different_mother_tonguehow will you do to retrieve the total number of Canadians that have a different mother tongue? you will do the caculation yourself like for a view
<p>Canadian who speak english or french but have a different mother tongue
<br>
<%= #census = #census.population - (#census.number_of_english_speaker + #census.number_of_french_speaker) %>
</p>
Your view or your controller shouldn't do much logic (calculations) so that's one of the reason why you have a method inside the model (or class) it should be like this
<p>Canadian who speak english or french but have a different mother tongue
<br>
<%= #census.total_people_that_have_a_different_mother_tongue %>
</p>
For the second part of your question what does those methods do. rails c -s on your terminal than call or create a new instance model Bean and check to see what it does (the output/results)
Bean.first
b = _
b.pastries
b.pastry_ids
b.pastry_list
edit: #paul-richer recommends to maintain a thin controller

Related

Refactor some model methods in Ruby on Rails

I have some methods in my model which I use to access stored text hashes in my views:
class CarSpec < ActiveRecord::Base
##fuel_type_select_data = Hash[(1..5).to_a.zip(['Petrol', 'Diesel', 'Gas/petrol', 'Hybrid', 'Electric'])]
##mileage_type_select_data = Hash[(1..2).to_a.zip(['km', 'miles'])]
##transmission_select_data = Hash[(1..3).to_a.zip(['Manual', 'Automatic', 'Tiptronic'])]
##wheel_drive_data = Hash[(1..3).to_a.zip(['Front', 'Rear', 'All'])]
##color_data = Hash[(1..8).to_a.zip(['black', 'white', 'beige',
'blue', 'yellow', 'green', 'red', 'silver'])]
def text_for_fuel_type
##fuel_type_select_data[fuel_type]
end
def text_for_mileage_type
##mileage_type_select_data[mileage_type]
end
def text_for_transmission
##transmission_select_data[transmission]
end
def text_for_wheel_drive
##wheel_drive_data[wheel_drive]
end
def text_for_color
##color_data[color]
end
def text_for_interior_color
##color_data[interior_color]
end
Currently, I need to write a new method for every field. How can I refactor these methods, so that I do not need to write a new method for each field? Please include how the new method/s would be called in the view.
Using constants instead of class variables would be much better in your case.
An example:
class CarSpec < ActiveRecord::Base
WHEEL_DRIVE_DATA = {'Front' => 1}
# remaining class code
end
Sample code for view:
<%= CarSpec::WHEEL_DRIVE_DATA['Front'] %>
Agree with #Humza: constants are better than class variables here.
The current set of methods can be defined dynamically - with some metaprogramming magic - as follows:
fields = %w{ fuel_type mileage_type transmission wheel_drive interior_color }
fields.each do |field|
define_method("text_for_#{field}") do
CarSpec.class_variable_get("###{field}_select_data").send(:[], eval(field))
end
end
Note:
For the above to work, the class variables for all fields will have to be consistent with the ###{field}_select_data name; so ##wheel_drive_data should be changed to ##wheel_drive_select_data, etc.

Accessing the variable from another model in my controller

So, I want to access one of the variables from my quotes model:
class Quotes < ActiveRecord::Base
attr_accessible :quotes1, :quotes10, :quotes2, :quotes3, :quotes4, :quotes5, :quotes6, :quotes7, :quotes8, :quotes9
attr_accessor :quotes1, :quotes10, :quotes2, :quotes3, :quotes4, :quotes5, :quotes6, :quotes7, :quotes8, :quotes9
end
Here is my pages controller, because I want to access it in views/pages/home.html.haml:
def home
#quotes = Quotes.find(:first, :order => "RANDOM()")
end
And here is my home.html.haml:
%h2
Client's opinions
%p
=#quotes
All of these quotes from 1...10 are strings, and have default values, they are not nil. And still I am not getting anything on my home page. Why?
controller:
quotes_number = (srand % 10) + 1
#quotes = Quotes.find(:first, :order => "RANDOM()").send(:"quotes#{quote_number}")
view:
%h2
Client's opinions
%p
=#quotes

How to write a virtual attribute's getter and setter methods?

My product model belongs to a telephone and I wanted to know how I would join both together for a virtual attribute. I can create a product and can find or create the telephone by it's number with the code below.
Product < ActiveRecord::Base
attr_accessible :name, :telephone_number
belongs_to :telephone
def telephone_number
telephone.try(:number)
end
def telephone_number=(number)
self.telephone = Telephone.find_or_create_by_number(number) if number.pr....
end
Now for getter method I put:
def product_with_number
[name, telephone_number].join(',')
end
But I'm not sure what's next or if this is what I'm getting at. I'm trying to make a single field where I can type in:
Cow's Beef Jerky, 555-323-1234
Where the comma seperates the product name and telephone and if the telephone number is new, it will create it. Am I on the right track, what next?
Thanks.
You need a corresponding setter:
def product_with_number=(str)
parts = str.split(',')
self.telephone_number = parts[0]
self.name = parts[1]
end
Then all you'd do is something like this:
#p = Product.New
#p.product_with_number = "Cow's Beef Jerky, 555-323-1234"
#p.save

how to put embedded document into an embedded document?

I have a form that has a category model and and embeded docuement called "FieldModule" and this has embedded document called "SubFieldModule"
For example
class Category
include MongoMapper::Document
key :name, String
many :field_modules
end
class FieldModule
include MongoMapper::EmbeddedDocument
key :name, String
many :sub_field_modules
end
class SubFieldModule
include MongoMapper::EmbeddedDocument
key :name, String
end
In my controller i edit action i have :
#category = Category.find(params[:id])
3.times do
#category.field_modules << FieldModule.new()
end
To set up 3 FieldModules for the category.
I want to be able to do the same for each FieldModules SubFieldModules like so
#category.field_modules.each do |mf|
mf << SubFieldModule.new()
end
but it doesnt work.
i get error:
NoMethodError in Sub categoriesController#edit
undefined method `<<' for #<FieldModule name: nil, _id: $oid4c2b9f594248ce19f000011b>
Anyone help me out on this ? as i then need to take it one level deeper doing the same.
Try this:
#cat = Category.new(:name => "Blah")
3.times do
#cat.field_modules << FieldModule.new()
end
#cat.field_modules.each do |mf|
mf.sub_field_modules << SubFieldModule.new()
end

ruby object array... or hash

I have an object now:
class Items
attr_accessor :item_id, :name, :description, :rating
def initialize(options = {})
options.each {
|k,v|
self.send( "#{k.to_s}=".intern, v)
}
end
end
I have it being assigned as individual objects into an array...
#result = []
some loop>>
#result << Items.new(options[:name] => 'name', options[:description] => 'blah')
end loop>>
But instead of assigning my singular object to an array... how could I make the object itself a collection?
Basically want to have the object in such a way so that I can define methods such as
def self.names
#items.each do |item|
item.name
end
end
I hope that makes sense, possibly I am overlooking some grand scheme that would make my life infinitely easier in 2 lines.
A few observations before I post an example of how to rework that.
Giving a class a plural name can lead to a lot of semantic issues when declaring new objects, as in this case you'd call Items.new, implying you're creating several items when in fact actually making one. Use the singular form for individual entities.
Be careful when calling arbitrary methods, as you'll throw an exception on any misses. Either check you can call them first, or rescue from the inevitable disaster where applicable.
One way to approach your problem is to make a custom collection class specifically for Item objects where it can give you the information you need on names and such. For example:
class Item
attr_accessor :item_id, :name, :description, :rating
def initialize(options = { })
options.each do |k,v|
method = :"#{k}="
# Check that the method call is valid before making it
if (respond_to?(method))
self.send(method, v)
else
# If not, produce a meaningful error
raise "Unknown attribute #{k}"
end
end
end
end
class ItemsCollection < Array
# This collection does everything an Array does, plus
# you can add utility methods like names.
def names
collect do |i|
i.name
end
end
end
# Example
# Create a custom collection
items = ItemsCollection.new
# Build a few basic examples
[
{
:item_id => 1,
:name => 'Fastball',
:description => 'Faster than a slowball',
:rating => 2
},
{
:item_id => 2,
:name => 'Jack of Nines',
:description => 'Hypothetical playing card',
:rating => 3
},
{
:item_id => 3,
:name => 'Ruby Book',
:description => 'A book made entirely of precious gems',
:rating => 1
}
].each do |example|
items << Item.new(example)
end
puts items.names.join(', ')
# => Fastball, Jack of Nines, Ruby Book
Do you know the Ruby key word yield?
I'm not quite sure what exactly you want to do. I have two interpretations of your intentions, so I give an example that makes two completely different things, one of them hopefully answering your question:
class Items
#items = []
class << self
attr_accessor :items
end
attr_accessor :name, :description
def self.each(&args)
#items.each(&args)
end
def initialize(name, description)
#name, #description = name, description
Items.items << self
end
def each(&block)
yield name
yield description
end
end
a = Items.new('mug', 'a big cup')
b = Items.new('cup', 'a small mug')
Items.each {|x| puts x.name}
puts
a.each {|x| puts x}
This outputs
mug
cup
mug
a big cup
Did you ask for something like Items.each or a.each or for something completely different?
Answering just the additional question you asked in your comment to tadman's solution: If you replace in tadman's code the definition of the method names in the class ItemsCollection by
def method_missing(symbol_s, *arguments)
symbol, s = symbol_s.to_s[0..-2], symbol_s.to_s[-1..-1]
if s == 's' and arguments.empty?
select do |i|
i.respond_to?(symbol) && i.instance_variables.include?("##{symbol}")
end.map {|i| i.send(symbol)}
else
super
end
end
For his example data you will get following outputs:
puts items.names.join(', ')
# => Fastball, Jack of Nines, Ruby Book
puts items.descriptions.join(', ')
# => Faster than a slowball, Hypothetical playing card, A book made entirely of precious gems
As I don't know about any way to check if a method name comes from an attribute or from another method (except you redefine attr_accessor, attr, etc in the class Module) I added some sanity checks: I test if the corresponding method and an instance variable of this name exist. As the class ItemsCollection does not enforce that only objects of class Item are added, I select only the elements fulfilling both checks. You can also remove the select and put the test into the map and return nil if the checks fail.
The key is the return value. If not 'return' statement is given, the result of the last statement is returned. You last statement returns a Hash.
Add 'return self' as the last line of initialize and you're golden.
Class Item
def initialize(options = {})
## Do all kinds of stuff.
return self
end
end

Resources