How it works find_by_* in rails - ruby-on-rails

May be its weird for some people about the question. By looking at the syntax its identifiable as class method.
Model.find_by_*
So if its class method it should be defined either in model we created or in
ActiveRecord::Base
So my question is how rails manages to add these methods and makes us available.
Examples like
Model.find_by_id
Model.find_by_name
Model.find_by_status
and etc.

You need to look at ActiveRecord::FinderMethods. Here you can find more details.
Internally, it fires a WHERE query based on attributes present in find_by_attributes. It returns the first matching object.
def find_by_attributes(match, attributes, *args)
conditions = Hash[attributes.map {|a| [a, args[attributes.index(a)]]}]
result = where(conditions).send(match.finder)
if match.bang? && result.nil?
raise RecordNotFound, "Couldn't find #{#klass.name} with #{conditions.to_a.collect {|p| p.join(' = ')}.join(', ')}"
else
yield(result) if block_given?
result
end
end
There is also find_all_by_attributes that returns all matching records.

Rails are using ruby metaprogramming method_missing for that. The method find_by_name is not in a model, instead of this rails are taking name as first argument and it calls it like find_by(name: ?) which is calling where(name: ?).take

Related

Ruby 2 Keyword Arguments and ActionController::Parameters

I have a rails 4 application that is running on ruby 2.1. I have a User model that looks something like
class User < ActiveModel::Base
def self.search(query: false, active: true, **extra)
# ...
end
end
As you can see in the search method I am attempting to use the new keyword arguments feature of ruby 2.
The problem is that when I call this code from in my controller all values get dumped into query.
params
{"action"=>"search", "controller"=>"users", query: "foobar" }
Please note that this is a ActionController::Parameters object and not a hash as it looks
UsersController
def search
#users = User.search(params)
end
I feel that this is because params is a ActionController::Parameters object and not a hash. However even calling to_h on params when passing it in dumps everything into query instead of the expected behavior. I think this is because the keys are now strings instead of symbols.
I know that I could build a new hash w/ symbols as the keys but this seems to be more trouble than it's worth. Ideas? Suggestions?
Keywords arguments must be passed as hash with symbols, not strings:
class Something
def initialize(one: nil)
end
end
irb(main):019:0> Something.new("one" => 1)
ArgumentError: wrong number of arguments (1 for 0)
ActionController::Parameters inherits from ActiveSupport::HashWithIndifferentAccess which defaults to string keys:
a = HashWithIndifferentAccess.new(one: 1)
=> {"one"=>1}
To make it symbols you can call symbolize_keys method. In your case: User.search(params.symbolize_keys)
I agree with Morgoth, however, with rails ~5 you will get a Deprecation Warning because ActionController::Parameters no longer inherits from hash. So instead you can do:
params.to_unsafe_hash.symbolize_keys
or if you have nested params as is often the case when building api endpoints:
params.to_unsafe_hash.deep_symbolize_keys
You might add a method to ApplicationController that looks something like this:
def unsafe_keyworded_params
#_unsafe_keyworded_params ||= params.to_unsafe_hash.deep_symbolized_keys
end
You most likely do need them to be symbols. Try this:
def search
#users = User.search(params.inject({}){|para,(k,v)| para[k.to_sym] = v; para}
end
I know it's not the ideal solution, but it is a one liner.
In this particular instance I think you're better off passing the params object and treating it as such rather than trying to be clever with the new functionality in Ruby 2.
For one thing, reading this is a lot clearer about where the variables are coming from and why they might be missing/incorrect/whatever:
def search(params)
raise ArgumentError, 'Required arguments are missing' unless params[:query].present?
# ... do stuff ...
end
What you're trying to do (in my opinion) only clouds the issue and confuses things when trying to debug problems:
def self.search(query: false, active: true, **extra)
# ...
end
# Method explicitly asks for particular arguments, but then you call it like this:
User.search(params)
Personally, I think that code is a bit smelly.
However ... personal opinion aside, how I would fix it would be to monkey-patch the ActionController::Parameters class and add a #to_h method which structured the data as you need it to pass to a method like this.
Using to_unsafe_hash is unsafe because it includes params that are not permitted. (See ActionController::Parameters#permit) A better approach is to use to_hash:
params.to_hash.symbolize_keys
or if you have nested params:
params.to_hash.deep_symbolize_keys
Reference: https://api.rubyonrails.org/classes/ActionController/Parameters.html#method-i-to_hash

How to transform a string into a variable/field?

I'm new to Ruby and I would like to find out what the best way of doing things is.
Assume the following scenario:
I have a text field where the user can input strings. Based on what the user inputs (after validation) I would like to access different fields of an instance variable.
Example: #zoo is an instance variable. The user inputs "monkey" and I would like to access #zoo.monkey. How can I do that in Ruby?
One idea that crossed my mind is to have a hash:
zoo_hash = { "monkey" => #zoo.monkey, ... }
but I was wondering if there is a better way to do this?
Thanks!
#zoo.attributes gives you a hash of the object attributes. So you can access them like
#zoo.attributes['monkey']
This will give nil if the attribute is not present. Calling a method which doesn't exist will throw NoMethodError
In your controller you could use the public_send (or even send) method like this:
def your_action
#zoo.public_send(params[:your_field])
end
Obviously this is no good, since someone can post somehing like delete_all as the method name, so you must sanitize the value you get from the form. As a simple example:
ALLOWED_METHODS = [:monkey, :tiger]
def your_action
raise unless ALLOWED_METHODS.include?(params[:your_field])
#zoo.public_send(params[:your_field])
end
There is much better way to do this - you should use Object#send or (even better, because it raises error if you try to call private or protected method) Object#public_send, like this:
message = 'monkey'
#zoo.public_send( message )
You could implement method_missing in your class and have it interrogate #zoo for a matching method. Documentation: http://ruby-doc.org/core-1.9.3/BasicObject.html#method-i-method_missing
require 'ostruct' # only necessary for my example
class ZooKeeper
def initialize
#zoo = OpenStruct.new(monkey: 'chimp')
end
def method_missing(method, *args)
if #zoo.respond_to?(method)
return #zoo.send(method)
else
super
end
end
end
keeper = ZooKeeper.new
keeper.monkey #=> "chimp"
keeper.lion #=> NoMethodError: undefined method `lion'

Rails: helper returns correct SQL result but ignores columns

I've got a helper in my ApplicationHelper file that works just fine:
def latest_issue
#issue = Issue.find(:all, :order => "id DESC", :limit => 1)
return #issue
end
After that, using #issue works just fine in any view, but considering that #issue has a column named message using #issue.message returns a No Method Error.
Any help would be fantastic! Cheers.
The issue instance variable is returning an array of objects not an instance. If you would like to select an attribute of an Issue object you need to return an instance of the object.
#issue = Issue.find(:last)
#issue.message
You may be trying to output all the message attributes of the Issue object, if that is the case you need to pass the #issue to an block
#issue.each do |issue|
issue.message
end
As Kyle C says, you're attempting to access the member message of an array of Issues, when you should be returning a single issue from your helper.
Assuming you're using Rails 3, A vastly improved version of your helper would be written this way:
def latest_issue
Issue.order(:id).last
end
A few notes on writing idomatic Ruby:
Avoid explicit return statements, and let the last statement be the return value of a method
Use the chainable methods like order(), where(), and limit() instead of passing arguments to find

What is the real benefit of scopes

I've looked at over 10 pages trying to find the benefit of a scope over any other ActiveRecord class method that returns an ActiveRecord::Relation.
In the following for example why are scopes better than the alternative below it which do the same thing:
#scope :pat1, lambda {{:conditions => ["name like ?", 'J%']}}
#scope :pat2, lambda {{:conditions => ["id > 5"]}}
def self.pat1
where("name like ?", 'J%')
end
def self.pat2
where("id > 5")
end
def patx
self.class.pat1.pat2.first
end
The documentation over and over again says that scopes are beneficial because they can be chained...
"All scope methods will return an ActiveRecord::Relation object which will allow for further methods (such as other scopes) to be called on it."
-guides.rubyonrails.org
"The main reason scopes are better than plain class methods is that they can be chained with other methods"
http://ruby.railstutorial.org
...but the alternative above can also be chained producing the same results.
Just trying to figure out if there's an emperor's new clothes thing going on here. Even from a syntactic standpoint there appears to be no benefit. Are they faster- some sources vaguely suggest that.
ActiveRecord scopes are really just syntax sugar wrapped in a best practice, as noted already.
In the 2.x days of Rails, when they were called "named_scope", they mattered a bit more. They allowed easy chaining of conditions for generating a query. With the improvements in Rails 3.x with Arel, it is simple to create functions for query relations, as you noted. Scopes just provide a simple and elegant solutions for chainable, predefined queries. Having all the scopes at the top of a model improves the readability and helps shows how the model is used.
When you write a scope, it is essentially doing the same thing. Here is what the Rails source looks like:
def scope(name, scope_options = {})
name = name.to_sym
valid_scope_name?(name)
extension = Module.new(&Proc.new) if block_given?
scope_proc = lambda do |*args|
options = scope_options.respond_to?(:call) ? unscoped { scope_options.call(*args) } : scope_options
options = scoped.apply_finder_options(options) if options.is_a?(Hash)
relation = scoped.merge(options)
extension ? relation.extending(extension) : relation
end
singleton_class.send(:redefine_method, name, &scope_proc)
end
The benefits to scopes in this case are that they are the idiomatic way of defining queries, in some cases fewer lines of code, and you can do extensions.
The example in the source looks like this:
scope :red, where(:color => 'red') do
def dom_id
'red_shirts'
end
end
Which allows you to call Model.red.dom_id.
Yes, they are syntactic short-cuts that basically represent the methods have you have found.
Why better?
The most immediate effect is that 2 lines code is way easier to read and maintain than 9 lines of code.
Rails always seeks a DRY approach and here the repeated def self.method end's are obscuring the actual code.
There are a few very interesting differences between scopes and class methods that return relations.
It is easier to deal with nil parameters for scopes with a simple param.present? check, for class methods you must explicitly return a non-nil relation if a param would cause a nil relation.
Scopes are more easily extensible than class methods. Simply pass a block (for instance to deal with pagination) to add the methods. Class methods can be extended but not as elegantly.
For the full rundown see this post from Plataformatec.

Scope vs Class Method in Rails 3

Based on the Rails 3 API, the difference between a scope and a class method is almost non-existent.
class Shipment < ActiveRecord::Base
def self.unshipped
where(:shipped => false)
end
end
is the same as
scope :unshipped, where(:shipped => false)
However, I'm finding that I'm sometimes getting different results using them.
While they both generate the same, correct SQL query, the scope doesn't always seem to return the correct values when called. It looks like this problem only occurs when its called the same way twice, albeit on a different shipment, in the method. The second time it's called, when using scope it returns the same thing it did the first time. Whereas if I use the class method it works correctly.
Is there some sort of query caching that occurs when using scope?
Edit:
order.line_items.unshipped
The line above is how the scope is being called. Orders have many line_items.
The generate_multiple_shipments method is being called twice because the test creates an order and generates the shipments to see how many there are. It then makes a change to the order and regenerates the shipments. However, group_by_ship_date returns the same results it did from the first iteration of the order.
def generate_multiple_shipments(order)
line_items_by_date = group_by_ship_date(order.line_items.unshipped)
line_items_by_date.keys.sort.map do |date|
shipment = clone_from_order(order)
shipment.ship_date = date
line_items_by_date[date].each { |line_item| shipment.line_items << line_item }
shipment
end
end
def group_by_ship_date(line_items)
hash = {}
line_items.each do |line_item|
hash[line_item.ship_date] ||= []
hash[line_item.ship_date] << line_item
end
hash
end
I think your invocation is incorrect. You should add so-called query method to execute the scope, such as all, first, last, i.e.:
order.line_items.unshipped.all
I've observed some inconsistencies, especially in rspec, that are avoided by adding the query method.
You didn't post your test code, so it's hard to say precisely, but my exeprience has been that after you modify associated records, you have to force a reload, as the query cache isn't always smart enough to detect a change. By passing true to the association, you can force the association to reload and the query to re-run:
order.line_items(true).unshipped.all
Assuming that you are referencing Rails 3.1, a scope can be affected by the default scope that may be defined on your model whereas a class method will not be.

Resources