After a long conversation with the fine people at IndexTank, I was not really sure on how to fix my problem, and I was wondering if anyone could help me.
I have an article model that belongs to a user model. This article model also has a boolean attribute called anonymous, which, if set to true gives the user the option to post the article without his name being shown.
Article
belongs_to :user
attr_accesible :anonymous, :user_id
User
has_many :articles
My problem is that if the article is posted as anonymous. I don't want tanker to search within the author name field, but I want it searching every other field. I tried to do this with an if else statement where I would normally put the "tankit" block, but that does not work.
Is there a way I could put the tankit block into a model method and use a validation call back like this?
def anon_index
if self.anonymous
tankit 'my_index' do
indexes VARIABLES ETC BUT NOT the user_ attributes
end
else # if anonymous is false
tankit 'my_index' do
indexes :title
indexes :body
indexes :user_penname
indexes :user_firstname
indexes :user_lastname
end
end
end
I was thinking either this or putting an if else statement where the "tankit" block declaration goes, but neither of those seem to, unless I'm doing something wrong.
how does this look?:
class Article < ActiveRecord::Base
tankit 'my_index' do
indexes :title
indexes :body
indexes :custom_penname
indexes :custom_firstname
indexes :custom_lastname
end
def custom_penname
if self.anonymous
'anonymous'
else
self.user_penname
end
end
def custom_firstname
#same for first name
end
def custom_lastname
#same for last name
end
end
Same approach, different scenario:
https://github.com/adrnai/rails-3-tanker-demo/blob/master/app/models/comment.rb
Related
class User
has_many :addresses
def csv_header
self.addresses.attribute_names
end
def csv_values
self.addresses.all do |addr|
addr.attributes.values
end
end
end
class Address
belongs_to :user
end
*i am trying to pull the attribute names of the address model to user model,but this method isn't working so can anyone help *
Not much needed here - I think you just need to map the addresses in csv_values.
class User
has_many :addresses
def csv_header
addresses.attribute_names
end
def csv_values
addresses.map do |addr|
addr.attributes.values
end
end
end
class Address
belongs_to :user
end
Does that fix this for you?
I'd be tempted to shift things around a little for clarity in the code and make use of delegate:
class user
...
delegate :attribute_names, to: :addresses, prefix: true, allow_nil: true
...
end
class Address
...
def self.mapped_values
all.map { |addr| addr.attributes.values }
end
...
end
Then you can just call user.addresses_attribute_names and user.addresses.mapped_values.
You can also just call Address.column_names to get the header array, if it will always stay consistent, as is likely to be the case.
Hope that helps!
Update based on comment:
To achieve the same for users, you can call the following:
Either call User.column_names or user.attribute_names to get the headers (on the class for the former, and an instance for the latter).
If you also need the users' mapped values, you can copy across the self.mapped_values method from the address model and use that. It's a little duplication, but for a pair of methods like this I wouldn't be inclined to separate these into a separate module.
Final tip - if you're calling the address methods from a collection of users (i.e. User.all) make sure you adjust it to include the addresses to avoid hitting your database in an inefficient way (User.includes(:addresses)...).
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
country attribute's default value is nil.
In countries table, some record has image_url, and the rest of the record's country attributes are nil.
So I coded this in helper
def image(user)
if user.country.image_url
image_tag "flags/#{user.country.image_url}.png"
end
end
However, it returns error when image_url was nil
Something went wrong
How can I fix?
You'll need two conditions: The user has to have a country, and that country has to have an image_url. Only then will there be something to show. Luckily, it's a simple tweak:
def image(user)
if(user.country && user.country.image_url)
image_tag "flags/#{user.country.image_url}.png"
end
end
If you're paranoid, you should make sure that user isn't nil either.
Hope that helps!
While method chaining like that certainly works, your code will look a lot cleaner and become less coupled if you implement some method delegation.
Inside of your User model:
class User < ActiveRecord::Base
belongs_to :country
delegate :image_url, :to => :country, :prefix => true, :allow_nil => true
end
Now your helper becomes simply:
def image(user)
if user.country_image_url
image_tag "flags/#{user.country_image_url}.png"
end
end
Law of Demeter states:
Each unit should have only limited knowledge about other units: only units "closely" related to the current unit.
Also check out Rail Best Practices Law of Demeter; if nothing else you're saving yourself the extra clause in your if statement & your code looks pretty.
I currently have the following controller method in a Rails app:
def index
#entries = []
#entries << QuickPost.where(:user_id.in => current_user.followees.map(&:ff_id) << current_user.id)
#entries << Infographic.where(:user_id.in => current_user.followees.map(&:ff_id) << current_user.id)
#entries.flatten!.sort!{ |a,b| b.created_at <=> a.created_at }
#entries = Kaminari.paginate_array(#entries).page(params[:page]).per(10)
end
I realise this is terribly inefficient so I'm looking for a better way to achieve the same goal but I'm new to MongoDB and wondering what the best solution would be.
Is there a way to make a sorted limit() query or a MapReduce function in MongoDB across two collections? I'm guessing there isn't but it would certainly save a lot of effort in this case!
I'm currently thinking I have two options:
Create a master 'StreamEntry' type model and have both Infographic and QuickPost inherit from that so that both data types are stored on the same collection. The issue with this is that I have existing data and I don't know how to move it from the old collections to the new.
Create a separate Stream/ActivityStream model using something like Streama (https://github.com/christospappas/streama). The issues I can see here is that it would require a fair bit of upfront work and due to privacy settings and editing/removal of items the stream would need to be rebuilt often.
Are there options I have overlooked? Am I over-engineering with the above options? What sort of best practices are there for this type of situation?
Any info would be greatly appreciated, I'm really liking MongoDB so far and want to avoid falling into pitfalls like this in the future. Thanks.
The inherit solution is fine, but when the inherited models are close.
For example :
class Post < BasePost
field :body, type: String
end
class QuickPost < BasePost
end
class BasePost
field :title, type: String
field :created_at, type: Time
end
But when the models grows, or are too different, your second solution is better.
class Activity
include Mongoid::Document
paginates_per 20
field :occurred_at, :type => Time, :default => nil
validates_presence_of :occurred_at
belongs_to :user
belongs_to :quick_post
belongs_to :infographic
default_scope desc(:occurred_at)
end
and for example :
class QuickPost
include Mongoid::Document
has_one :activity, :dependent => :destroy
end
The dependant destroy make the activity destroyed when the QuickPost is destroyed. You can use has_many and adapt.
And to create the activities, you can create an observer :
class ActivityObserver < Mongoid::Observer
observe :quick_post, :infographic
def after_save(record)
if record.is_a? QuickPost
if record.new_record?
activity = record.build_activity
activity.user = record.user
# stuff when it is new
else
activity = record.activity
end
activity.occurred_at = record.occurred_at
# common stuff
activity.save
end
end
end
I have an indexed model called Article and I don't want solr to index unpublished articles.
class Article < ActiveRecord::Base
searchable do
text :title
text :body
end
end
How can I specify that article that is not #published? should not be indexed?
Be sure to index the published status.
class Article < ActiveRecord::Base
searchable do
text :title
text :body
boolean :is_published, :using => :published?
end
end
Then add a filter to your query
Sunspot.search(Article) do |search|
search.with(:is_published, true)
# ...
end
If you want to make sure unpublished articles are never included in the search index, you can do it this way instead:
class Article < ActiveRecord::Base
searchable :if => :published? do
text :title
text :body
end
end
The model will then only be indexed when published.
My approach is less interesting if you also want admins to be able to search for articles, including unpublished ones, however.
Note: calling article.index! will add the instance to the index regardless of the :if => :method param.
A small look into the code base of sunspot_rails reveals a method called maybe_mark_for_auto_indexing which will be added to the models that include solr. You could override that method and set #marked_for_auto_indexing based on your criteria in the specific model. Its monkey patching but can help you solve the problem. The code for ur reference is in lib/sunspot/searchable.rb.