Missing keywords error on initializing a constructor - ruby-on-rails

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'

Related

Fetch optional parameters not working in 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)

Rails merge (and manual assignment) assigning nil

I am trying to create a model in a controller using strong params in Rails 5.1 (some things changed from previous for strong_params). However, when I inspect the params, the merged ones are NOT present and I am getting an ForbiddenAttributesError tracing back to the Model.new line below. The only thing in the Model is verify presence for all the attributes.
class ModelController < ApplicationController
before_action :application_controller_action
def create
#model = Model.new(strong_params)
if #model.valid?
result = #model.save
else
render html: 'MODEL NOT VALID'
end
render html: 'DONE'
end
private
def strong_params
# attr_1 and attr_2 are set in the application controller and are available here.
params.require(:model).permit(:name, :attribute_1, :attribute_2).merge(attribute_1: #attr_1, attribute_2: #attr_2)
# Inserting the following two lines causes a ForbiddenAttributesError
puts params.inspect # DOES NOT INCLUDE #attr_1 and/or #attr_2
return params
end
I may be doing something wrong though because I've even tried putting the strong params into a model with the attributes (which I can inspect just before) and it still fails because the validation for attr_1 and attr_2 fail in the Model.
def create
puts #user.inspect (not nil)
#model = Model.new(name: strong_params[:name], attribute_1: #attr_1, attribute_2: #attr_2)
UPDATE:
OK, I'm getting some weird errors from my troubleshooting. It seems the merge is not working correctly, though I'm sure it was at one point.
The first thing I checked was #attr_1 and #attr_2, they are definitely getting set.
For troubleshooting purposes, I've reduced the application before_action to this:
def application_before_action
#attr_1 = Model.first
#attr_2 = Model.last
With the code above, inspecting the params object and then returning it after the require().permit(), I am getting a ForbiddenAttributesError (no indication of what). If I remove those lines, I get a missing attributes error from the model indicating that #attr_1 and #attr_2 are missing.
UPDATE 2
Changed the title of the question, because I probably got confused during troubleshooting. I think the issue is just that the merge is assigning nil... but strangely so is the manual assignment suggested by (myself originally) and another answer here. The attributes keys are there, but they're getting assigned nil. Also, noticed my example was using a single Model, when there are actually two Models, Model1 and Model2. I am assigning the values from Model1 to Model2.
Here is a better demonstration of the error:
def create
puts '0:'
puts #model1.inspect
puts '1:'
puts strong_params.inspect
#model2 = Model2.new(strong_params) do |m|
m.user_id = #attr_1
m.account_number = #attr_2
end
puts '3:'
puts #model2.inspect
if #model2.valid?
result = #model2.save
render html: 'SUCCESS' and return
else
render html: #model2.errors.full_messages and return
end
end
Outputs in console:
0:
#<Model1 id: 29, attribute_1: 'test_value_1', attribute_2: 'test_value_2', created_at: "2018-08-15 03:55:08", updated_at: "2018-08-15 04:05:01">
1:
<ActionController::Parameters {"name"=>"test_name", "attribute_1"=>nil, "attribute_2"=>nil} permitted: true>
3:
#<Model2 id: nil, name: 'test_name', attribute_1: nil, attribute_2: nil, created_at: nil, updated_at: nil>
Obviously the nil id and timestamps are because the model has not been saved yet.
The html model2.errors.full_messages are: ["attribute_1 can't be blank", "attribute_2 can't be blank"]
SOLUTION
Coming from a pure ruby environment previously, I was mistaken about ActiveRecord default accessors for models. Removing the accessors seems to have resolved the problem.
Instead of mucking about with the params hash you can just assign the odd values one by one:
class ModelController < ApplicationController
before_action :application_controller_action
def create
#model = Model.new(strong_params) do |m|
m.attribute_1 = #attr_1
m.attribute_2 = #attr_2
end
if #model.valid?
result = #model.save
else
render html: 'MODEL NOT VALID'
end
# don't do this it will just give a double render error
render html: 'DONE'
end
private
private
def strong_params
params.require(:model).permit(:name, :attribute_1, :attribute_2)
end
end
In general this is a much more readable way to merge params with values from the session for example.
The reason your strong parameters method does not work is its just plain broken in every possible way. The main point is that you're not returning the whitelisted and merged params hash. You're returning the whole shebang.
You also seem under the faulty impression that .require, .permit and .merge alter the orginal hash - they don't - they return a new hash (well actually an ActionContoller::Parameters instance to be specific).
def strong_params
# attr_1 and attr_2 are set in the application controller and are available here.
permitted = params.require(:model).permit(:name, :attribute_1, :attribute_2)
.merge(attribute_1: #attr_1, attribute_2: #attr_2)
puts permitted.inspect
permitted # return is implicit
end
Or just:
def strong_params
# attr_1 and attr_2 are set in the application controller and are available here.
params.require(:model).permit(:name, :attribute_1, :attribute_2)
.merge(attribute_1: #attr_1, attribute_2: #attr_2)
end
You could convert to hash before merge
params.require(:model).permit(:name).to_h.merge(attribute_1: #attr_1, attribute_2: #attr_2)
You would have to be very sure that you are assigning non-user input though otherwise you are negating the purpose of strong parameters.

ruby: mass initializing instance variables

Is there an easy way to bulk assign instance variables
def initialize(title: nil, label_left: nil, label_right: nil, color_set: nil)
#title = title
#label_left = label_left
#label_right = label_right
#color_set = color_set
end
Can I loop/iterate through the initialize arguments and assign automatically?
If you want specific variables, then not really. Using reflection here, even if it was available, would be trouble.
What you've got here is the most trivial case. Often you'll see code that looks more like this:
def initialize(title: nil, label: nil, label_width: nil, color_set: nil)
#title = title.to_s
#label = label_left.to_s
#label_width = label_width.to_i
#color_set = MyRGBConverter.to_rgb(color_set)
end
The initializer is a place where you can do any necessary conversion and validation. If some of those arguments are required to be certain values you'll have tests for that, raise an exception on an error and so forth. The repetitive code you have here in the minimal case often gets expanded and augmented on so there's no general purpose solution possible.
That leads to code like this:
def initialize(title: nil, label: nil, label_width: nil, color_set: nil)
#title = title.to_s
unless (#title.match(/\S/))
raise "Title not specified"
end
#label = label_left.to_s
unless (#label.match(/\A\w+\z/))
raise "Invalid label #{#label.inspect}"
end
#label_width = label_width.to_i
if (#label_width <= 0)
raise "Label width must be > 0, #{#label_width} specified."
end
#color_set = MyRGBConverter.to_rgb(color_set)
end
If you really don't care about which arguments you're taking in then you can do this in Ruby 2.3 with the new keyword-arguments specifier **:
def initialize(**options)
options.each do |key, value|
instance_variable_set("##{key}", value)
end
end
Note that's potentially dangerous if those values are user supplied, so you might want to white-list them somehow:
VALID_OPTIONS = [ :title, :label_left, :label_right, :color_set ]
def initialize(**options)
options.each do |key, value|
raise "Unknown option #{key.inspect}" unless (VALID_OPTIONS.include?(key))
instance_variable_set("##{key}", value)
end
end
Where you're seeing a lot of these arguments being passed in on a regular basis you might want to evaluate if creating some kind of struct-like object and passing that through might be a better plan. It depends on how your code works.
#tadman provided already an excellent answer to this, but here is one more: If you are willing to dispense with named parameters, you could do it like this:
def initialize(*args)
#title, #label_left, #label_right, #color_set, *nada = args
fail "Too many arguments" unless nada.empty?
end
[UPDATE: Fixed the code, according to the comment given by #sawa].

Interpolate Method Definition

This method does not have a description on the APIdock. I know instance_exec in Ruby is similar to the this binding mechanism in JavaScript.
def interpolate(sql, record = nil)
if sql.respond_to?(:to_proc)
owner.instance_exec(record, &sql)
else
sql
end
end
Could someone briefly describe it?
First of all, the check for respond_to?(:to_proc) is necessary to make sure sql might be converted to lambda (by ampersand & to be passed to instance_exec. To simplify things, one might treat sql here as being a lambda already:
def interpolate(sql, record = nil) # assume sql is lambda
owner.instance_exec(record, &sql)
end
As by documentation on instance_exec:
Executes the given block within the context of the receiver...
That said, lambda will be executed as it was the ordinal code, placed somewhere inside instance method of the receiver.
class Owner
def initialize
#records = [:zero, :one, :two]
end
end
record_by_index = ->(idx) { #records[idx] }
Owner.new.instance_exec 1, &record_by_index #⇒ :one
The code above is [more or less] an equivalent to:
class Owner
def initialize
#records = [:zero, :one, :two]
end
def record_by_index idx
#records[idx]
end
end
Owner.new.record_by_index(1) #⇒ :one
The actual parameters of call to instance_exec will be passed to the codeblock. In the context of Owner’s instance we have an access to instance variables, private methods, etc. Hope it helps.

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.

Resources