Rails STI getting parent class instead of child class - ruby-on-rails

Here is my code:
def data
providers.map do |provider|
binding.pry
[
ERB::Util.h(provider.id),
link_to(raw(image_tag provider.avatar_url(:small), class: 'img-responsive'), admin_provider_path(provider))provider.enabled))
]
end
end
def providers
#providers ||= fetch_providers
end
def fetch_providers
providers = Provider.order("#{sort_column} #{sort_direction}")
providers = providers.page(page).per_page(per_page)
if params[:sSearch].present?
providers = providers.where("email like :search", search: "%#{params[:sSearch]}%")
end
providers
end
My Problem is, I always get nil from provider.avatar_url(:small). After I debug this provider using pry like this:
provider.class.name
=> "User"
I have defined the providers variable to taken from Provider not User
providers = Provider.order("#{sort_column} #{sort_direction}")
I can't get "Provider" class. Which of course carrierwave will search the file using user upload directory not provider upload directory. How do I get this? Thanks in advance
My STI class:
class Provider < User
has_many :products
after_create :assign_default_role
mount_uploader :avatar, AvatarUploader
def self.all
User.with_role(:provider)
end
private
def assign_default_role
self.add_role "provider"
end
end

This will happen if you are using STI without having a type column in your table. When loading record from database, ActiveRecord is using information in that column to determine the class which is to be used to instantiate the object.
To fix, you will need to add the missing column and populate with a Provider for every record representing a subclass.

Related

Rails create a Model for a controller that uses a .group method

I need to collect my customers with Spree::Order.group(:email) (since we have a guest checkout option).
The controller is as such:
class CustomersController < ApplicationController
def index
#customers = Spree::Order.where(state: "complete").group(:email).select(
:email,
'count(id) AS total_orders_count',
'sum(payment_total) AS amount',
'array_agg(number) AS order_numbers',
'array_agg(completed_at) AS completion_dates'
)
end
Can I create a customer.rb model for these #customers so I can move the logic there. I need to .joins(:line_items) and filter by date, so I figure it'd be cleaner.
P.S. As an asside...the 'sum(payment_total' AS amount always returns 0.0 because the payment_total is a BigDecimal object. What's the correct syntax for this request that would act like 'sum(payment_total.to_f)'....
Since you don't have a customers table, and you're trying to abstract the concept of a guest customer out of the orders table, creating a model is a reasonable approach.
Consider the following:
class Customer < ActiveRecord::Base
# Use the `orders` table for this "virtual" model
self.table_name = Spree::Order.table_name
# ensure no writes ever occur from this model as a precaution
def readonly?; true; end
scope :with_completed_orders, -> {
where(state: "complete")
.select(
:email,
'count(id) AS total_orders_count',
'sum(payment_total) AS amount',
'array_agg(number) AS order_numbers',
'array_agg(completed_at) AS completion_dates'
)
.group(:email)
.order(:email) # prevents errors - remove it and try Customer.first in the console.
}
scope :by_email, -> (email) { where(email: email) }
# Default scopes are generally a no-no, but a convenience here
def self.default_scope
# this could be any scope that groups by :email
self.with_completed_orders
end
# example of a potentially useful instance method
def orders
# returns a scope, which you can chain
Spree::Order.where(email: email)
end
end
The selects in the scope populate Customer instance attributes of the same name, as you probably know.
This will allow you to do things like:
customers = Customer.all
c = Customer.by_email('test#example.com')
c.amount # returns BigDecimal
c.order_numbers # returns Array
c.orders.first # example of chaining scope for #orders instance method
In the controller:
class CustomersController < ApplicationController
def index
#customers = Customer.all
end
end
Hopefully this gives you some ideas.

Convert an Object to hash then save it to user's column

