Rails 5 model Initialise wrong number of arguments? - ruby-on-rails

Hi there im trying to create a model for a runescape item. Using there api and httparty. Im having a number of issues. But this one is regarding the use of overriding the initialize method saying it has the wrong number of arguments.
class Item < ApplicationRecord
require 'json'
include HTTParty
base_uri 'http://services.runescape.com/m=itemdb_oldschool/'
attr_accessor :name, :description, :price, :icon_url
def initialize(name, description, price, icon_url)
super
self.name = name
self.description = description
self.price = price
self.icon_url = icon_url
end
def self.find(name)
response = get("/api/catalogue/detail.json?item=#{name}")
if response.success?
parsed = JSON.parse(response)
self.new(
parsed["item"]["name"],
parsed["item"]["description"],
parsed["item"]["current"]["price"],
parsed["item"]["icon_large"]
)
else
# this just raises the net/http response that was raised
raise response.response
end
end
end
So in rails console i run the following command to test it:
item_test = Item.find("227")
ArgumentError: wrong number of arguments (given 4, expected 0..1)
from /Users/jacksharville/.rvm/gems/ruby-2.3.1/gems/activerecord-5.0.1/lib/active_record/core.rb:312:in `initialize'
from /Users/jacksharville/Desktop/OSCRUDDY/app/models/item.rb:13:in `initialize'
from /Users/jacksharville/.rvm/gems/ruby-2.3.1/gems/activerecord-5.0.1/lib/active_record/inheritance.rb:65:in `new'
But the initialize takes 4 arguments. When reduce it to one then it says it requires 4. Which leaves me very confused.
Im not even sure that overriding the base initialise is the way forward to do something like this. So if you have a better idea please let me know i'm new to this.
In conclusion my question is why is my object not being created correctly? Secondly is this the right approach for creating the object?

The problem I see here is that you are trying to mixin HTTParty with an ActiveRecord subclass, when really you should have two separate classes. The Item class should be responsible for interfacing with your database. You should create another class, i.e. ItemResource with Httparty, which will have the responsibility of connecting to the Runescape resource and respond with a response. You should use this to grab the data from the resource, and create an Item record with that data. Single Responsibility Principle
One thing to keep in mind is that you should almost never be redefining initialize for any models that inherit from ApplicationRecord. Checkout the API
EDIT
This code should get you relatively close
#app/models/item.rb
class Item < ApplicationRecord; end
#app/models/item_resource.rb | app/resources/item_resource.rb or something similar
require 'json'
class ItemResource
include HTTParty
self.base_uri 'http://services.runescape.com/m=itemdb_oldschool/'
def find name
response = get("/api/catalogue/detail.json?item=#{name}")
if response.success?
parsed = JSON.parse(response)
Item.new(
name: parsed["item"]["name"],
description: parsed["item"]["description"],
price: parsed["item"]["current"]["price"],
icon_large: parsed["item"]["icon_large"]
)
else
raise response.response
end
end
end
#somewhere else
item = ItemResource.find 'Elysian Spirit Shield'
if item.save
#do stuff
else
#handle failure
end

Related

Rails Console Argument Error Creating a New object

I am new to Rails and Ruby development but I am trying to create an object called Currency which takes in two params and does some calculations on them. I am using attr_accessor to set up the params and I put the file inside the lib directory.
Whenever I run rails console and try to do c = Currency.new(100, "CAD") I get the following error:
ArgumentError: wrong number of arguments (given 2, expected 0)
from (irb):5:in `initialize'
from (irb):5:in `new'
from (irb):5
I did make sure to include the file in application.rb. Here is a skeleton of my class:
class Currency
class << self
attr_accessor :input_value, :currency_iso
USD_ISO = "USD"
USD_TO_DM = 2.8054
def converted_value
convert_to_dm
end
private
def convert_to_dm
#input_value / USD_TO_DM
end
end
end
I have looked all over and I am stumped on what this issue may be. I have tried with and without an initialize method and I have tried creating a more basic version.
The problem here is that you are defining the method as a class method. And you are not defining the initialize method with those two params. Let's check the code below:
class Currency
attr_accessor :input_value, :currency_iso
USD_ISO = "USD"
USD_TO_DM = 2.8054
def initialize(input_value, currency_iso)
#input_value = input_value
#currency_iso = currency_iso
end
def converted_value
convert_to_dm
end
private
def convert_to_dm
input_value / USD_TO_DM
end
end
Also, due to you have already defined the attr_accessor you don't need to use the # when calling those attributes.
I found this post. It can help you to understand better the difference between class method and instance method.

How can I share data with a concern in Rails 4.2?

I am attempting to use Rails Concerns (or even a bare Module mixin) to share methods across some of my models.
Given a simple model, I am storing some encoded data in one of the
fields:
class DataElement < ActiveRecord::Base
include EmbeddedData
ENCODED = %w(aliases)
end
I’ve then made a concern with the needed methods for managing the data:
module EmbeddedData
extend ActiveSupport::Concern
included do
after_find :decode_fields
before_save :encode_fields
#decoded = {}
end
def decoded(key, value = false)
#decoded[key][:value] if #decoded.has_key? key
end
def decode_fields
#decoded = {} if #decoded.nil?
ENCODED.each do |field|
if attributes[field]
#decoded[field] = {
value: JSON.parse(attributes[field]),
dirty: false
}
end
end
end
def encode_fields
ENCODED.each do |field|
if decoded[field] && decoded[field][:dirty]
attributes[field] = #decoded[field][:value].to_json
end
end
end
end
Given this setup, I get the error uninitialized constant EmbeddedData::ENCODED
If I change the reference to self::ENCODED in the Concern I get the error:
# is not a class/module
I've even tried making a method on the concern register_fields that I can then call from the model, but the model just throws an unknown method error.
Running out of ideas here and looking for help.
So it turns out the way to access the class constant is:
self.class::ENCODED

How to obscure the id in a url (ruby on rails) [duplicate]

This question already has answers here:
How do I obfuscate the ids of my records in rails?
(6 answers)
Closed 7 years ago.
I have a web app made with Ruby On Rails. For now when I want to display an object I have to access the following page: http://mywebapp.com/object/1234 with 1234 the id of the object.
I would like to encode that object id and have the following result: http://mywebapp.com/object/5k (it is just an example).
How can it be done?
Many thanks,
Martin
All these converting methods are reversible, so IMHO if your object has some name or title or whatever, then the best way is adding a slug.
In such case add a new attribute :slug to your object, let automatically generate it's value from object name (or something else) on the model:
class MyObject
validates_format_of :slug, :with => /\A[a-z\-0-9]*\Z/
before_validation :generate_slug, :on => :create
def generate_slug
if self.slug.blank?
slug = self.name.mb_chars.downcase.normalize(:kd).to_s.gsub(/-/, " ").squeeze(" ")
slug = slug.gsub(/\s/, "-").gsub(/[^a-z\-0-9]/, "")
current = 1
self.slug = slug
while true
conflicts = MyObject.where("slug = ?", self.slug).count
if conflicts != 0
self.slug = "#{slug}-#{current}"
current += 1
else
break
end
end
end
end
end
then the URL can be http://mywebapp.com/object/my_object_slug, because in action you find the object via this slug:
class MyObjectController
def some_action
my_object = MyObject.find_by_slug(params[:slug])
...
end
end
Don't forget modify routes.rb:
match "object/:slug", :to => "my_objects#some_action"
You could probably do this with Base64 encoding (although if you're really trying to keep the internal id secret someone could probably guess you're using Base64 encoding and easily determine the id).
Your controller would need to look a bit like this
class ThingsController < ApplicationController
require 'base64'
def show
#thing = Thing.find Base64.urlsafe_decode64(params[:id])
end
def edit
#thing = Thing.find Base64.urlsafe_decode64(params[:id])
end
#These are just a couple of very simple example actions
end
Now actually encoding your URLs is going to be a little bit trickier - I'll look into it as it seems like an interesting problem (but I'm not making any promises).
Edit -
A bit of reading reveals that ActionView uses the to_param method in url_for to get the id of an object. We can override this in the model itself to encode the id like so
class Thing < ActiveRecord::Base
def to_param
Base64.urlsafe_encode64 self.id.to_s
end
end
Everything I've written here is conjectural. I haven't done this before or tested the code so I can't give any guarantee as to whether it will work or whether it will introduce unforeseen problems. I'd be very interested to hear how you go.

Legacy table with column named "class" in Rails

I've got a legacy table that my rails application shares with another application. It has a column called "class". The first time I reference any attribute in that model, I get an error. Subsequent references to attributes work. Is there a good workaround for this, or should I just go modify the other application that uses this table (ugh)?
>> Member::Ssg.find(:first)
=> #<Member::Ssg ssg_key: #<BigDecimal:10b169688,'0.253E3',4(8)>, org_id: 2, academic_year: 2006, class: true, next_due_date: "2011-06-01", submitted_date: "2006-02-13", notes: nil, owner_id: "1">
>> Member::Ssg.find(:first).notes
NoMethodError: undefined method `generated_methods' for true:TrueClass
from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.8/lib/active_record/attribute_methods.rb:247:in `method_missing'
from (irb):2
>> Member::Ssg.find(:first).notes
=> nil
SOLUTION:
I went with a combination of the Bellmyer solution and adding the code below to my model
class << self
def instance_method_already_implemented?(method_name)
return true if method_name == 'class'
super
end
end
NOTE: Please see the updated solution at the end of this answer. Leaving the original outdated solution for historic reasons.
This has come up often enough (legacy column names interfering with ruby/rails) that I might just make a plugin out of this. Here's how you can fix it right away, though. Create this file in your app:
# lib/bellmyer/create_alias.rb
module Bellmyer
module CreateAlias
def self.included(base)
base.extend CreateAliasMethods
end
module CreateAliasMethods
def create_alias old_name, new_name
define_method new_name.to_s do
self.read_attribute old_name.to_s
end
define_method new_name.to_s + "=" do |value|
self.write_attribute old_name.to_s, value
end
end
end
end
end
And now, in your model:
class Member < ActiveRecord::Base
include Bellmyer::CreateAlias
create_alias 'class', 'class_name'
end
The first parameter to create_alias is the old method name, and the second parameter is the new name you want to call it, that won't interfere with rails. It basically uses the read_attribute and write_attribute methods to interact with the column instead of the ruby methods that get defined by ActiveRecord. Just be sure to use the new name for the field everywhere, like so:
member.class_name = 'helper'
This works with ruby 1.8, but I haven't tested with ruby 1.9 yet. I hope this helps!
UPDATE: I've found a better solution that works in Rails 3, the safe_attributes gem. I've written a blog post explaining how to use it, with example code snippets, and a full sample app you can download from github and play around with. Here's the link:
Legacy Database Column Names in Rails 3
The following works in Rails 6.0.2.2
class ReasonCode < ApplicationRecord
class << self
def instance_method_already_implemented?(method_name)
return true if method_name == 'class'
super
end
end
def as_json(options={})
add_class = attributes.keys.include?('class')
if add_class
if options[:only]
add_class = Array(options[:only]).map(&:to_s).include?('class')
elsif Array(options[:except])
add_class = Array(options[:except]).map(&:to_s).exclude?('class')
end
end
options[:except] = Array(options[:except])
options[:except].push('class')
json = super(options)
json['class'] = attributes['class'] if add_class
json
end
end
Adapted from this answer https://www.ruby-forum.com/t/activerecord-column-with-reserved-name-class/125705/2. The as_json method was added because rendering the record as json gave a SystemStackError (stack level too deep). I followed the serialization code in the Rails repo to only render the class attribute if specified in the as_json options.

ActiveModel based class does not create the same results as an ActiveRecord equivilent

I am developing a Rails 3 app in a largely tabless capacity. I am using savon_model and ActiveModel to generate similar behaviour to ActiveRecord equivalents. Below is my code:
class TestClass
include Savon::Model
include ActiveModel::Validations
# Configuration
endpoint "http://localhost:8080/app/TestService"
namespace "http://wsns.test.com/"
actions :getObjectById, :getAllObjects
attr_accessor :id, :name
def initialize(hash)
#id = hash[:id]
#name = hash[:name]
end
client do
http.headers["Pragma"] = "no-cache"
end
def self.all
h = getAllObjects(nil).to_array
return convert_array_hash_to_obj(h, :get_all_objects_response)
end
def self.find(id)
h = getObjectById(:arg0 => id).to_hash
return convert_hash_to_obj(h, :get_object_by_id_response)
end
private
def self.convert_array_hash_to_obj(arrayhash, returnlabel)
results = Array.new
arrayhash.each do |hash|
results << convert_hash_to_obj(hash, returnlabel)
end
return results
end
def self.convert_hash_to_obj(hash, returnlabel)
return TestClass.new(hash[returnlabel][:return])
end
end
OK, so everything works as expected; values are pulled from the web service and onto the page. Unfortunately, when I look at the html produced at the client side there are some issues. The Show links are along the following lines:
/testclasses/%23%3CTestClass:0xa814cb4%3E
instead of...
/testclasses/1
So, I did a print of the object (hash?) to the console to compare the outputs.
[#<System:0xa814cb4 #id="1", #name="CIS">]
instead of what I believe it should be...
[#<System id="1", name="CIS">]
I have three questions:
1: What is the hex suffix on my class name when it is printed out
2: How can I modify my class to match the desired output when printed to the console?
3: Why are the frontend links (Show, Edit, Delete) broken and is there an easy fix?
Thanks so much for your time and apologies for rubbish code / stupid questions. This is my first Ruby or Rails app!
Gareth
The hex suffix is the object id of your instance of System
You can manipulate the output on the console by implementing an inspect instance method
The Rails url helpers use the to_param instance method to build these links. You should implement this if you are going to use your class as an ActiveRecord substitute.
Generally speaking, if you want to use all the Rails goodies with an own implementation of a model class, you should use ActiveModel:Lint::Test to verify which parts of the ActiveModel APIs are working as expected.
More information can be found here: http://api.rubyonrails.org/classes/ActiveModel/Lint/Tests.html

Resources