rails 3 pagination with kaminari on mongoid embedded documents - ruby-on-rails-3

When I call paginate with kaminari on a collection of embedded documents I get the following error:
(Access to the collection for Document is not allowed since it is an embedded document, please access a collection from the root document.):
Any idea on how I can fix that ? I have installed kaminari as a gem.
Alex

You just need to access the collection through the parent object. For example, given the following models:
class User
include Mongoid::Document
embeds_many :bookmarks
end
class Bookmark
include Mongoid::Document
embedded_in :user
end
Then to paginate a given user's bookmarks you would do:
#user.bookmarks.page(params[:page])

I found this issue on Kaminari: https://github.com/amatsuda/kaminari/issues/89
So I forked it, and fixed it following the solution provided by spatrik. I am not 100% sure it will work on all cases and that this solution does not have any drawbacks. But for the moment it works exactly as expected.
Alex

I just sent a patch for the issue. Take a look at the request. hope this helps solve your issue.
https://github.com/amatsuda/kaminari/pull/155/files

With the previous theTRON example :
class User
include Mongoid::Document
embeds_many :bookmarks
end
class Bookmark
include Mongoid::Document
field :created_at, :type => DateTime
embedded_in :user
end
the following, will get you the error you described in your post :
#user.bookmarks.desc(:created_at).page(params[:page])
while the nex one will works fine :
#user.bookmarks.page(params[:page]).desc(:created_at)
I hope it help.

Related

How does Devise hide some fields when output json

I am using Grape + Mongoid + Devise.
I found that the Devise user model have more fields (e.g. encrypted_password, sign_in_count, last_sign_in_at) than the user json output when I wrote API response.
I have searched in Devise code, didn't find anything like custom to_json, how does Devise achieve that?
I'm not sure on Grape, but on Rails you can do it with a serializer (as Grape has many code compatible with Rails, I think there's a big chance to work).
To use a serializer, you need to include the "active_model_serializers" gem.
Example:
class UserSerializer < ActiveModel::Serializer
attributes :id, :email, :username
end
On this example, Devise will always print only these 3 fields on a JSON output.
To include all attributes except some of them, you can do something like this:
class UserSerializer < ActiveModel::Serializer
attributes(*(User.attribute_names - ["date_created", "first_name"] ).map(&:to_sym))
end
Also, at least on Rails, you'll want to remove the root from the output. To do this, add this code to your application_controller.rb:
def default_serializer_options
{root: false}
end

Mongoid code not working in production

The following code works in development like a charm, but in production no update is ever issued to the database. Basically I am trying to update a collection element that is nested 3 levels deep ..
def set_collection_value
#article = Article.find(params[:id])
#article.highlights.items.find(params[:item_id])
item.update_attributes(params[:values])
render :json => item
end
The model Article in question looks like this:
class Article
include Mongoid::Document
embeds_one :highlight, class_name: 'Highlight'
end
highlight.rb
class Highlight
includes Mongoid::Document
embedded_in :article
embeds_many :items, class_name: 'HighlightElement'
end
highlight_item.rb
class HighlightElement
include Mongoid::Document
embedded_in :highlight
field :title, type: String
field :teaser, type: String
field :image, type: String
field :body, type: String
attr_accessible :title, :teaser, :image, :body
end
The funny thing here is that even when I run webrick locally in production mode it works like a charm. Only once I deploy to my ubuntu server running mongodb v1.2.2 the above code silently does nothing.
I even went as far as to copy my environments/development.rb onto my environments/production.rb hoping to fix the issue.. but to no avail..
Any ideas? I am running rails 3.2.7 with mongoid 3.0.3
It would also be helpful if someone could point out how to make the moped/mongoid debug log messages show up in production.log. I configured Debugging as described in the logs - but these debug messages are only visible when running rails s - not in production.log
Solved it myself..
Turns out Mongodb 1.2.2 from the official Ubuntu repository sources was the problem.
After updating to 2.0.5 everything worked again correctly.

Issue with pushing additional values in a embeds_many mongoid relation