Could not find nothing close to what I'm trying to do. I want to store an object into a user's column. That column is in the form of an array:
#postgres
def change
add_column :users, :interest, :string, array: true, default: '{}'
end
I have another model called FooBar setup for other use. Each user has unique information inside as I've added a user_id key.
Im trying to make more sense:
def interest
#user = User.find(current_user.id ) # I need the logged in user's id
#support = Support.find(params[:id]) # I need the post's id they are on
u = FooBar.new
u.user_id = #user
u.support_id = #support
u.save # This saves a new Foo object..this is what I want
#user.interest.push(FooBar.find(#user)) # This just stores the object name itself ;)
end
So when I call u1 = FooBar.find(1) I get value return in hash. I want when I say u1.interest I get the same. The reason is, I need to target those keys on the user ie: u1.interest[0].support_id
Is this possible? I've looked over my basic ruby docs and nothing works. Oh..if I passed FooBar.find(#user).inspect I get the hash but not the way I want it.
Im trying to do something similar to stripe. Look at their data key. That's a hash.
Edit for Rich' answer:
I have, literally, a model called UserInterestSent model and table:
class UserInterestSent < ActiveRecord::Base
belongs_to :user
belongs_to :support # you can call this post
end
class CreateUserInterestSents < ActiveRecord::Migration
def change
create_table :user_interest_sents do |t|
t.integer :user_id # user's unique id to associate with post (support)
t.integer :interest_sent, :default => 0 # this will manually set to 1
t.integer :support_id, :default => 0 # id of the post they're on
t.timestamps # I need the time it was sent/requested for each user
end
end
end
I call interest interest_already_sent:
supports_controller.rb:
def interest_already_sent
support = Support.find(params[:id])
u = UserInterestSent.new(
{
'interest_sent' => 1, # they can only send one per support (post)
'user_id' => current_user.id, # here I add the current user
'support_id' => support.id, # and the post id they're on
})
current_user.interest << u # somewhere this inserts twice with different timestamps
end
And the interest not interests, column:
class AddInterestToUsers < ActiveRecord::Migration
def change
add_column :users, :interest, :text
end
end
HStore
I remembered there's a PGSQL datatype called hStore:
This module implements the hstore data type for storing sets of
key/value pairs within a single PostgreSQL value. This can be useful
in various scenarios, such as rows with many attributes that are
rarely examined, or semi-structured data. Keys and values are simply
text strings.
Heroku supports it and I've seen it used on another live application I was observing.
It won't store your object in the same way as Stripe's data attribute (for that, you'll just need to use text and save the object itself), but you can store a series of key:value pairs (JSON).
I've never used it before, but I'd imagine you can send a JSON object to the column, and it will allow you to to use the attributes you need. There's a good tutorial here, and Rails documentation here:
# app/models/profile.rb
class Profile < ActiveRecord::Base
end
Profile.create(settings: { "color" => "blue", "resolution" => "800x600" })
profile = Profile.first
profile.settings # => {"color"=>"blue", "resolution"=>"800x600"}
profile.settings = {"color" => "yellow", "resolution" => "1280x1024"}
profile.save!
--
This means you should be able to just pass JSON objects to your hstore column:
#app/controllers/profiles_controller.rb
class ProfilesController < ApplicationController
def update
#profile = current_user.profile
#profile.update profile_params
end
private
def profile_params
params.require(:profile).permit(:x, :y, :z) #-> z = {"color": "blue", "weight": "heavy"}
end
end
As per your comments, it seems to me that you're trying to store "interest" in a User from another model.
My first interpretation was that you wanted to store a hash of information in your #user.interests column. Maybe you'd have {name: "interest", type: "sport"} or something.
From your comments, it seems like you're wanting to store associated objects/data in this column. If this is the case, the way you're doing it should be to use an ActiveRecord association.
If you don't know what this is, it's essentially a way to connect two or more models together through foreign keys in your DB. The way you set it up will determine what you can store & how...
#app/models/user.rb
class User < ActiveRecord::Base
has_and_belongs_to_many :interests,
class_name: "Support",
join_table: :users_supports,
foreign_key: :user_id,
association_foreign_key: :support_id
end
#app/models/support.rb
class Support < ActiveRecord::Base
has_and_belongs_to_many :users,
class_name: "Support",
join_table: :users_supports,
foreign_key: :support_id,
association_foreign_key: :user_id
end
#join table = users_supports (user_id, support_id)
by using this, you can populate the .interests or .users methods respectively:
#config/routes.rb
resources :supports do
post :interest #-> url.com/supports/:support_id/interest
end
#app/controllers/supports_controller.rb
class SupportsController < ApplicationController
def interest
#support = Support.find params[:support_id] # I need the post's id they are on
current_user.interests << #support
end
end
This will allow you to call #user.interests and bring back a collection of Support objects.
Okay, look.
What I suggested was an alternative to using interest column.
You seem to want to store a series of hashes for an associated model. This is exactly what many-to-many relationships are for.
The reason your data is being populated twice is because you're invoking it twice (u= is creating a record directly on the join model, and then you're inserting more data with <<).
I must add that in both instances, the correct behaviour is occurring; the join model is being populated, allowing you to call the associated objects.
What you're going for is something like this:
def interest_already_sent
support = Support.find params[:id]
current_user.interests << support
end
When using the method I recommended, get rid of the interest column.
You can call .interests through your join table.
When using the code above, it's telling Rails to insert the support object (IE support_id into the current_user (IE user_id) interests association (populated with the UserInterestSelf table).
This will basically then add a new record to this table with the user_id of current_user and the support_id of support.
EDIT
To store Hash into column, I suggest you to use "text" instead
def change
add_column :users, :interest, :text
end
and then set "serialize" to attribute
class User < ActiveRecord::Base
serialize :interest
end
once it's done, you can save hash object properly
def interest
#user = User.find(current_user.id ) # I need the logged in user's id
#support = Support.find(params[:id]) # I need the post's id they are on
u = FooBar.new
u.user_id = #user
u.support_id = #support
u.save # This saves a new Foo object..this is what I want
#user.interest = u.attributes # store hash
#user.save
end
To convert AR object to hash use object.attributes.
To store a custom hash in a model field you can use serialize or ActiveRecord::Store
You can also use to_json method as object.to_json
User.find(current_user.id ).to_json # gives a json string

