I have a little bit of a complex model and want to get the full functionality capable with my limited understanding of rails.
I have a section, a header (which uses acts_as_tree), and an item.
I use json to bring sets of data in. This has worked very well. I would like to be able to bring attributes in for whole sets of data such as 'is_shippable'. I would like to be able to specify anywhere in the tree the is_shippable value and set to true. Also, I would like to be able to override at the header or item level to set it to false.
I have decided that it makes sense to have is_shippable as an attribute on the section, header, and item and try to use a before_create callback to determine whether it should be is_shippable.
For example:
section
header -acts_as_tree
item - is_shippable
sample json:
{
"name":"sample section",
"is_shippable": true,
"headers_attributes":[
{
"name":"sample_section"
"items_attributes":[{
"name":"sample item",
"is_shippable":false,
}
]
}
]
}
in header.rb
before_save :default_values
private
def default_values
self.is_shippable ||=self.section.is_shippable
# need to be able to set header to is_shippable=false if specified explicitly at that level
end
in item.rb
before_save :default_values
private
def default_values
# if not set, default to 0
self.is_shippable ||= 0
self.is_shippable=1 if self.header.is_shippable==true
# need to be able to set item to is_shippable=false if specified explicitly at that level
end
Is there a better way to do this than I am doing? How would I execute in if statements checking if is_shippable is set to false if it has been set to true higher in the hierarchy?
EDIT - also there are more features that is_shippable like is_fragile, is_custom_size etc...
I would be more inclined to use a before_filter in the controller to modify the nested item params.
something like:
before_filter :set_is_shippable, :only => [:update, :create]
def set_is_shippable
is_shippable = params[:section][:is_shippable]
params[:section][:items_attributes].each_with_index do |item, index|
unless item[:is_shippable]
params[:section][:items_attributes][index][:is_shippable] = is_shippable
end
end
end
I highly recommend the ancestry gem. It is has many more tree traversal methods, and also optimizes the number of queries to the database.
If I understand your dilemma correctly, ancestry would allow you to do something such as:
#section.descendants.all?(&:is_shippable)
In any case, ancestry is much more expressive and will certainly give you greater flexibility. The github wiki linked below for the gem is the best I've ever seen. Very well organized, perhaps perusing it will give you some more ideas.
https://github.com/stefankroes/ancestry
http://railscasts.com/episodes/262-trees-with-ancestry
Related
I use jsonapi-resources gem in my Rails app. One of my resources is using pagination. There are cases when I want to disable the pagination and make sure that user will get back all the results.
I tried custom implementation with some custom filter, but failed.
I tried passing "weird" values to paginator, but that did not help (examples for paged paginator):
/api/v1/bars?page[size]=-1 - value not allowed
/api/v1/bars?page[size]=0 - value not allowed
/api/v1/bars?page[size]=999999 - sure, this might work, but it is 100%
i got exactly the same problem today and found this unanswered question.
Below is how I dealt with the problem.
class CustomPaginator < PagedPaginator
attr_reader :limit, :offset, :disable_pagination
def initialize(params)
#disable_pagination = params.nil?
super
end
def apply(relation, _order_options)
disable_pagination ? relation : super
end
end
and in the resources I wanted to use the CustomPaginator I just added paginator :custom like below:
class UserResource < JSONAPI::Resource
paginator :custom
....user implementation
end
now whenever i want to use the paginator i need to explicitly use paginations params when I do the request to the server and when I don't use them I will get full unpaginated response.
The class implementation is not perfect (duh!) but it's just to show you the general concept
I have an ActiveAdmin resource whose collection requires some particularly complex queries to avoid a bunch of n+1's. I tried to modify controller#scoped_collection with all the proper .includes or .joins, but it was still doing n+1's.
I've since changed to an approach of just doing the queries by hand and dumping the relevant results into Hashes. Then in my index view, I grab the data from the hashes, rather than from the scoped_collection.
Unfortunately, I can't figure out how to make an instance variable available in the index scope. I got around that by putting it into the params hash (which is available in the index scope), but that ends up breaking the filtering.
So, is there some way I'm not seeing to scope variables from inside scoped_collection or some other sort of before_filter-like method that will be available inside index?
Sample code:
ActiveAdmin.register ComplexResource do
actions :index
controller do
def scoped_collection
#complex_collection_relation = ComplexResource.where(crazy: :stuff)
# a bunch more lines of code to do avoid n+1's and put info into supporting data hash
#supporting_data_hash = {}
complex_collection_relation.each{|r| supporting_data_hash[r.id] = stuff }
# my hacky workaround because #supporting_data_hash isn't available below
params[:supporting_data_hash] = #supporting_data_hash
return #complex_collection_relation
end
end
filter :my_filter_that_gets_broken, as: :string
index do
column :name
column :complex_attribute_1 do |r|
params[:supporting_data_hash][r.id][:complex_attribute_1]
end
# how can I do something like this without using the params workaround
# column :complex_attribute_1 do |r|
# #supporting_data_hash[r.id][:complex_attribute_1]
# end
end
end
I know this is an old question but it's the first one that comes up when googling. Also, I'm not 100% sure it applies to the duplicate question so I'll leave my answer here:
Instance variables can be reached as helpers when defining columns for a resource. E.g.:
ActiveAdmin.register Company do
controller do
before_action :load_data, only: :index
def load_data
#loaded_data = expensive_operation
end
end
index do
column 'Something' do |company|
loaded_data[company.id]
end
end
end
I found a reference to it on this answer on Github issues
#Jiemurat 's answer to this question is the solution. This question is duplicative of How do I use instance variables, defined in the controller, in the view with ActiveAdmin?
I'm new to Rails (I've worked in MVC but not that much) and I'm trying to do things the "right" way but I'm a little confused here.
I have a site navigation with filters Items by different criteria, meaning:
Items.popular
Items.recommended
User.items
Brand.items # by the parent brand
Category.items # by a category
The problem is that I don't know how to deal with this in the controller, where each action does a similar logic for each collection of items (for example, store in session and respond to js)
Either I have an action in ItemsController for every filter (big controller) or I put it in ItemsController BrandsController, CategoriesController (repeated logic), but neither provides a "clean" controller.
But I don't know witch one is better or if I should do something else.
Thanks in advance!
You're asking two separate questions. Items.popular and Items.recommended are best achieved in your Item model as a named scope This abstracts what Xavier recommended into the model. Then in your ItemsController, you'd have something like
def popular
#items = Item.popular
end
def recommended
#items = Item.recommended
end
This isn't functionally different than what Xavier recommended, but to me, it is more understandable. (I always try to write my code for the version of me that will come to it in six months to not wonder what the guy clacking on the keyboard was thinking.)
The second thing you're asking is about nested resources. Assuming your code reads something like:
class User
has_many :items
end
then you can route through a user to that user's items by including
resources :users do
resources :items
end
in your routes.rb file. Repeat for the other nested resources.
The last thing you said is
The problem is that I don't know how to deal with this in the controller, where each action does a similar logic for each collection of items (for example, store in session and respond to js)
If what I've said above doesn't solve this for you (I think it would unless there's a piece you've left out.) this sounds like a case for subclassing. Put the common code in the superclass, do the specific stuff in the subclass and call super.
There's a pretty convenient way to handle this, actually - you just have to be careful and sanitize things, as it involves getting input from the browser pretty close to your database. Basically, in ItemsController, you have a function that looks a lot like this:
def search
#items = Item.where(params[:item_criteria])
end
Scary, no? But effective! For security, I recommend something like:
def search
searchable_attrs = [...] #Possibly load this straight from the model
conditions = params[:item_criteria].keep_if do |k, v|
searchable_attrs.contains? k
end
conditions[:must_be_false] = false
#items = Item.where(conditions)
end
Those first four lines used to be doable with ActiveSupport's Hash#slice method, but that's been deprecated. I assume there's a new version somewhere, since it's so useful, but I'm not sure what it is.
Hope that helps!
I think both answers(#Xaviers and #jxpx777's) is good but should be used in different situations. If your view is exactly the same for popular and recommended items then i think you should use the same action for them both. Especially if this is only a way to filter your index page, and you want a way to filter for both recommended and popular items at the same time. Or maybe popular items belonging to a specific users? However if the views are different then you should use different actions too.
The same applies to the nested resource (user's, brand's and category's items). So a complete index action could look something like this:
# Items controller
before_filter :parent_resource
def index
if #parent
#items = #parent.items
else
#items = Item.scoped
end
if params[:item_criteria]
#items = #items.where(params[:item_criteria])
end
end
private
def parent_resource
#parent = if params[:user_id]
User.find(params[:user_id])
elsif params[:brand_id]
Brand.find(params[:brand_id])
elsif params[:category_id]
Category.find(params[:category_id])
end
end
This is probably one of the things that all new users find out about Rails sooner or later. I just realized that rails is updating all fields with the serialize keyword, without checking if anything really changed inside. In a way that is the sensible thing to do for the generic framework.
But is there a way to override this behavior? If I can keep track of whether the values in a serialized fields have changed or not, is there a way to prevent it from being pushed in the update statement? I tried using "update_attributes" and limiting the hash to the fields of interest, but rails still updates all the serialized fields.
Suggestions?
Here is a similar solution for Rails 3.1.3.
From: https://sites.google.com/site/wangsnotes/ruby/ror/z00---topics/fail-to-partial-update-with-serialized-data
Put the following code in config/initializers/
ActiveRecord::Base.class_eval do
class_attribute :no_serialize_update
self.no_serialize_update = false
end
ActiveRecord::AttributeMethods::Dirty.class_eval do
def update(*)
if partial_updates?
if self.no_serialize_update
super(changed)
else
super(changed | (attributes.keys & self.class.serialized_attributes.keys))
end
else
super
end
end
end
Yes, that was bugging me too. This is what I did for Rails 2.3.14 (or lower):
# config/initializers/nopupdateserialize.rb
module ActiveRecord
class Base
class_attribute :no_serialize_update
self.no_serialize_update = false
end
end
module ActiveRecord2
module Dirty
def self.included(receiver)
receiver.alias_method_chain :update, :dirty2
end
private
def update_with_dirty2
if partial_updates?
if self.no_serialize_update
update_without_dirty(changed)
else
update_without_dirty(changed | (attributes.keys & self.class.serialized_attributes.keys))
end
else
update_without_dirty
end
end
end
end
ActiveRecord::Base.send :include, ActiveRecord2::Dirty
Then in your controller use:
model_item.no_serialize_update = true
model_item.update_attributes(params[:model_item])
model_item.increment!(:hits)
model_item.update_attribute(:nonserializedfield => "update me")
etc.
Or define it in your model if you do not expect any changes to the serialized field once created (but update_attribute(:serialized_field => "update me" still works!)
class Model < ActiveRecord::Base
serialize :serialized_field
def no_serialize_update
true
end
end
I ran into this problem today and ended up hacking my own serializer together with a getter and setter. First I renamed the field to #{column}_raw and then used the following code in the model (for the media attribute in my case).
require 'json'
...
def media=(media)
self.media_raw = JSON.dump(media)
end
def media
JSON.parse(media_raw) if media_raw.present?
end
Now partial updates work great for me, and the field is only updated when the data is actually changed.
The problem with Joris' answer is that it hooks into the alias_method_chain chain, disabling all the chains done after (like update_with_callbacks which accounts for the problems of triggers not being called). I'll try to make a diagram to make it easier to understand.
You may start with a chain like this
update -> update_with_foo -> update_with_bar -> update_with_baz
Notice that update_without_foo points to update_with_bar and update_without_bar to update_with_baz
Since you can't directly modify update_with_bar per the inner workings of alias_method_chain you might try to hook into the chain by adding a new link (bar2) and calling update_without_bar, so:
alias_method_chain :update, :bar2
Unfortunately, this will get you the following chain:
update -> update_with_bar2 -> update_with_baz
So update_with_foo is gone!
So, knowing that alias_method_chain won't let you redefine _with methods my solution so far has been to redefine update_without_dirty and do the attribute selection there.
Not quite a solution but a good workaround in many cases for me was simply to move the serialized column(s) to an associated model - often this actually was a good fit semantically anyway.
There is also discussions in https://github.com/rails/rails/issues/8328.
I am developing a Rails web application and am confused about how to utilize the lookup table values in my models. Here is an example model from my app:
table name: donations
id
amount
note
user_id
appeal_id
donation_status_id
donation_type_id
is_anonymous
created_at
updated_at
The fields *donation_status_id* and *donation_type_id* refer to lookup tables. So in my code I have several random places where I make calls like this:
my_donation = Donation.find(params[:id])
if my_donation.donation_status_id == DonationStatus.find_by_name("completed").id
#do something
end
To my inexperienced eyes, a one-off query to the DonationStatus table seems incredibly wasteful here, but I don't see any other good way to do it. The first idea I thought of was to read all my lookup tables into a hash at application startup and then just query against that when I need to.
But is there a better way to do what I am trying to do? Should I not worry about queries like this?
Thanks!
Since you have two models, you should use ActiveRecord Model Associations when building the models.
class Donation
has_one :donation_status
end
class DonationStatus
belongs_to :donation
end
Then when you do
my_donation = Donation.find(params[:id])
if my_donation.donation_status.status_name == 'complete'
#do something
end
For more information, you may want to read up how rails is doing the model associations http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html Don't worry about performance, rails has taken care of that for you if you follow how the way it should be done
How about putting it in a constant? For example, something like this:
class DonationStatus < ActiveRecord::Base
COMPLETED_DONATION_ID = DonationStatus.find_by_name("completed").id
PENDING_DONATION_ID = DonationStatus.find_by_name("pending").id
# ...
end
class DonationsController < ApplicationController
def some_action
my_donation = Donation.find(params[:id])
if my_donation.donation_status_id == DonationStatus::COMPLETED_DONATION_ID
#do something
end
end
This way, DonationStatus.find_by_name("pending").id gets executed exactly one. I'm assuming, of course, that this table won't change often.
BTW, I learned this trick in Dan Chak's book, Enterprise Rails.
EDIT: I forgot to mention: in practice, I declare constants like this:
COMPLETED_DONATION_ID = DonationStatus.find_by_name("completed").id rescue "Can't find 'completed' in donation_statuses table"
What you could do is add this method to Donation:
# Donation.rb
def completed?
self.donation_status.name == 'completed' ? true : false
end
And then just do my_donation.completed?. If this is called a second time, Rails will look to cache instead of going to the DB.
You could add memcached if you want, or use Rails' caching further, and do:
def completed?
return Rails.cache.fetch("status_#{self.donation_status_id}_complete") do
self.donation_status.name == 'completed' ? true : false
end
end
What that will do is make a hash key called (for example) "status_1_complete" and if it's not defined the first time, will evaluate the block and set the value. Otherwise, it will just return the value. That way, if you had 1,000,000,000 donations and each of them had donation_status 1, it would go directly to the cache. memcached is quite fast and popular.