I'm building backend system, as written in Iain Hecker's tutorial: http://iain.nl/backends-in-rails-3-1 and I try to adapt it to MongoDB with Mongoid.
So when I need to write in backend/resourse_helper.rb
module Backend::ResourceHelper
def attributes
resource_class.attribute_names - %w(id created_at updated_at)
end
end
I get the following error:
undefined method `attribute_names' for Backend::User:Class
(I rooted backend to "backend/users#index").
Backend::User inherits from User:
class User
include Mongoid::Document
devise_for :users
field :name
field :address
end
I just need a list of fields for that User:Class, as I guess (i.e. ["email", "name", "address", ...]), but I broke my head trying to find how.
Mongoid already provides you the attributes for an object:
Model.new.attributes
To get the names for these attributes:
Model.fields.keys
Use the built-in method:
Model.attribute_names
# => ["_id", "created_at", "updated_at", "name", "address"]
One thing to take note of is that Model.fields.keys will only list the field keys that are defined in the Model class. If you use dynamic fields they will not be shown. Model.attributes.keys will also include the attribute keys for any dynamic fields you have used.
You're on the right track with attribute_names. I think you just need to make sure you're including your module in the proper place. For instance, if you had the same module:
module Backend::ResourceHelper
def attributes
resource_class.attribute_names - %w(id created_at updated_at)
end
end
Your class should look like so:
class User
include Mongoid::Document
extend Backend::ResourceHelper
devise_for :users
field :name
field :address
end
Then calling User.attributes should return ["name", "address"]
Related
I've currently got a user object but to avoid redundancy, I'd like to wrap it into a presenter object called MerchantUser/ProviderUser. However, with ActiveAdmin, I'm a little confused on how to do this. I've tried using before_create to change the user into the corresponding presenters but in index...do, I'm still seeing that user.class is equal to User and not the wrapper classes that I've defined.
I've looked into scoping_collection but unfortunately that only works on collections and not individual objects?
ActiveAdmin.register User, as: "Companies" do # rubocop:disable Metrics/BlockLength
before_create do |user|
if user.merchant?
user = MerchantUser.new(user)
else
user = ProviderUser.new(user)
end
end
actions :all, except: [:destroy]
permit_params :name, :email, contract_attributes: [:id, :flat_rate, :percentage]
filter :role, as: :select
index do # rubocop:disable Metrics/BlockLength
column :name do |user|
user.name <---I want it so I can just do this without the if/else blocks like below.
end
column :role
column :contact_phone
column :email
column :website do |user|
if user.merchant?
user.company.website
else
user.provider.website
end
end
column :flat_rate do |user|
money_without_cents_and_with_symbol(user.contract.flat_rate)
end
column :percentage do |user|
number_to_percentage(user.contract.percentage, precision: 0)
end
actions
end
Have you looked into Active Admin's support for decorators? This page is quite comprehensive. The best way to implement them depends on how your decorator/presenter object is implemented.
Link summary: use decorate_with or look into using this gem for PORO support
Are you sure you want/need a presenter here? You can register the same Rails model multiple times as ActiveAdmin resources with different names and customizations (filters, index page, forms, etc). You can also use Rails STI or just subclass Rails models, perhaps with different Rails default_scope and then register the subclasses.
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
How do would I format my model so that it will output a json document with an id and name field?
Because my model has custom field names and I am using tokeninput and it requires me to output it to id and name.
any ideas?
You have so many options here, you can use jbuilder, rabl. But I think the easiest one is to use Active Model Serializers.
Let's say you have a model name User.
First install the bundle, then:
rails g serializer user
Then at app/serializers/user_serializer.rb:
class ArticleSerializer < ActiveModel::Serializer
attributes :id, :name
end
You might want to pass a only option to to_json
:only => [:id, :name]
For example, if you want to get id and name of User
User.all.to_json :only => [:id, :name]
If the model does not contain Id and Name as described by OP. Then you use custom select at the time of querying the db.
User.select('filed1 as id, field2 as name').all.to_json
Maybe in your controller
require 'json'
# ...
def show_json
user_obj_to_json = User.first.to_json
render user_obj_to_json
end
I asked a similar question about select options a while ago but I still can't seem to wrap my head around it. I'm rather new to rails but here's what I'm trying to do
I have a Post table & in it, I have a "post_status" column. I
would like to give each post 3 options:
Draft
Pending
Publish
How would I go about creating these 3 options in Rails? (I was advised not to use booleans for this)
Thank you in advance
Elaborating on #Alexander Kobelev answer, I'd put it all in the model:
class Post < ActiveRecord::Base
STATUS_OPTIONS = {
:draft => 'Draft',
:pending => 'Pending',
:published => 'Published'
}
validates_inclusion_of :post_status, :in => STATUS_OPTIONS.keys
end
in your view:
Post Status: <%= select(:post, :post_status, Post::STATUS_OPTIONS.invert) %>
In this particular instance they look like status flags that could be handled a few ways, but you've asked about select options so here's a solution for that method.
Because you don't specify if you need to keep the values already in the table I've detailed a method that allows you to keep them by converting them to IDs (assuming they are currently strings), if this is not relevant then follow only the bold instructions.
Create a PostStatus resource (model, migrate, controller/view if you need the ability to change them).
Define the relationships
PostStatus
has_many :posts
Post
belongs_to :post_status
Add values to your PostStatus table (if you have a live system with strings in the table you should match the existing post status strings here to allow you to convert the data (detailed below).
Change column name to post_status_id in the Post table, change its type to int. If this isn't live then just redo the migrate with the column as integer. If it is a live system you'll need to convert your data into a new column instead of just changing its type, the below is a suggested method.
add_column :posts, :post_status_id_tmp, :int
Post.reset_column_information # make the new column available to model methods
Post.all.each do |post|
# Assuming you have a string with the option text currently:
post.post_status_id_tmp = PostStatus.find_by_name(post.post_status).id
post.save
end
remove_column :posts, :post_status
rename_column :posts, :post_status_tmp, :post_status_id
In your post form add a selectbox.
<%= form.collection_select :post_status_id, PostStatus.all, :id, :name %>
That should at the least get you started!
You can try something like this:
class Post < ActiveRecord::Base
validates_inclusion_of :status, :in => [:draft, :pending, :publish]
def status
read_attribute(:status).to_sym
end
def status= (value)
write_attribute(:status, value.to_s)
end
end
where status is :string, limit: 20 (it's just for example) in migration
or you can try to use https://github.com/jeffp/enumerated_attribute
I have problem with mongomapper associations. I have one class names User and other named Model. User has many models but...
user = User.first
=> <User ...
user.models
=> []
Model.find_by_user_id(user.id.to_s)
=> <Model ...
Model.find_by_user_id(user.id.to_s).user == user
=> true
Class code (simplified):
class User
include MongoMapper::Document
# some keys definition
many :models
end
class Model
include MongoMapper::Document
# some keys definitions
belongs_to :user
end
What I am doing wrong?
It appears that MM no longer uses String format for the FK column, so
Model.find_by_user_id(user.id.to_s)
should be
Model.find_by_user_id(user.id)
Furthermore, the datatype of the Model.user_id column should be set to
key :user_id, Mongo::ObjectID
When I ran into this problem, I had to delete and recreate my collection to get it to work- in other words I used to have user_id as a String, but it would only "take" when I switched it when I rebuilt my database. Luckily I am working with test data so that was easy enough.
What kind of errors or exceptions are you getting? The code you posted looks fine.
ah, this is poorly documented in the mm docs. You need to do this here:
class User
include MongoMapper::Document
# some keys definition
many :models, :in => :model_ids
end
class Model
include MongoMapper::Document
# some keys definitions
# no belongs_to necessary here
end
You can then add models to your user via:
# use an existing object
u = User.create ...
m = Model.create ...
# and add the model to the user
u.models << m
# don't forget to save
u.save
# you can then check if it worked like so:
# u.model_ids => [ BSON::ID 'your user id']
Hope that helped.