Fetch optional parameters not working in rails - ruby-on-rails

I am sending optional paramters to a method but they are not received. Using binding.pry I have checked but the link parameter is not received but id parameter is received in the send_email method. It is always returned as nil. Please help find the problem where I am going wrong
class EmailsController < MyAccountController
def send_emails
#user = current_user
id = #user.id
HiringMailer.send_email(id, link: true).deliver_now
end
end
class HiringMailer < ApplicationMailer
def send_email(id, joining = false, options={})
link = options[:link]
binding.pry_remote
#user = User.find(id)
#joining_user = joining
to = (['abc#yah.com', 'adx#yah.com']).flatten
mail to: to, subject: "Joining Date"
end
end
Update 1
HiringMailer.send_email(id, link: true).deliver_now
def send_email(id, joining = false, , **options)
binding.pry_remote
end

The link: true argument is getting swallowed up by the joining variable.
Let me explain. Here's the method signature:
def send_email(id, joining = false, options={})
Now, if you call that method with: send_email(123, link: true), then we end up with:
id = 123
joining = {link: true}
options = {}
To prevent this unwanted affect, you need to explicitly pass all three variables: send_email(123, false, link: true).
...But wait, there's an even better way! Use keyword arguments instead. You can define the method like this:
def send_email(id, joining: false, **options)
And call it exactly like you were doing before:
send_email(123, link: true)
The only minor difference (which is frankly a clear improvement) is that you'll need to invoke the method slightly differently if you want to set joining = true:
# Before:
send_email(123, true, link: true)
# After:
send_email(123, joining: true, link: true)

Related

Minitest::Mock#expect without specific order?