I have been breaking my head around this for a long time now. Not sure if my approach is correct or if its not possible using mongoid. SO without further adieu, here is the problem:
I have the following 2 models:
def user
embeds_many :needs, :class_name => "Property"
embeds_many :skills, :class_name => "Property"
end
def property
end
Both these models of course have other code but I have skipped that for brevity.
With this structure I am able to access/add "Property" data as embedded "needs" & "skills" on my user model. Something like this works flawlessly
User.first.update_attributes(skills: [Property.first])
The problem is something like this doesn't work.
User.first.skills.push(Property.first)
User.first.skills << Property.first
There is no error. Both the above statements return true on console. But the values don't persist to the DB.
I basically want a Property model which can be maintained/created independent of the User model, thats why the "embedded_in" on Property is missing in my code.
The question is, am I doing it right? Or there is their a different way that I should go about the design of these models?
Cage is right. You will need to put the embedded_in on the Property model if you want the persistence to work properly. If you want to manage the lifecycle of Property outside the User model, you will have to use 'has_many'.
Please add more details as to what exactly is the purpose of doing what you are doing. I am going to make some assumptions-
Needs and skills are a global list of values, that should be maintained separately
User can have a subset of skills and needs
You want to store the needs and skills as 'plain string' and not references so you can query them without referencing the needs and skills collection. This reduces one query
If the above is correct, then you can use custom keys to store the needs and skills-
class Property
include Mongoid::Document
field :text, :type => String
field :_id, type: String, default ->{ text }
end
class User
include Mongoid::Document
has_many :needs, :class_name => "Property"
has_many :skills, :class_name => "Property"
end
Now you can do something like-
User.first.need_ids
This will give the text of the need and you can avoid another query.
Note- that this is potentially very risky if your 'Property' objects are mutable.
For solution try doing this
u = User.first
u.skills.push(Property.first)
u.skills << Property.first
it will work fine.

ActAsTaggableOn in mongodb and rails 3

I want to adding tagging facility in my application . so, I am using acts_as_taggable_on : https://github.com/mbleigh/acts-as-taggable-on
I have added following line my Gemfile:
gem 'acts-as-taggable-on', '~> 2.2.2'
and when I add following line in my user model:
acts_as_taggable_on
It gives me this error:
undefined local variable or method `acts_as_taggable_on' for User:Class
Kindly, tell me what am I doing wrong?
That gem isn't going to work with mongoid and mongodb because it is built to allow tagging using a relational database using active record.
The good news is that this is very simple to do in mongoid. Simply add a new Array field named after whatever you would have listed as being acts_as_taggable_on. If you also have acts_as_taggable, include a generic tags field as well.
If you were going to have a model that looked like this:
class User < ActiveRecord::Base
acts_as_taggable
acts_as_taggable_on :skills, :interests
end
You would build it like this with mongoid:
class User
include Mongoid::Document
field :tags, type: Array
field :skills, type: Array
field :interests, type: Array
end
Then when you wanted to save a tag, lets say as an interest you would do the following:
#user.interests << 'computers'

How to user defined friendly URLs in Rails 3?

Now i have something like this
http://myapp.com/pages/1
http://myapp.com/pages/2
http://myapp.com/pages/3
http://myapp.com/pages/4
And each page belong to one user
What i need is to each user to set it's own custom name for the page.
I was thinking of using the friendly_id gem http://norman.github.com/friendly_id/
but I don't find any method to directly edit the slug to set a custom friendly url
how should i proceed?
FriendlyID is a great gem.
It shouldn't be hard to implement user defined page URL.
Create table pages with user_id and link
class User < ActiveRecord::Base
has_many :pages
class Page < ActiveRecord::Base
belongs_to :user
has_friendly_id :link # link is name of the column whose value will be replaced by slugged value
On the page#new you add an input for the link attribute.
Alternatively, you could set friendly_id on title or something else with :use_slug => true option. This way FriendlyID will take the title and modify it so it doesn't have and restricted characters. It will use it's own table to store slugs. Use cached_slug to increase performanse.
Updated
To give users a choice whether they wan't to set a custom link, you could do this:
Set friendly_id on the link field without slugs..
Make a virtual attribute permalink so you could show it in your forms.
In the before_filter, check whether the permalink is set.
If it is, write it to the link field.
If it's not, write title to the link field.
FriendlyID uses babosa gem to generate slugs. If you decide to use it as well, this is how your filter could look like:
protected
def generate_link
#you might need to use .nil? instead
self.link = self.permalink.empty? ? make_slug(self.title) : make_slug(self.permalink)
end
def make_slug(value)
value.to_slug.normalize.to_s #you could as well use ph6py's way
end
Adding to_param method to one of the models should help:
def to_param
"#{id}-#{call_to_method_that_returns_custom_name.parameterize}"
end
Hope this is what you are looking for :)
I am not using the friendly_url gem and am not sure whether my way is efficient. But it works fine for me.
I have a model called Node with id and friendly url field called url_title.
My routes.rb file:
resource 'nodes/:url_title', :to => 'Nodes#view'
nodes_controller.rb
class NodesController <ActiveController
def view
#node = Node.find_by_url_title(:params(url_title))
end
end
And use the #node variable to populate your view.
Now, whenever I type www.example.com/nodes/awesome-title , it takes me to the proper page. One argument against this can be need to create an index on a non-primary field. But I think that might be required for better performance even in the friendly_url gem. Also, the non-primary field url_title needs to be unique. Again, this might be required even for correct working for friendly_url .
Feel free to correct me if I am wrong in these assumptions.
There are a variety of ways, you can achieve this-
1) using Stringex
2) sluggable-finder
3) friendly_id
A complete step by step methodology with reasons for each to be used can be found out here. Happy reading!