Access attributes inside hashed column

I have the following class:
class Profile < ActiveRecord::Base
serialize :data
end
Profile has a single column data that holds a serialized hash. I would like to define accessors into that hash such that I can execute profile.name instead of profile.data['name']. Is that possible in Rails?
The simple straightforward way:
class Profile < ActiveRecord::Base
serialize :data
def name
self.data['name']
end
def some_other_attribute
self.data['some_other_attribute']
end
end
You can see how that can quickly become cumbersome if you have lots of attributes within the data hash that you want to access.
So here's a more dynamic way to do it and it would work for any such top level attribute you want to access within data:
class Profile < ActiveRecord::Base
serialize :data
def method_missing(attribute, *args, &block)
return super unless self.data.key? attribute
self.data.fetch(attribute)
end
# good practice to extend respond_to? when using method_missing
def respond_to?(attribute, include_private = false)
super || self.data.key?(attribute)
end
end
With the latter approach you can just define method_missing and then call any attribute on #profile that is a key within data. So calling #profile.name would go through method_missing and grab the value from self.data['name']. This will work for whatever keys are present in self.data. Hope that helps.
Further reading:
http://www.trottercashion.com/2011/02/08/rubys-define_method-method_missing-and-instance_eval.html
http://technicalpickles.com/posts/using-method_missing-and-respond_to-to-create-dynamic-methods/
class Profile < ActiveRecord::Base
serialize :data # always a hash or nil
def name
data[:name] if data
end
end
I'm going to answer my own question. It looks like ActiveRecord::Store is what I want:
http://api.rubyonrails.org/classes/ActiveRecord/Store.html
So my class would become:
class Profile < ActiveRecord::Base
store :data, accessors: [:name], coder: JSON
end
I'm sure everyone else's solutions work just fine, but this is so clean.
class Profile < ActiveRecord::Base
serialize :data # always a hash or nil
["name", "attr2", "attr3"].each do |method|
define_method(method) do
data[method.to_sym] if data
end
end
end
Ruby is extremely flexible and your model is just a Ruby Class. Define the "accessor" method you want and the output you desire.
class Profile < ActiveRecord::Base
serialize :data
def name
data['name'] if data
end
end
However, that approach is going to lead to a lot of repeated code. Ruby's metaprogramming features can help you solve that problem.
If every profile contains the same data structure you can use define_method
[:name, :age, :location, :email].each do |method|
define_method method do
data[method] if data
end
end
If the profile contains unique information you can use method_missing to attempt to look into the hash.
def method_missing(method, *args, &block)
if data && data.has_key?(method)
data[method]
else
super
end
end

