Ruby on Rails Override a method that expects a block - ruby-on-rails

With the new attribute marked_deleted in AssistantTeacher class,
We want all queries to work with the basic assumption of only selection where marked_deleted attribute is false.
This is easy (using squeel query language instead of standard AR query language)
class AssistantTeacher < ActiveRecord.Base
# Gives a default query which always includes the condition here
default_scope {where{marked_deleted == false }}
end
This means that queries like AssistantTeacher.all will actually be
AssistantTeacher.all.where{marked_deleted == false}
Works great.
Likewise AssistantTeacher.find_each() also works with the limitation.
likewise
AssistantTeacher.where{atcode == "MPL"}
also executes as
I
However then the tricky part:
We need to reverse the default_scope for admins etc in special cases:
class AssistantTeacher < ActiveRecord.Base
# Gives a default query which always includes the condition here
default_scope {where{marked_deleted == false }}
# If a query is unscoped then the default_scope limitation does not apply
# scoped is the default scope when there is no default_scope
unscoped { scoped}
end
This works fine for
def self.all_including_marked_deleted
return unscoped{all}
end
However: the question
I can't figure out how to do an unscoped for an admin version of find_each with a block
def self.find_each_including_marked_deleted &block
return unscoped{find_each(block)}
end
DOESNOT work. Nor do any other combination with block that I can think of.
Anyone any ideas what I can do to get my overriding method find_each_including_marked_deleted to pass its block to the unscoped call?

You just have a small syntax problem. If you add a & to the front of your block, you should be fine:
def self.find_each_including_marked_deleted &block
unscoped { find_each &block }
end
What's going on here? Inside the method body, the block variable is an instance of Proc (that's what the & is doing in front of the block param). You can verify this by checking block.class. find_each takes a block, not a proc, so you need the & to convert the proc back to a block.
Clear as mud? You can read more about it here.

Related

Is it possible to pass a nested property of a hash to function in ruby

I have this function in rails controller:
def validate_params(*props)
props.each do |prop|
unless params[prop].start_with?('abc')
# return error
end
end
end
im thinking if I have params[:name] and params[:bio] and I want to validate name & bio with this function (not every attribute I might want to validate), I will call it with validate_params(:name, :bio). But, for nested param it won't work like params[:user][:name]. Is there anything I can do to pass this nested property to my function or is there a completely different approach? Thanks
Rails Validations generally belong in the model. You should post some additional info about what you're trying to do. For example, if you wanted to run the validation in the controller because these validations should only run in a certain context (i.e., only when this resource is interacted with from this specific endpoint), use on: to define custom contexts.
If you don't want to do things the rails way (which you should, imo), then don't call params in the method body. i.e.
def validate_params(*args)
args.each do |arg|
unless arg.start_with?('abc')
# return error
end
end
end
and call with validate_params(params[:user], params[:user][:name]
but yeah... just do it the rails way, you'll thank yourself later.

How it works find_by_* in 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

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

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.

Are `return` and `break` useless inside a Ruby block when used as a callback?

In Rails, blocks can be used as callbacks, e.g.:
class User < ActiveRecord::Base
validates_presence_of :login, :email
before_create {|user| user.name = user.login.capitalize
if user.name.blank?}
end
When a block is used like this, is there any use for break and return? I'm asking because normally in a block, break will break out of the loop, and return will return from the enclosing method. But in a callback context, I can't get my head round what that means.
The Ruby Programming Language suggests that return could cause a LocalJumpError but I haven't been able to reproduce this in a Rails callback.
Edit: with the following code I'd expect a LocalJumpError, but all the return does is stop the rest of the callback executing.
class User < ActiveRecord::Base
validates_presence_of :login, :email
before_create do |user|
return
user.name = user.login.capitalize
end
Actually it's kind of interesting...
When you use before_create in Rails 3, we take the block or lambda that you give us and convert it into a method. We then invoke the method with the current ActiveRecord object, for backwards compatibility with the old Rails approach.
As a result, the following is equivalent to your snippet:
class User < ActiveRecord::Base
validates_presence_of :login, :email
before_create do
self.name = login.capitalize if name.blank?
end
end
Because of this behavior, you can call return from the block, and it will behave the same as a return in a normal method (because it is a normal method).
In general, next in a block "returns" from the block.
The specific behavior of normal blocks is:
When you call next, you are skipping the rest of the block, and returning control to the method that invoked the block.
When you call break, you are skipping the rest of the block, and also immediately returning from the method that invoked the block.
You can see that behavior in normal iterators:
value = [1,2,3,4].each do |i|
next if i == 2
puts i
end
In this case, value will be [1,2,3,4], the normal return value of the each method, and the output will be:
1
3
4
In the case of break:
value = [1,2,3,4].each do |i|
break if i == 2
puts i
end
In this case, the value will be nil, since the break also immediately returned from the each method. You can force a return with a specific value by using break n, which will make value the same as n. The output in the above case will be:
1
The important thing is that next and break do not just apply to iterators, although their semantics are designed to behave like their equivalents in C in the case of iterators.
The operation of return within Ruby blocks depends whether the block was constructed with Proc.new or lambda.
I recommend you read the highest rated answer on this other Stack Overflow question: When to use lambda, when to use Proc.new?
In this case, the block has the properties of one created with Proc.new.
Calling return in the context of that kind of callback would only make sense if this also made sense (which it obviously doesn't):
class Foo
return "bar"
end

Resources