Suppose I have a class:
class Foo
def process
MyModel.where(id: [1,3,5,7]).each do |my_model|
ExternalService.dispatch(my_modal.id)
end
end
end
I want to test it:
class FooTest < ActiveSupport::TestCase
def process_test
external_service_mock = MiniTest::Mock.new
[1,3,5,7].each do |id|
external_service_mock.expect(:call, true, id)
end
ExternalService.stub(:dispatch, events_mock) do
Foo.new.process
end
external_service_mock.verify
end
end
However, #expect enforces that the following calls are made in the same order as #expect was called. That's not good for me, because I have no confidence, in what order will the results be returned by the DB.
How can I solve this problem? Is there a way to expect calls without specific order?
Try Using a Set
require 'set'
xs = Set[3,5,7,9]
#cat = Minitest::Mock.new
#cat.expect :meow?, true, [xs]
#cat.meow? 7 # => ok...
#cat.expect :meow?, true, [xs]
#cat.meow? 4 # => boom!
Alternatively, a less specific option:
Given that the value returned by the mock isn't a function of the parameter value, perhaps you can just specify a class for the parameter when setting up your mock. Here's an example of a cat that expects meow? to be called four times with an arbitrary integer.
#cat = Minitest::Mock.new
4.times { #cat.expect(:meow?, true, [Integer]) }
# Yep, I can meow thrice.
#cat.meow? 3 # => true
# Mope, I can't meow a potato number of times.
#cat.meow? "potato" # => MockExpectationError

How to pass `self` when invoking a method

I have a method that sends a message when it triggers something. The signature is:
Sender.send(user, message_id, source)
The usage scenario is in the callbacks in active model. When some model is verified, we send a message through the template:
after_save if: 'verified_changed? && verified' do
Sender.send(user, :verified, self) # pass self as the template parameter
end
We pass self. There are some solutions to make invoking simple as below:
after_save if: 'verified_changed? && verified' do
Sender.send(user, :verified) # not need to pass self, guess it when invoking
end
Is there a method that passes self when a method is invoked?
More concrete examples are below:
#define a method, return the environment self
def guess
end
guess #=> main
class A
guess #=> A
def a
guess
end
end
A.new.a #=> #<A:0x000000026cd2f0>
I share my send method below (In class Sender):
def send(*users, id, source = self)
desc = source.class.to_s
text = "There is a new process in your #{desc}: #{id}"
users.reject{|user| user.nil?}.each do |user|
send_message(user, tag: 'Process', text: text)
send_mail(user, subject: text, text: text)
send_sms(user, text: text)
end
end

Missing keywords error on initializing a constructor

I have a Source model and ArticlesController. When user clicks scrape button, the control is passed to below mentioned ArticlesController#Scrape. The scrape then calls Source model where the sources are being initialised and the list of articles are returned in a form of hash to articles inside Scrape.
Source Model -
class Source
attr_accessor :source_name, :source_url, :source_type, :source_key, :tag_name
def self.all_instances
##array
end
# Default constructor
def initialize
end
def initialize(source_name:, source_url:, source_type:, source_key:, tag_name:)
#source_name = source_name
#source_url = source_url
#source_type = source_type
#source_key = source_key
#tag_name = tag_name
##array << self
end
def init
self.new('The Age',
'http://www.theage.com.au/rssheadlines/victoria/article/rss.xml',
'RSS',
'',
'Victoria News')
end
def import
init()
//returns hash of articles back
end
end
class ArticlesController < ApplicationController
def scrape
#get_Articles = Source.new
articles = #get_Articles.import
//stores articles in article model
//redirect to article path
end
end
I am getting ArgumentError in ArticlesController#scrape on #get_Articles = Source.new
Inside Source class the constructor def initialize(source_name:, source_url:, source_type:, source_key:, tag_name:) is being called. To rectify the issue I created a default constructor also, so that the parameterized constructor doesn't get called. However, I am not sure how to fix this problem. Could somebody please help?
I think you are doing it wrong with the def initialize method. You don't want parameterized constructor just removed it.
if you want this as well then you need to handle this for null values also.
Just creating a default constructor will not solve the issue because it will be override with other one.
You can try like this
def initialize(options ={})
#source_name = options[:source_name] if options[:source_name].present?
#handle and assign other keys and values similer to this
##array << self
end
now you can use this as
#get_Articles = Source.new
or
#get_Articles = Source.new(source_name: "abc")
First of all, the way you are trying to overload initialize method is incorrect. In ruby if you define the same method again in same class then the most latest interpretation will take preceding(based on when it get interpreted). So here initialize with parameter taking preceding.
There are many ways to overload a method based on parameters
Approach one: define method with default value assignment like below
def initialize(source_name = nil, source_url = nil, source_type = nil, source_key= nil, tag_name = nil)
end
In this approach the sequence of arguments does matter when invoking. i.e we can not invoke method with only tag_name the other values should also be passed as some value or nil
like Source.new nil, nil, nil, nil, 'tag_name_value'
Approach two: Using Hash as arguments (mentioned by #Prakash): This is the most popular and generic. In this we need to explicitly check for required argument name and need to assign default values to them if needed. This is mostly done by hash merging
def initialize(options ={})
options = {source_name: nil, source_url: nil, source_type: nil, source_key: nil, tag_name: nil}.merge(options)
end
# calling method
Source.new source_name: 'somevalue' #or so one
The disadvantage of this approach is there can be many keys in hash passed to method and to handle that you need to do extra check on input hash
Source.new source_name: 'somevalue', unexpected_key: 'unexpectedvalue'
Approach three
Ruby 2.0 has introduced the keyword arguments (also named argument in ruby 1.9) where you can provide a name to parameters like you were trying, the only thing you should keep in mind is to assign a default value to every parameter.
def initialize(source_name: nil, source_url: nil, source_type: nil, source_key: nil, tag_name: nil)
end
now you can invoke like
Source.new
Source.new source_url: 'somevalue'
Source.new source_name: 'somevalue'
Source.new source_type: 'somevalue', source_name: 'somevalue'
Source.new tag_name: 'somevalue'
# or any combination of arguments in any sequence
# but not the following, this give you error 'unknown keyword: unexpected_key'
Source.new tag_name: 'somevalue', unexpected_key: 'unexpectedvalue'

Rails Method Ignoring Default Param - WHY?

I am at a loss as to why this is happening. I have the following function:
def as_json(options = {})
json = {
:id => id,
# ... more unimportant code
}
unless options[:simple]
# ... more unimportant code
end
json
end
It works most of the time, but in one particular partial where I call this:
window.JSONdata = <%= #day.to_json.html_safe %>
I get the following error:
ActionView::Template::Error (You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.[]):
Pointing to the line "unless options[:simple]". As far as I can tell, the options hash is nil - thus the method is ignoring the default param assignment. WHY? I can fix this by changing the method to:
def as_json(options)
options ||= {}
json = {
:id => id,
# ... more unimportant code
}
unless options[:simple]
# ... more unimportant code
end
json
end
Does this make any sense to anyone!? Most appreciative for your help.
This is because you're using to_json, which has a default options of nil. to_json will eventually call as_json and pass the nil as options.
Here's where it happens on the Rails source code. First, to_json is defined with the default options of nil.
# https://github.com/rails/rails/blob/v3.0.7/activesupport/lib/active_support/core_ext/object/to_json.rb#L15
def to_json(options = nil)
ActiveSupport::JSON.encode(self, options)
end
Eventually it will arrive here.
# https://github.com/rails/rails/blob/v3.0.7/activesupport/lib/active_support/json/encoding.rb#L41
def encode(value, use_options = true)
check_for_circular_references(value) do
jsonified = use_options ? value.as_json(options_for(value)) : value.as_json
jsonified.encode_json(self)
end
end
As you see, as_json is called with value.as_json(options_for(value)) and options_for(value) will return the default value of to_json, which is nil.

How do I pass a var from one model's method to another?

Here is my one model..
CardSignup.rb
def credit_status_on_create
Organization.find(self.organization_id).update_credits
end
And here's my other model. As you can see what I wrote here is an incorrect way to pass the var
def update_credits
#organization = Organization.find(params[:id])
credit_count = #organization.card_signups.select { |c| c.credit_status == true}.count
end
If it can't be done by (params[:id]), what can it be done by?
Thanks!
Ideally the data accessible to the controller should be passed as parameter to model methods. So I advise you to see if it is possible to rewrite your code. But here are two possible solutions to your problem. I prefer the later approach as it is generic.
Approach 1: Declare a virtual attribute
class CardSignup
attr_accessor call_context
def call_context
#call_context || {}
end
end
In your controller code:
def create
cs = CardSignup.new(...)
cs.call_context = params
if cs.save
# success
else
# error
end
end
In your CardSignup model:
def credit_status_on_create
Organization.find(self.organization_id).update_credits(call_context)
end
Update the Organization model. Note the change to your count logic.
def update_credits
#organization = Organization.find(call_context[:id])
credit_count = #organization.card_signups.count(:conditions =>
{:credit_status => true})
end
Approach 2: Declare a thread local variable accessible to all models
Your controller code:
def create
Thread.local[:call_context] = params
cs = CardSignup.new(...)
if cs.save
# success
else
# error
end
end
Update the Organization model. Note the change to your count logic.
def update_credits
#organization = Organization.find((Thread.local[:call_context] ||{})[:id])
credit_count = #organization.card_signups.count(:conditions =>
{:credit_status => true})
end
Use an attr_accessor.
E.g.,
class << self
#myvar = "something for all instances of model"
attr_accessor :myvar
end
#myothervar = "something for initialized instances"
attr_accessor :myothervar
then you can access them as ModelName.myvar and ModelName.new.myvar respectively.
You don't say whether you're using Rails 2 or 3 but let's assume Rails 2 for this purpose (Rails 3 provides the a new DSL for constructing queries).
You could consider creating a named scope for in your Organization model as follows:
named_scope :update_credits,
lambda { |id| { :include => :card_signup, :conditions => [ "id = ? AND card_signups.credit_status = TRUE", id ] } }
And then use it as follows:
def credit_status_on_create
Organization.update_credits(self.organization_id)
end
Admittedly I don't quite understand the role of the counter in your logic but I'm sure you could craft that back into this suggestion if you adopt it.

Resources