Rails newbie wondering why the find method is called on the Article class but not the instanced model.
#article = Article.find(params[:id])
It makes sense to me to do Article.new since we are instantiating an object from the Article class but why do we call the find method on the Article class even for Article.all?
Article is associated with the whole collection. And it is logical to have find on Collection to find a record from the collection. It is more clearly than having Article.new.load(params[:id]).
More than that it's considered to be a common practice, consider next example:
class Article
##instances = []
attr_accessor :name
def initialize(name)
#name = name
##instances << self
end
def self.find(name)
##instances.find { |i| i.name == name }
end
end
article = Article.new('New Article')
Article.find('New Article') #=> #<Article:0x00000001668040>
About Rails. find is one of those methods which can be called on a model or on a Relation. Some of those methods do even return Relation, for example all, where, order, limit and offset. And it is nice because you can form a chain:
Article.where(..) #=> Relation
Article.where(..).find(12) #=> record with id: 12 satisfying some additional requirements
Related
I have a model in ruby on rails with the below code, which uses a singelton class definition. Also, som metaprogramming logic. But, I don't understand when this code will invoke.Is it when an attribute below specified is editing?
class Product < ApplicationRecord
class << self
['cat_no', 'effort', 'impact', 'effect', 'feedback'].each do |attr|
define_method "update_#{attr}" do |pr, count, user_id|
pr.order=pr.cat_no
pr.idea=pr.description
pr.update("#{attr}"=>count,:last_modified_by=>user_id)
end
end
end
end
Please help.
Thanks
This code generates five methods, one for each attribute name in the list. All these generated methods take three arguments and will basically look like this (I use the impact attribute name as an example):
def self.update_impact(pr, count, user_id)
pr.order = pr.cat_no
pr.idea = pr.description
pr.update("impact" => count, :last_modified_by => user_id)
end
That means there are five methods generated that update the passed in pr with some data from itself and with a count and a user_id.
Note that this method only deals with a specific pr therefore it is certainly better to use an instance instead of a class method as Stefan already suggested in his comment. And IMO there is not really a benefit in meta-programming here. I would change the logic to
def update_count(type, count, user_id) # or any another name that makes sense in the domain
if type.in?(%i[cat_no effort impact effect feedback])
update(
:order => cat_no,
:idea => description,
:last_modified_by => user_id,
type => count
)
else
raise ArgumentError, "unsupported type '#type'"
end
end
and call it instead of
Model.update_impact(pr, count, user_id)
like this
pr.update_count(:impact, count, user_id)
I joined Rails team and maintain the codes.
Some of the objects are controlled by Gem virtus, but I really don't understand like below code is doing.
I understand the result that the attribute 'latest_book' can collect latest book from Books but why it can be done? What 'books=(books)' is doing? and Why 'super books' is here?
class GetBooks
include Virtus.model
include ActiveModel::Model
attribute :books, Array[Book]
attribute :latest_book, Book
def books=(books)
self.latest_book = books.sort_by { |book| book['createdate'] }.last
super books
end
end
Could you help me?
def books=(books) is defining a method called books= which takes a single argument books. Yes, that's confusing. It should probably be def books=(value) or def books=(new_books).
And yes, the = is part of the method name. self.books = value is really syntax sugar for self.books=(value). Again, the method is books=.
super books is super(books). super calls the next inherited or included method of the same name; it's calling books= created by attribute :books, Array[Book]. This is a "method override" which allows you to add to the behavior of an existing method.
When books= is called it updates latest_books and then calls its the original method to set the books attribute.
gb = GetBooks.new
gb.books = [old_book, new_book]
p gb.latest_book # new_book
p gb.books # [old_book, new_book]
I am trying to assign session values to model object as below.
# models/product.rb
attr_accessor :selected_currency_id, :selected_currency_rate, :selected_currency_icon
def initialize(obj = {})
selected_currency_id = obj[:currency_id]
selected_currency_rate = obj[:currency_rate]
selected_currency_icon = obj[:currency]
end
but this works only when I initialize new Product object
selected_currency = (session[:currency].present? ? session : Currency.first.attributes)
Product.new(selected_currency)
While, i need to set these setter methods on each product object automatically even if was fetched from Database.(active record object) ie. Product.all or Product.first
Earlier i was manually assigning values to each product object after retrieving it from db on controller side.
#products.each do |product|
product.selected_currency_id = session[:currency_id]
product.selected_currency_rate = session[:currency_rate]
product.selected_currency_icon = session[:currency]
end
But then i need to do it on every method where product details need to be displayed. Please suggest a better alternative to set these setter methods automatically on activerecord objects.
I don't think you really want to do this on the model layer at all. One thing you definitely don't want to do is override the initializer on your model and change its signature and not call super.
Your model should only really know about its own currency. Displaying the price in another currency should be the concern of another object such as a decorator or a helper method.
For example a really naive implementation would be:
class ProductDecorator < SimpleDelegator
attr_accessor :selected_currency
def initialize(product, **options)
# Dynamically sets the ivars if a setter exists
options.each do |k,v|
self.send "#{k}=", v if self.respond_to? "#{k}="
end
super(product) # sets up delegation
end
def price_in_selected_currency
"#{ price * selected_currency.rate } #{selected_currency.icon}"
end
end
class Product
def self.decorate(**options)
self.map { |product| product.decorate(options) }
end
def decorate(**options)
ProductDecorator.new(self, options)
end
end
You would then decorate the model instances in your controller:
class ProductsController
before_action :set_selected_currency
def index
#products = Product.all
.decorate(selected_currency: #selected_currency)
end
def show
#product = Product.find(params[:id])
.decorate(selected_currency: #selected_currency)
end
private
def set_selected_currency
#selected_currency = Currency.find(params[:selected_currency_id])
end
end
But you don't need to reinvent the wheel, there are numerous implementations of the decorator pattern like Draper and dealing with currency localization is complex and you really want to look at using a library like the money gem to handle the complexity.
I am going through Sandi Metz's Practical Object Oriented Design. In Chapter 4, Creating Flexible Interfaces, I am presented with some UML diagrams to show the interactions and messages of two classes. For example, below is the following diagram from the book:
The description of this image is the following:
Therefore, this sequence diagram can be read as follows: Customer Moe sends the suitable_trips message to the Trip class, which is activated to process it and then, when finished, returns a response.
Would an implementation like the following be accurate?
class Customer
attr_reader :name, :on_date, :of_difficulty, :need_bike
def initialize(name, on_date, of_difficulty, need_bike)
#name = name
#on_date = on_date
#of_difficulty = of_difficulty
#need_bike = need_bike
end
end
class Trip
attr_reader :trip
def initialize(trip)
#trip = trip
end
def suitable_trips(on_date, of_difficulty, need_bike)
#gives list of suitable trips based on factors
end
end
moe = Customer.new("moe", "now", "easy", "yes")
trip = Trip.new('Grand Canyon')
trip.suitable_trips(moe.on_date, moe.of_difficulty, moe.need_bike)
Sometimes I think I get it but then I'll run into another UML diagram and then get confused by wording especially when it comes to receiver and sender. I just want to be sure I'm getting this right so I understand completely where methods are supposed to go and in which class.
Therefore, this sequence diagram can be read as follows: Customer Moe sends the suitable_trips message to the Trip class, which is activated to process it and then, when finished, returns a response.
Let’s implement it exactly as stated.
class Trip
class << self # class methods
def suitable_trips(on_date, of_difficulty, need_bike)
# gives list of suitable trips based on factors
[Trip.new(...), Trip.new(...), ...]
end
end
end
class Customer
attr_reader :name
def initialize(name)
#name = name
end
# this is a search function, specific for Moe
def best_fit(on_date, of_difficulty, need_bike)
trips = Trip.suitable_trips(on_date, of_difficulty, need_bike)
# get the best accordingly to some rules
# that are specific for Moe
trips.find { |trip| ... }
end
end
And use it like:
moe = Customer.new("Moe")
moe.best_fit("2019-06-01", :hard, false)
#⇒ Trip instance or `nil`
Sidenote: there are tons of great books on Ruby and my personal advice would be to pick up one of them instead. Sandi Metz is probably a great populist, but it makes sense to read tutorials from people who are actually good at coding.
I understand my question is a bit vague but I don't know how else to describe it. I've asked in numerous places and no one seems to understand why I want to do this. But please bear with me, and I'll explain why I want something like this.
I'm using Liquid Templates to allow users to make some dynamic pages on my site. And for those that don't know, Liquid uses a class of theirs called LiquidDrop to expose certain items to the user. Any method in the drop can be called by the Liquid template.
class PageDrop < Liquid::Drop
def initialize(page)
#page = page
end
def name
#page.name
end
def children
PagesDrop.new(#page.children)
end
end
class PagesDrop < Liquid::Drop
def initialize(pages)
#pages = pages
end
def group_by
GroupByDrop.new(#pages)
end
def all
#pages.all
end
def size
#pages.size
end
end
For example, I want to be able to do this:
#page_drop = PageDrop.new(#page)
#page_drop.children # to get an array of children
instead of
#page_drop.children.all
Why do I have a pages drop?
Because I want to be able to cleanly split up the methods I can do to an array of pages, and methods I can do to a single page. This allows me to group pages like so:
#page_drop.children.group_by.some_method_here_that_the_group_drop_contains
To make it simpler for my users, I don't want them to have to think about adding "all" or not to a drop instance to get the "default" object/s that it contains. To reiterate:
#pages_drop = PagesDrop.new(Page.all)
#pages_drop == #pages_drop.pages #I want this to be true, as well as
#pages_drop == #pages_drop.all
Where did I get this idea?
In Rails, a scope (association object) (#person.friends) seems to return the array when you do certain things to it: #person.friends.each, for person in #person.friends
This isn't really possible. When you write #instance you aren't really calling an instance as you describe, you're getting a reference to the object that #instance refers to.
The reason it seems to work with the collections for Rails' associations is that the the association objects are instances of Array that have had some of their methods overridden.
I would consider removing PagesDrop and using the group_by(&:method) syntax if you want a concise way to express groupings. If you do want to keep it then you can get some way towards what you want by implementing each and [] on PagesDrop and having them delegate to #pages. That will let you use #page_drop.children in for loops, for instance.
It looks like you want to implement has_many outside of rails. Will the following work?
class PageDrop < Liquid::Drop
attr_accessor :children
def initialize(page)
#page = page
#children = []
end
def name
#page.name
end
end
This allows you to do the following:
#page_drop = PageDrop.new(#page)
#page_drop.children.size # => 0
#page_drop.children # => []
This also gives you all the standard array functions (group_by, size, each, etc). If you want to add your own methods, create a class that inherits from Array and add your methods there.
class PageArray < Array
def my_method
self.each{|a| puts a}
end
end
class PageDrop < Liquid::Drop
attr_accessor :children
def initialize(page)
#page = page
#children = PageArray.new
end
[...]
end
#page_drop = PageDrop.new(#page)
#page_drop.children.size # => 0
#page_drop.children # => []
#page_drop.children.my_method # Prints all the children
Then any functions you don't define in PageArray fall through to the Ruby Array methods.