Rails sha1 hash generator

Hi hopefully somebody can help me out. I'm a bit stuck at the moment. I'm trying to create an app for a tracking system, I currently have a table called sdel_hashed. Following online videos I so far set up digest/sha1 to work partly. If I enter the following commands in the console:
sdel = Sdel.find(1)
sdel.hashed_sdel = Sdel.hash('secret')
sdel.save
And then view the record in the browser it show up as the hash and not secret, but if I try and enter the word secret through the new action it doesn't get hashed. I think there is maybe something missing in the create action but I cannot find answers anywhere. i would greatly appreciate any help. I'll include now what I have in my controller and model.
Thanks
model sdel
require 'digest/sha1'
class Sdel < ActiveRecord::Base
attr_accessible :hashed_sdel
def self.hash(sdel="")
Digest::SHA1.hexdigest(sdel)
end
end
controller sdels
class SdelsController < ApplicationController
def list
#sdel = Sdel.all
end
def new
#sdel = Sdel.new
end
def create
#sdel = Sdel.new(params[:sdel])
if #sdel.save
redirect_to(:action => 'list')
else
render('new')
end
end
end
Migration file
class CreateSdels < ActiveRecord::Migration
def change
create_table :sdels do |t|
t.string "hashed_sdel"
t.timestamps
end
end
end
Sounds like you may want to use a before_save filter to invoke the hash class method on the Sdel model prior to saving when the attribute has been modified. Perhaps something along the lines of this:
require 'digest/sha1'
class Sdel < ActiveRecord::Base
attr_accessible :hashed_sdel
before_save { self.hashed_sdel = self.class.hash(hashed_sdel) if hashed_sdel_changed? }
def self.hash(sdel="")
Digest::SHA1.hexdigest(sdel)
end
end
This way, if you have a form that has a text_field for your hashed_sdel attribute, it will automatically get run through the hash class method you have before it the record gets saved (assuming the attribute has changed from it's previous value).

How to create a form for the rails-settings plugin

I have a Rails 3 App that has needs some user defined settings. I would like to use this https://github.com/ledermann/rails-settings plugin. I have it working in the rails console. But I am having trouble getting working in a form. Do I use fields_for & attr_accessible? If so I am having no luck.
I need to add settings for two Models:
For example, settings that are specific to a User,
user = User.find(123)
user.settings.color = :red
user.settings.color
# => :red
user.settings.all
# => { "color" => :red }
(The above works fine for me in the console.)
but I need to administer them through a standard web form. I'd love to know how others are handling this.
Thanks.
What I did is add dynamic setters/getters to my User class as such
class User < ActiveRecord::Base
has_settings
def self.settings_attr_accessor(*args)
args.each do |method_name|
eval "
def #{method_name}
self.settings.send(:#{method_name})
end
def #{method_name}=(value)
self.settings.send(:#{method_name}=, value)
end
"
end
end
settings_attr_accessor :color, :currency, :time_zone
end
With that, you can use "color" just like any other attribute of your User model.
Also it's very simple to add more settings, just add them to the list

Resources