In my gem I have a class called Client that I want to operate like this:
client = Client.new
client.content_type('pages').content_type
That means I want to set a property and then expect to immediately get it back in the same chain. This is what I have so far:
class Client
attr_reader :content_type
def initialize(options = {})
#options = options
end
def content_type
#content_type
end
def content_type(type_id)
#content_type = type_id
self
end
end
Now when I try to run client.content_type('pages').content_type I get:
wrong number of arguments (given 0, expected 1) (ArgumentError)
from chaining.rb:16:in `<main>'
What am I doing wrong? How do I write this correctly?
The names of your methods are conflicting. The second method is overriding the first. Either use different names or consolidate to do both like:
class Client
attr_reader :content_type
def initialize(options = {})
#options = options
end
def content_type(type_id = nil)
if type_id
#content_type = type_id
self
else
#content_type
end
end
end
Btw, this code stinks because the returned values for this are of different type depending on how it's called. You shouldn't do it unless you have a good reason to.
Related
def self.grab
article = self.article_names
links = self.article_links
body = self.article_body
articles = {}
articles[:title] = article
articles[:url] = links
articles[:body] = body
art = Ello::Hello.new
art(articles)
end
When I run this with
class Ello::Hello
attr_accessor :url, :article, :body,
##all = []
def initialize(hash)
#article = hash["title"]
#body = hash["body"]
#url = hash["url"]
##all << self
end
def self.all
##all
end
end
I get wrong number of arguments error? I know that usually when it says wrong number it means that it's not exactly reading the argument that I put in. But I feel like I did put in an argument but I'm unsure of why it's not being read.
In such cases, you should always paste the complete error message, and indicate which line in your code is affected.
Anyway, I can see that your wrote art = Ello::Hello.new (0 arguments), but the initialize method for this class expects 1 argument.
I have a class that look like this (simplified)
class Timereg < ActiveRecord::Base
def initialize(hour_id)
super()
self.hour_id = hour_id
self.status = -2
self.slug = SecureRandom.uuid.to_s
end
end
When using it like this
Timereg.new(1)
all is good.
How can I use it with this
Timereg.create!
I can't figure out the syntax. I get:
ArgumentError: wrong number of arguments (2 for 1)
It is a bad idea to override create or initialize methods.
You should use callbacks. You can use after_initialize hook to assign the values.
You need to add the block parameter:
def initialize(hour_id, &block)
super(nil, &block)
..
end
This is what ActiveRecord's create! looks like:
# File activerecord/lib/active_record/persistence.rb, line 47
def create!(attributes = nil, &block)
if attributes.is_a?(Array)
attributes.collect { |attr| create!(attr, &block) }
else
object = new(attributes, &block)
object.save!
object
end
end
Because your .new does not take two parameters, the create! fails.
But as #kartikey-tanna said, it may not be a good idea to modify the initializer in the first place.
Consider something like:
def self.create_for_hour_id!(hour_id, attributes = nil)
timereg = new(attributes)
timereg.hour_id = hour_id
yield timereg if block_given?
timereg.save!
timereg
end
And use the callbacks to set up the slugs, status, etc:
before_validation :generate_slug
private
def generate_slug
self.slug ||= SecureRandom.uuid.to_s
end
I am building a DSL and have this module
module EDAApiBuilder
module Client
attr_accessor :api_client, :endpoint, :url
def api_client(api_name)
#apis ||= {}
raise ArgumentError.new('API name already exists.') if #apis.has_key?(api_name)
#api_client = api_name
#apis[#api_client] = {}
yield(self) if block_given?
end
def fetch_client(api_name)
#apis[api_name]
end
def endpoint(endpoint_name)
raise ArgumentError.new("Endpoint #{endpoint_name} already exists for #{#api_client} API client.") if fetch_client(#api_client).has_key?(endpoint_name)
#endpoint = endpoint_name
#apis[#api_client][#endpoint] = {}
yield(self) if block_given?
end
def url=(endpoint_url)
fetch_client(#api_client)[#endpoint]['url'] = endpoint_url
end
end
end
so that I have tests like
context 'errors' do
it 'raises an ArgumentError when trying to create an already existent API client' do
expect {
obj = MixinTester.new
obj.api_client('google')
obj.api_client('google')
}.to raise_error(ArgumentError,'API name already exists.')
end
it 'raises an ArgumentError when trying to create a repeated endpoint for the same API client' do
expect {
obj = MixinTester.new
obj.api_client('google') do |apic|
apic.endpoint('test1')
apic.endpoint('test1')
end
}.to raise_error(ArgumentError,"Endpoint test1 already exists for google API client.")
end
end
I would rather have #api_clientwritten as an assignment block
def api_client=(api_name)
so that I could write
obj = MixinTester.new
obj.api_client = 'google' do |apic| # <=== Notice the difference here
apic.endpoint('test1')
apic.endpoint('test1')
end
because I think this notation (with assignment) is more meaningful. But then, when I run my tests this way I just get an error saying that the keyworkd_do is unexpected in this case.
It seems to me that the definition of an assignment block is syntactic sugar which won't contemplate blocks.
Is this correct? Does anyone have some information about this?
By the way: MixinTester is just a class for testing, defined in my spec/spec_helper.rb as
class MixinTester
include EDAApiBuilder::Client
end
SyntaxError
It seems to me that the definition of an assignment [method] is syntactic
sugar which won't contemplate blocks.
It seems you're right. It looks like no method with = can accept a block, even with the normal method call and no syntactic sugar :
class MixinTester
def name=(name,&block)
end
def set_name(name, &block)
end
end
obj = MixinTester.new
obj.set_name('test') do |x|
puts x
end
obj.name=('test') do |x| # <- syntax error, unexpected keyword_do, expecting end-of-input
puts x
end
Alternative
Hash parameter
An alternative could be written with a Hash :
class MixinTester
def api(params, &block)
block.call(params)
end
end
obj = MixinTester.new
obj.api client: 'google' do |apic|
puts apic
end
#=> {:client=>"google"}
You could adjust the method name and hash parameters to taste.
Parameter with block
If the block belongs to the method parameter, and not the setter method, the syntax is accepted :
def google(&block)
puts "Instantiate Google API"
block.call("custom apic object")
end
class MixinTester
attr_writer :api_client
end
obj = MixinTester.new
obj.api_client = google do |apic|
puts apic
end
# =>
# Instantiate Google API
# custom apic object
It looks weird, but it's pretty close to what you wanted to achieve.
I want to create a special settings class Settings. The class should be able to handle cases when a user types something like Settings.new.method_1.method_2.method_3 and it's translated to something like:
result = nil
if ConfigurationSettings['method_1'].present?
result = ConfigurationSettings['method_1']
if result['method_2'].present?
result = result['method_2']
...
end
return result
Of course, I'll make it more flexible later so it can have more than 2/3 "methods".
I guess this is the issue you are facing:
class Settings
def abc
puts "abc"
end
def xyz
puts "xyz"
end
end
s = Settings.new
s.abc
#abc
# => nil
s.xyz
#xyz
# => nil
s.abc.xyz
#abc
#NoMethodError: undefined method `xyz' for nil:NilClass
The issue here is s.abc is returning nil and xyz is called over nil. What you are trying to achieve is called Method Chaining. Now, xyz needs an Settings object. Simplest thing to do here is:
class Settings2
def abc
puts "abc"
self
end
def xyz
puts "xyz"
self
end
end
s2 = Settings2.new
s2.abc.xyz
#abc
#xyz
method_missing is available for your use and can be used to help you solve this problem. Coupling this with method chaining and you're good to go. For example:
class Settings
def method_missing(meth)
puts "Missing #{meth}"
self
end
def test
puts "Test"
self
end
end
a = Settings.new
a.test
a.test.b
a.b.test
The trouble with the other answers is all the methods return "self" so if you want to access a nested value...
final_value = Settings.new.method_1.method_2.method_3
You're just going to get the whole settings hash instead.
Try this instead...
class Settings
class SubSettings
def initialize(sub_setting)
#sub_setting = sub_setting
end
def method_missing(method, *arguments, &block)
if #sub_setting[method].is_a?(Hash)
SubSettings.new #sub_setting[method]
else
#sub_setting[method]
end
end
def answer
#sub_setting
end
end
def initialize
#settings = ConfigurationSettings
end
def method_missing(method, *arguments, &block)
SubSettings.new #settings[method]
end
end
ConfigurationSettings = {level1a: {level2a: {level3a: "hello", level3b: "goodbye"}, level2b: {level3b: "howdy"}}}
result = Settings.new.level1a.level2a.level3b
p result
=> "goodbye"
What this does is take the initial method and takes the associated sub-hash of the ConfigurationSettings hash and stored it into a new object of class SubSettings. It applies the next method and if the result is another sub-hash it iterates to create another SubSettings, etc. It only returns the actual result when it no longer sees hashes.
i want to make a method inside a module (for grouping reason) that can be called as a module.method, something like this:
helpers do
module UserSession
def logged_in?
not session[:email].nil?
end
def logout!
session[:email] = nil
end
end
end
but when i try to call it using UserSession.logged_in? it said that logged_in is not UserSession's method
undefined method `logged_in?' for UserSession:Module
when i move the method as UserSession's method:
helpers do
module UserSession
def self.logged_in?
not session[:email].nil? # error
end
def self.logout!
session[:email] = nil
end
end
end
it gives an error, that i could not access the session variable
undefined local variable or method `session' for UserSession:Module
what is the best solution for this problem?
You can use a different convention for the helpers method.
module UserSession
def logged_in?
not session[:email].nil?
end
def logout!
session[:email] = nil
end
end
helpers UserSession
get '/foo' do
if logged_in?
'Hello you!'
else
'Do I know you?'
end
end
The module definition can of course be in another (required) file.
Behind the scenes, helpers <Module> is doing an include, but not simply into the Sinatra application sub-class you are using for your app. The include needs to be made compatible with how get, post etc work their magic, and helpers does that for you.
nevermind, i found the answer, i have tried define_method('UserSession.logged_in?') also, but no luck
last thing i've tried is:
# outside helpers
class UserSession
##session = nil
def initialize session
##session ||= session
end
def self.check
throw('must be initialized first') if ##session.nil?
end
def self.logged_in?
self.check
not ##session[:email].nil?
end
def self.logout
self.check
##session.delete :email
end
end
but something must be called first
before // do
UserSession.new session
end
then it can be used as desired:
get '/' do
if UserSession.logged_in?
# do something here
end
end